mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2025-02-18 10:53:43 +01:00
6
.gitignore
vendored
6
.gitignore
vendored
@ -3,6 +3,6 @@ Netburner.txt
|
||||
/doc/build
|
||||
/node_modules
|
||||
/dist/*.map
|
||||
/tests/*.map
|
||||
/tests/*.bundle.*
|
||||
/tests/*.css
|
||||
/test/*.map
|
||||
/test/*.bundle.*
|
||||
/test/*.css
|
||||
|
@ -7,35 +7,45 @@
|
||||
p {
|
||||
font-size: $defaultFontSize * 0.8125;
|
||||
}
|
||||
|
||||
a {
|
||||
font-size: $defaultFontSize * 0.875;
|
||||
}
|
||||
h2 {
|
||||
}
|
||||
|
||||
.stock-market-info-and-purchases {
|
||||
> h2 {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
> p {
|
||||
display: block;
|
||||
margin-left: 10px;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
> a, > button {
|
||||
margin: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
#stock-market-list li {
|
||||
#stock-market-list {
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
button {
|
||||
font-size: $defaultFontSize;
|
||||
}
|
||||
}
|
||||
|
||||
#stock-market-container p {
|
||||
padding: 6px;
|
||||
margin: 6px;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
#stock-market-container a {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#stock-market-watchlist-filter {
|
||||
display: block;
|
||||
margin: 5px 5px 5px 10px;
|
||||
padding: 4px;
|
||||
width: 50%;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.stock-market-input {
|
||||
@ -47,14 +57,36 @@
|
||||
color: var(--my-font-color);
|
||||
}
|
||||
|
||||
.stock-market-price-movement-warning {
|
||||
border: 1px solid white;
|
||||
color: red;
|
||||
margin: 2px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.stock-market-position-text {
|
||||
color: #fff;
|
||||
display: block;
|
||||
|
||||
p {
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.stock-market-order-list {
|
||||
overflow-y: auto;
|
||||
max-height: 100px;
|
||||
|
||||
li {
|
||||
color: #fff;
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.stock-market-order-cancel-btn {
|
||||
|
2
dist/engine.bundle.js
vendored
2
dist/engine.bundle.js
vendored
File diff suppressed because one or more lines are too long
2
dist/engineStyle.bundle.js
vendored
Normal file
2
dist/engineStyle.bundle.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
!function(n){function t(t){for(var e,i,f=t[0],c=t[1],l=t[2],p=0,s=[];p<f.length;p++)i=f[p],r[i]&&s.push(r[i][0]),r[i]=0;for(e in c)Object.prototype.hasOwnProperty.call(c,e)&&(n[e]=c[e]);for(a&&a(t);s.length;)s.shift()();return u.push.apply(u,l||[]),o()}function o(){for(var n,t=0;t<u.length;t++){for(var o=u[t],e=!0,f=1;f<o.length;f++){var c=o[f];0!==r[c]&&(e=!1)}e&&(u.splice(t--,1),n=i(i.s=o[0]))}return n}var e={},r={1:0},u=[];function i(t){if(e[t])return e[t].exports;var o=e[t]={i:t,l:!1,exports:{}};return n[t].call(o.exports,o,o.exports,i),o.l=!0,o.exports}i.m=n,i.c=e,i.d=function(n,t,o){i.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:o})},i.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},i.t=function(n,t){if(1&t&&(n=i(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var o=Object.create(null);if(i.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var e in n)i.d(o,e,function(t){return n[t]}.bind(null,e));return o},i.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return i.d(t,"a",t),t},i.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},i.p="";var f=window.webpackJsonp=window.webpackJsonp||[],c=f.push.bind(f);f.push=t,f=f.slice();for(var l=0;l<f.length;l++)t(f[l]);var a=c;u.push([353,0]),o()}({300:function(n,t,o){},302:function(n,t,o){},304:function(n,t,o){},306:function(n,t,o){},308:function(n,t,o){},310:function(n,t,o){},312:function(n,t,o){},314:function(n,t,o){},316:function(n,t,o){},318:function(n,t,o){},320:function(n,t,o){},322:function(n,t,o){},324:function(n,t,o){},326:function(n,t,o){},328:function(n,t,o){},330:function(n,t,o){},332:function(n,t,o){},334:function(n,t,o){},336:function(n,t,o){},338:function(n,t,o){},340:function(n,t,o){},342:function(n,t,o){},344:function(n,t,o){},346:function(n,t,o){},348:function(n,t,o){},350:function(n,t,o){},353:function(n,t,o){"use strict";o.r(t);o(352),o(350),o(348),o(346),o(344),o(342),o(340),o(338),o(336),o(334),o(332),o(330),o(328),o(326),o(324),o(322),o(320),o(318),o(316),o(314),o(312),o(310),o(308),o(306),o(304),o(302),o(300)}});
|
||||
//# sourceMappingURL=engineStyle.bundle.js.map
|
138
dist/engine.css → dist/engineStyle.css
vendored
138
dist/engine.css → dist/engineStyle.css
vendored
@ -1,46 +1,3 @@
|
||||
/* COLORS */
|
||||
/* Attributes */
|
||||
/**
|
||||
* Customized styling for the Code Mirror editor
|
||||
*/
|
||||
#codemirror-form-wrapper {
|
||||
height: 80%;
|
||||
margin: 10px 0px 0px 6px; }
|
||||
|
||||
.CodeMirror {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: 2px solid var(--my-highlight-color);
|
||||
z-index: 1;
|
||||
font-family: "Lucida Console", "Lucida Sans Unicode", "Fira Mono", "Consolas", "Courier New", Courier, monospace, "Times New Roman";
|
||||
font-size: 16px; }
|
||||
|
||||
/**
|
||||
* Highlight matches
|
||||
*/
|
||||
.cm-matchhighlight {
|
||||
background-color: #8F908A; }
|
||||
|
||||
.CodeMirror-selection-highlight-scrollbar {
|
||||
background-color: #8F908A; }
|
||||
|
||||
/**
|
||||
* Show Invisibles
|
||||
*/
|
||||
.cm-whitespace::before {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
color: #404F7D; }
|
||||
|
||||
/**
|
||||
* Vim command display
|
||||
*/
|
||||
#codemirror-vim-command-display-wrapper {
|
||||
background-color: white;
|
||||
font-size: 13px;
|
||||
height: 30px;
|
||||
margin-left: 6px; }
|
||||
|
||||
/* COLORS */
|
||||
/* Attributes */
|
||||
/* COLORS */
|
||||
@ -933,6 +890,49 @@ button {
|
||||
|
||||
/* Specified overrides for Code mirror Editor are defined in codemirror-override.scss */
|
||||
|
||||
/* COLORS */
|
||||
/* Attributes */
|
||||
/**
|
||||
* Customized styling for the Code Mirror editor
|
||||
*/
|
||||
#codemirror-form-wrapper {
|
||||
height: 80%;
|
||||
margin: 10px 0px 0px 6px; }
|
||||
|
||||
.CodeMirror {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: 2px solid var(--my-highlight-color);
|
||||
z-index: 1;
|
||||
font-family: "Lucida Console", "Lucida Sans Unicode", "Fira Mono", "Consolas", "Courier New", Courier, monospace, "Times New Roman";
|
||||
font-size: 16px; }
|
||||
|
||||
/**
|
||||
* Highlight matches
|
||||
*/
|
||||
.cm-matchhighlight {
|
||||
background-color: #8F908A; }
|
||||
|
||||
.CodeMirror-selection-highlight-scrollbar {
|
||||
background-color: #8F908A; }
|
||||
|
||||
/**
|
||||
* Show Invisibles
|
||||
*/
|
||||
.cm-whitespace::before {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
color: #404F7D; }
|
||||
|
||||
/**
|
||||
* Vim command display
|
||||
*/
|
||||
#codemirror-vim-command-display-wrapper {
|
||||
background-color: white;
|
||||
font-size: 13px;
|
||||
height: 30px;
|
||||
margin-left: 6px; }
|
||||
|
||||
/* COLORS */
|
||||
/* Attributes */
|
||||
/**
|
||||
@ -1313,25 +1313,30 @@ button {
|
||||
font-size: 13px; }
|
||||
#stock-market-container a {
|
||||
font-size: 14px; }
|
||||
#stock-market-container h2 {
|
||||
margin-top: 10px;
|
||||
margin-left: 10px;
|
||||
display: block; }
|
||||
|
||||
.stock-market-info-and-purchases > h2 {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
margin-left: 10px; }
|
||||
|
||||
.stock-market-info-and-purchases > p {
|
||||
display: block;
|
||||
margin-left: 10px;
|
||||
width: 70%; }
|
||||
|
||||
.stock-market-info-and-purchases > a, .stock-market-info-and-purchases > button {
|
||||
margin: 10px; }
|
||||
|
||||
#stock-market-list {
|
||||
list-style: none; }
|
||||
#stock-market-list li button {
|
||||
font-size: 16px; }
|
||||
|
||||
#stock-market-container p {
|
||||
padding: 6px;
|
||||
margin: 6px;
|
||||
width: 70%; }
|
||||
|
||||
#stock-market-container a {
|
||||
margin: 10px; }
|
||||
|
||||
#stock-market-watchlist-filter {
|
||||
width: 50%;
|
||||
margin-left: 10px; }
|
||||
display: block;
|
||||
margin: 5px 5px 5px 10px;
|
||||
padding: 4px;
|
||||
width: 50%; }
|
||||
|
||||
.stock-market-input {
|
||||
display: inline-block;
|
||||
@ -1341,13 +1346,28 @@ button {
|
||||
border: 1px solid #fff;
|
||||
color: var(--my-font-color); }
|
||||
|
||||
.stock-market-price-movement-warning {
|
||||
border: 1px solid white;
|
||||
color: red;
|
||||
margin: 2px;
|
||||
padding: 2px; }
|
||||
|
||||
.stock-market-position-text {
|
||||
color: #fff;
|
||||
display: inline-block; }
|
||||
display: block; }
|
||||
.stock-market-position-text p {
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
margin: 4px; }
|
||||
.stock-market-position-text h3 {
|
||||
margin: 4px; }
|
||||
|
||||
.stock-market-order-list {
|
||||
overflow-y: auto;
|
||||
max-height: 100px; }
|
||||
.stock-market-order-list li {
|
||||
color: #fff;
|
||||
padding: 4px; }
|
||||
|
||||
.stock-market-order-cancel-btn {
|
||||
background-color: #000;
|
||||
@ -4866,4 +4886,4 @@ html {
|
||||
margin-right: 0px; }
|
||||
|
||||
|
||||
/*# sourceMappingURL=engine.css.map*/
|
||||
/*# sourceMappingURL=engineStyle.css.map*/
|
623
dist/vendor.bundle.js
vendored
623
dist/vendor.bundle.js
vendored
File diff suppressed because one or more lines are too long
@ -86,8 +86,11 @@ Sleeve memory dictates what a sleeve's synchronization will be when its reset by
|
||||
switching BitNodes. For example, if a sleeve has a memory of 10, then when you
|
||||
switch BitNodes its synchronization will initially be set to 10, rather than 1.
|
||||
|
||||
Memory can only be increased by purchasing upgrades from The Covenant.
|
||||
It is a persistent stat, meaning it never gets reset back to 1.
|
||||
Memory can only be increased by purchasing upgrades from The Covenant. Just like
|
||||
the ability to purchase additional sleeves, this is only available in BitNodes-10
|
||||
and above, and is only available after defeating BitNode-10 at least once.
|
||||
|
||||
Memory is a persistent stat, meaning it never gets reset back to 1.
|
||||
The maximum possible value for a sleeve's memory is 100.
|
||||
|
||||
Re-sleeving
|
||||
|
@ -7,10 +7,14 @@ 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.
|
||||
Fundamentals
|
||||
------------
|
||||
The Stock Market is not as simple as "buy at price X and sell at price Y". The following
|
||||
are several fundamental concepts you need to understand about the stock market.
|
||||
|
||||
.. note:: For those that have experience with finance/trading/investing, please be aware
|
||||
that the game's stock market does not function exactly like it does in the real
|
||||
world. So these concepts below should seem similar, but won't be exactly the same.
|
||||
|
||||
Positions: Long vs Short
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -21,15 +25,73 @@ is the exact opposite. In a Short position you purchase shares of a stock and
|
||||
earn a profit if the price of that stock decreases. This is also called 'shorting'
|
||||
a stock.
|
||||
|
||||
NOTE: Shorting stocks is not available immediately, and must be unlocked later in the
|
||||
.. note:: Shorting stocks is not available immediately, and must be unlocked later in the
|
||||
game.
|
||||
|
||||
.. _gameplay_stock_market_spread:
|
||||
|
||||
Spread (Bid Price & Ask Price)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
The **bid price** is the maximum price at which someone will buy a stock on the
|
||||
stock market.
|
||||
|
||||
The **ask price** is the minimum price that a seller is willing to receive for a stock
|
||||
on the stock market
|
||||
|
||||
The ask price will always be higher than the bid price (This is because if a seller
|
||||
is willing to receive less than the bid price, that transaction is guaranteed to
|
||||
happen). The difference between the bid and ask price is known as the **spread**.
|
||||
A stock's "price" will be the average of the bid and ask price.
|
||||
|
||||
The bid and ask price are important because these are the prices at which a
|
||||
transaction actually occurs. If you purchase a stock in the long position, the cost
|
||||
of your purchase depends on that stock's ask price. If you then try to sell that
|
||||
stock (still in the long position), the price at which you sell is the stock's
|
||||
bid price. Note that this is reversed for a short position. Purchasing a stock
|
||||
in the short position will occur at the stock's bid price, and selling a stock
|
||||
in the short position will occur at the stock's ask price.
|
||||
|
||||
.. _gameplay_stock_market_spread_price_movement:
|
||||
|
||||
Transactions Influencing Stock Price
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Buying or selling a large number of shares of a stock will influence that stock's price.
|
||||
|
||||
Buying a stock in the long position will cause that stock's price to
|
||||
increase. Selling a stock in the long position will cause the stock's price to decrease.
|
||||
The reverse occurs for the short position. The effect of this depends on how many shares
|
||||
are being transacted. More shares will have a bigger effect on the stock price. If
|
||||
only a small number of shares are being transacted, the stock price may not be affected.
|
||||
|
||||
Note that this "influencing" of the stock price **can happen in the middle of a transaction**.
|
||||
For example, assume you are selling 1m shares of a stock. That stock's bid price
|
||||
is currently $100. However, selling 100k shares of the stock causes its price to drop
|
||||
by 1%. This means that for your transaction of 1m shares, the first 100k shares will be
|
||||
sold at $100. Then the next 100k shares will be sold at $99. Then the next 100k shares will
|
||||
be sold at $98.01, and so on.
|
||||
|
||||
This is an important concept to keep in mind if you are trying to purchase/sell a
|
||||
large number of shares, as **it can negatively affect your profits**.
|
||||
|
||||
.. note:: On the Stock Market UI, the displayed profit of a position does not take
|
||||
this "influencing" into account.
|
||||
|
||||
Transactions Influencing Stock Forecast
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
In addition to influencing stock price, buying or selling a large number of shares
|
||||
of a stock will also influence that stock's "forecast". The forecast is the likelihood
|
||||
that the stock will increase or decrease in price. The magnitude of this effect depends
|
||||
on the number of shares being transacted. More shares will have a bigger effect on the
|
||||
stock forecast.
|
||||
|
||||
.. _gameplay_stock_market_order_types:
|
||||
|
||||
Order Types
|
||||
^^^^^^^^^^^
|
||||
There are three different types of orders you can make to buy or sell stocks on the exchange:
|
||||
Market Order, Limit Order, and Stop Order.
|
||||
|
||||
Note that Limit Orders and Stop Orders are not available immediately, and must be unlocked
|
||||
.. note:: Limit Orders and Stop Orders are not available immediately, and must be unlocked
|
||||
later in the game.
|
||||
|
||||
When you place a Market Order to buy or sell a stock, the order executes immediately at
|
||||
@ -71,3 +133,42 @@ A Limit Order to sell will execute if the stock's price <= order's price
|
||||
A Stop Order to buy will execute if the stock's price <= order's price
|
||||
|
||||
A Stop Order to sell will execute if the stock's price >= order's price.
|
||||
|
||||
.. note:: Stop Orders do **not** take into account the fact that transactions can
|
||||
influence a stock's price. Limit Orders, however, do take this into account.
|
||||
|
||||
For example, assume you have a Limit Order set to purchase a stock at
|
||||
$5. Then, the stock's price drops to $5 and your Limit Order executes.
|
||||
However, the transaction causes the stock's price to increase before
|
||||
the order finishes executing all of the shares. Your Limit Order will
|
||||
stop executing, and will continue only when the stock's price drops back to
|
||||
$5 or below.
|
||||
|
||||
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.
|
||||
|
||||
Under the Hood
|
||||
--------------
|
||||
Stock prices are updated very ~6 seconds.
|
||||
|
||||
Whether a stock's price moves up or down is determined by RNG. However,
|
||||
stocks have properties that can influence the way their price moves. These properties
|
||||
are hidden, although some of them can be made visible by purchasing the
|
||||
Four Sigma (4S) Market Data upgrade. Some examples of these properties are:
|
||||
|
||||
* Volatility
|
||||
* Likelihood of increasing or decreasing
|
||||
* How easily a stock's price/forecast is influenced by transactions
|
||||
* Spread percentage
|
||||
* Maximum price (not a real maximum, more of a "soft cap")
|
||||
|
||||
Each stock has its own unique values for these properties.
|
||||
|
||||
Offline Progression
|
||||
-------------------
|
||||
The Stock Market does not change or process anything while the game has closed.
|
||||
However, it does accumulate time when offline. This accumulated time allows
|
||||
the stock market to run 50% faster when the game is opened again. This means
|
||||
that stock prices will update every ~4 seconds instead of 6.
|
||||
|
@ -3,6 +3,50 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
v0.47.0 - 5/17/2019
|
||||
-------------------
|
||||
* Stock Market changes:
|
||||
* Implemented spread. Stock's now have bid and ask prices at which transactions occur
|
||||
* Large transactions will now influence a stock's price and forecast
|
||||
* This "influencing" can take effect in the middle of a transaction
|
||||
* See documentation for more details on these changes
|
||||
* Added getStockAskPrice(), getStockBidPrice() Netscript functions to the TIX API
|
||||
* Added getStockPurchaseCost(), getStockSaleGain() Netscript functions to the TIX API
|
||||
|
||||
* Re-sleeves can no longer have the NeuroFlux Governor augmentation
|
||||
* This is just a temporary patch until the mechanic gets re-worked
|
||||
|
||||
* hack(), grow(), and weaken() functions now take optional arguments for number of threads to use (by MasonD)
|
||||
* codingcontract.attempt() now takes an optional argument that allows you to configure the function to return a contract's reward
|
||||
* Adjusted RAM costs of Netscript Singularity functions (mostly increased)
|
||||
* Adjusted RAM cost of codingcontract.getNumTriesRemaining() Netscript function
|
||||
* Netscript Singularity functions no longer cost extra RAM outside of BitNode-4
|
||||
* Corporation employees no longer have an "age" stat
|
||||
* Gang Wanted level gain rate capped at 100 (per employee)
|
||||
* Script startup/kill is now processed every 3 seconds, instead of 6 seconds
|
||||
* getHackTime(), getGrowTime(), and getWeakenTime() now return Infinity if called on a Hacknet Server
|
||||
* Money/Income tracker now displays money lost from hospitalizations
|
||||
* Exported saves now have a unique filename based on current BitNode and timestamp
|
||||
* Maximum number of Hacknet Servers decreased from 25 to 20
|
||||
* Bug Fix: Corporation employees stats should no longer become negative
|
||||
* Bug Fix: Fixed sleeve.getInformation() throwing error in certain scenarios
|
||||
* Bug Fix: Coding contracts should no longer generate on the w0r1d_d43m0n server
|
||||
* Bug Fix: Duplicate Sleeves now properly have access to all Augmentations if you have a gang
|
||||
* Bug Fix: getAugmentationsFromFaction() & purchaseAugmentation() functions should now work properly if you have a gang
|
||||
* Bug Fix: Fixed issue that caused messages (.msg) to be sent when refreshing/reloading the game
|
||||
* Bug Fix: Purchasing hash upgrades for Bladeburner/Corporation when you don't actually have access to those mechanics no longer gives hashes
|
||||
* Bug Fix: run(), exec(), and spawn() Netscript functions now throw if called with 0 threads
|
||||
* Bug Fix: Faction UI should now automatically update reputation
|
||||
* Bug Fix: Fixed purchase4SMarketData()
|
||||
* Bug Fix: Netscript1.0 now works properly for multiple 'namespace' imports (import * as namespace from "script")
|
||||
* Bug Fix: Terminal 'wget' command now correctly evaluates directory paths
|
||||
* Bug Fix: wget(), write(), and scp() Netscript functions now fail if an invalid filepath is passed in
|
||||
* Bug Fix: Having Corporation warehouses at full capacity should no longer freeze game in certain conditions
|
||||
* Bug Fix: Prevented an exploit that allows you to buy multiple copies of an Augmentation by holding the 'Enter' button
|
||||
* Bug Fix: gang.getOtherGangInformation() now properly returns a deep copy
|
||||
* Bug Fix: Fixed getScriptIncome() returning an undefined value
|
||||
* Bug Fix: Fixed an issue with Hacknet Server hash rate not always updating
|
||||
|
||||
v0.46.3 - 4/20/2019
|
||||
-------------------
|
||||
* Added a new Augmentation: The Shadow's Simulacrum
|
||||
|
@ -64,9 +64,9 @@ documentation_title = '{0} Documentation'.format(project)
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.46'
|
||||
version = '0.47'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.46.3'
|
||||
release = '0.47.0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
@ -27,6 +27,7 @@ secrets that you've been searching for.
|
||||
Script Editors <scripteditors>
|
||||
Game Frozen or Stuck? <gamefrozen>
|
||||
Guides & Tips <guidesandtips>
|
||||
Tools & Resources <toolsandresources>
|
||||
Changelog <changelog>
|
||||
Donate <https://paypal.me/danielyxie>
|
||||
|
||||
|
@ -1,9 +1,14 @@
|
||||
grow() Netscript Function
|
||||
=========================
|
||||
|
||||
.. js:function:: grow(hostname/ip)
|
||||
.. js:function:: grow(hostname/ip[, opts={}])
|
||||
|
||||
:param string hostname/ip: IP or hostname of the target server to grow
|
||||
:param object opts: Optional parameters for configuring function behavior. Properties:
|
||||
|
||||
* threads (*number*) - Number of threads to use for this function.
|
||||
Must be less than or equal to the number of threads the script is running with.
|
||||
|
||||
:returns: The number by which the money on the server was multiplied for the growth
|
||||
:RAM cost: 0.15 GB
|
||||
|
||||
@ -19,3 +24,4 @@ grow() Netscript Function
|
||||
Example::
|
||||
|
||||
grow("foodnstuff");
|
||||
grow("foodnstuff", { threads: 5 }); // Only use 5 threads to grow
|
||||
|
@ -1,9 +1,14 @@
|
||||
hack() Netscript Function
|
||||
=========================
|
||||
|
||||
.. js:function:: hack(hostname/ip)
|
||||
.. js:function:: hack(hostname/ip[, opts={}])
|
||||
|
||||
:param string hostname/ip: IP or hostname of the target server to hack
|
||||
:param object opts: Optional parameters for configuring function behavior. Properties:
|
||||
|
||||
* threads (*number*) - Number of threads to use for this function.
|
||||
Must be less than or equal to the number of threads the script is running with.
|
||||
|
||||
:returns: The amount of money stolen if the hack is successful, and zero otherwise
|
||||
:RAM cost: 0.1 GB
|
||||
|
||||
@ -20,3 +25,4 @@ hack() Netscript Function
|
||||
|
||||
hack("foodnstuff");
|
||||
hack("10.1.2.3");
|
||||
hack("foodnstuff", { threads: 5 }); // Only use 5 threads to hack
|
||||
|
@ -21,4 +21,13 @@ hackAnalyzeThreads() Netscript Function
|
||||
If this function returns 50, this means that if your next `hack()` call
|
||||
is run on a script with 50 threads, it will steal $1m from the `foodnstuff` server.
|
||||
|
||||
**Warning**: The value returned by this function isn't necessarily a whole number.
|
||||
.. warning:: The value returned by this function isn't necessarily a whole number.
|
||||
.. warning:: It is possible for this function to return :code:`Infinity` or :code:`NaN` in
|
||||
certain uncommon scenarios. This is because in JavaScript:
|
||||
|
||||
* :code:`0 / 0 = NaN`
|
||||
* :code:`N / 0 = Infinity` for 0 < N < Infinity.
|
||||
|
||||
For example, if a server has no money available and you want to hack some positive
|
||||
amount from it, then the function would return :code:`Infinity` because
|
||||
this would be impossible.
|
||||
|
@ -1,9 +1,14 @@
|
||||
weaken() Netscript Function
|
||||
===========================
|
||||
|
||||
.. js:function:: weaken(hostname/ip)
|
||||
.. js:function:: weaken(hostname/ip[, opts={}])
|
||||
|
||||
:param string hostname/ip: IP or hostname of the target server to weaken
|
||||
:param object opts: Optional parameters for configuring function behavior. Properties:
|
||||
|
||||
* threads (*number*) - Number of threads to use for this function.
|
||||
Must be less than or equal to the number of threads the script is running with.
|
||||
|
||||
:returns: The amount by which the target server's security level was decreased. This is equivalent to 0.05 multiplied
|
||||
by the number of script threads
|
||||
:RAM cost: 0.15 GB
|
||||
@ -18,3 +23,4 @@ weaken() Netscript Function
|
||||
Example::
|
||||
|
||||
weaken("foodnstuff");
|
||||
weaken("foodnstuff", { threads: 5 }); // Only use 5 threads to weaken
|
||||
|
@ -1,13 +1,20 @@
|
||||
attempt() Netscript Function
|
||||
============================
|
||||
|
||||
.. js:function:: attempt(answer, fn[, hostname/ip=current ip])
|
||||
.. js:function:: attempt(answer, fn[, hostname/ip=current ip, opts={}])
|
||||
|
||||
:param answer: Solution for the contract
|
||||
:param string fn: Filename of the contract
|
||||
:param string hostname/ip: Hostname or IP of the server containing the contract.
|
||||
Optional. Defaults to current server if not provided
|
||||
:param object opts: Optional parameters for configuring function behavior. Properties:
|
||||
|
||||
* returnReward (*boolean*) If truthy, then the function will return a string
|
||||
that states the contract's reward when it is successfully solved.
|
||||
|
||||
Attempts to solve the Coding Contract with the provided solution.
|
||||
|
||||
:returns: Boolean indicating whether the solution was correct
|
||||
:returns: Boolean indicating whether the solution was correct. If the :code:`returnReward`
|
||||
option is configured, then the function will instead return a string. If the
|
||||
contract is successfully solved, the string will contain a description of the
|
||||
contract's reward. Otherwise, it will be an empty string.
|
||||
|
@ -8,8 +8,7 @@ later in 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
|
||||
The Gang mechanic and the Gang API are unlocked in BitNode-2.
|
||||
|
||||
**Gang API functions must be accessed through the 'gang' namespace**
|
||||
|
||||
|
@ -18,8 +18,12 @@ access even after you 'reset' by installing Augmentations
|
||||
|
||||
getStockSymbols() <tixapi/getStockSymbols>
|
||||
getStockPrice() <tixapi/getStockPrice>
|
||||
getStockAskPrice() <tixapi/getStockAskPrice>
|
||||
getStockBidPrice() <tixapi/getStockBidPrice>
|
||||
getStockPosition() <tixapi/getStockPosition>
|
||||
getStockMaxShares() <tixapi/getStockMaxShares>
|
||||
getStockPurchaseCost() <tixapi/getStockPurchaseCost>
|
||||
getStockSaleGain() <tixapi/getStockSaleGain>
|
||||
buyStock() <tixapi/buyStock>
|
||||
sellStock() <tixapi/sellStock>
|
||||
shortStock() <tixapi/shortStock>
|
||||
|
@ -5,7 +5,7 @@ workForFaction() Netscript Function
|
||||
|
||||
:param string factionName: Name of faction to work for. CASE-SENSITIVE
|
||||
:param string workType:
|
||||
Type of work to perform for the faction
|
||||
Type of work to perform for the faction:
|
||||
|
||||
* hacking/hacking contracts/hackingcontracts
|
||||
* field/fieldwork/field work
|
||||
|
@ -61,5 +61,5 @@ getInformation() Netscript Function
|
||||
workChaExpGain: charisma exp gained from work,
|
||||
workMoneyGain: money gained from work,
|
||||
},
|
||||
workRepGain: sl.getRepGain(),
|
||||
workRepGain: Reputation gain rate when working for factions or companies
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
getSleeveAugmentations() Netscript Function
|
||||
=======================================
|
||||
===========================================
|
||||
|
||||
.. js:function:: getSleeveAugmentations(sleeveNumber)
|
||||
|
||||
|
@ -6,7 +6,11 @@ getOrders() Netscript Function
|
||||
:RAM cost: 2.5 GB
|
||||
|
||||
Returns your order book for the stock market. This is an object containing information
|
||||
for all the Limit and Stop Orders you have in the stock market.
|
||||
for all the :ref:`Limit and Stop Orders <gameplay_stock_market_order_types>`
|
||||
you have in the stock market.
|
||||
|
||||
.. note:: This function isn't accessible until you have unlocked the ability to use
|
||||
Limit and Stop Orders.
|
||||
|
||||
The object has the following structure::
|
||||
|
||||
|
12
doc/source/netscript/tixapi/getStockAskPrice.rst
Normal file
12
doc/source/netscript/tixapi/getStockAskPrice.rst
Normal file
@ -0,0 +1,12 @@
|
||||
getStockAskPrice() Netscript Function
|
||||
=====================================
|
||||
|
||||
.. js:function:: getStockAskPrice(sym)
|
||||
|
||||
:param string sym: Stock symbol
|
||||
:RAM cost: 2 GB
|
||||
|
||||
Given a stock's symbol, returns the ask price of that stock (the symbol is a sequence
|
||||
of two to four capital letters, **not** the name of the company to which that stock belongs).
|
||||
|
||||
See :ref:`gameplay_stock_market_spread` for details on what the ask price is.
|
12
doc/source/netscript/tixapi/getStockBidPrice.rst
Normal file
12
doc/source/netscript/tixapi/getStockBidPrice.rst
Normal file
@ -0,0 +1,12 @@
|
||||
getStockBidPrice() Netscript Function
|
||||
=====================================
|
||||
|
||||
.. js:function:: getStockBidPrice(sym)
|
||||
|
||||
:param string sym: Stock symbol
|
||||
:RAM cost: 2 GB
|
||||
|
||||
Given a stock's symbol, returns the bid price of that stock (the symbol is a sequence
|
||||
of two to four capital letters, **not** the name of the company to which that stock belongs).
|
||||
|
||||
See :ref:`gameplay_stock_market_spread` for details on what the bid price is.
|
@ -6,9 +6,12 @@ getStockPrice() Netscript Function
|
||||
:param string sym: Stock symbol
|
||||
:RAM cost: 2 GB
|
||||
|
||||
Returns the price of a stock, given its symbol (NOT the company name). The symbol is a sequence
|
||||
of two to four capital letters.
|
||||
Given a stock's symbol, returns the price of that stock (the symbol is a sequence
|
||||
of two to four capital letters, **not** the name of the company to which that stock belongs).
|
||||
|
||||
.. note:: The stock's price is the average of its bid and ask price.
|
||||
See :ref:`gameplay_stock_market_spread` for details on what this means.
|
||||
|
||||
Example::
|
||||
|
||||
getStockPrice("FISG");
|
||||
getStockPrice("FSIG");
|
||||
|
15
doc/source/netscript/tixapi/getStockPurchaseCost.rst
Normal file
15
doc/source/netscript/tixapi/getStockPurchaseCost.rst
Normal file
@ -0,0 +1,15 @@
|
||||
getStockPurchaseCost() Netscript Function
|
||||
=========================================
|
||||
|
||||
.. js:function:: getStockPurchaseCost(sym, shares, posType)
|
||||
|
||||
:param string sym: Stock symbol
|
||||
:param number shares: Number of shares to purchase
|
||||
:param string posType: Specifies whether the order is a "Long" or "Short" position.
|
||||
The values "L" or "S" can also be used.
|
||||
:RAM cost: 2 GB
|
||||
|
||||
Calculates and returns how much it would cost to buy a given number of
|
||||
shares of a stock. This takes into account :ref:`spread <gameplay_stock_market_spread>`,
|
||||
:ref:`large transactions influencing the price of the stock <gameplay_stock_market_spread_price_movement>`
|
||||
and commission fees.
|
15
doc/source/netscript/tixapi/getStockSaleGain.rst
Normal file
15
doc/source/netscript/tixapi/getStockSaleGain.rst
Normal file
@ -0,0 +1,15 @@
|
||||
getStockSaleGain() Netscript Function
|
||||
=====================================
|
||||
|
||||
.. js:function:: getStockSaleGain(sym, shares, posType)
|
||||
|
||||
:param string sym: Stock symbol
|
||||
:param number shares: Number of shares to purchase
|
||||
:param string posType: Specifies whether the order is a "Long" or "Short" position.
|
||||
The values "L" or "S" can also be used.
|
||||
:RAM cost: 2 GB
|
||||
|
||||
Calculates and returns how much you would gain from selling a given number of
|
||||
shares of a stock. This takes into account :ref:`spread <gameplay_stock_market_spread>`,
|
||||
:ref:`large transactions influencing the price of the stock <gameplay_stock_market_spread_price_movement>`
|
||||
and commission fees.
|
@ -19,8 +19,10 @@ placeOrder() Netscript Function
|
||||
NOT case-sensitive.
|
||||
:RAM cost: 2.5 GB
|
||||
|
||||
Places an order on the stock market. This function only works for `Limit and Stop Orders <http://bitburner.wikia.com/wiki/Stock_Market#Order_Types>`_.
|
||||
|
||||
The ability to place limit and stop orders is **not** immediately available to the player and must be unlocked later on in the game.
|
||||
Places an order on the stock market. This function only works
|
||||
for :ref:`Limit and Stop Orders <gameplay_stock_market_order_types>`.
|
||||
|
||||
Returns true if the order is successfully placed, and false otherwise.
|
||||
|
||||
.. note:: The ability to place limit and stop orders is **not** immediately available to
|
||||
the player and must be unlocked later on in the game.
|
||||
|
23
doc/source/toolsandresources.rst
Normal file
23
doc/source/toolsandresources.rst
Normal file
@ -0,0 +1,23 @@
|
||||
Tools & Resources
|
||||
=================
|
||||
|
||||
Official Script Repository
|
||||
--------------------------
|
||||
There are plans to create an official repository of Bitburner scripts. As of right now,
|
||||
this is not a priority and has not been started. However, if you'd like
|
||||
to contribute scripts now, you can find the repository
|
||||
`here <https://github.com/bitburner-official/bitburner-scripts>`_ and submit pull requests.
|
||||
|
||||
Visual Studio Code Extension
|
||||
----------------------------
|
||||
One user created a Bitburner extension for the Visual Studio Code (VSCode) editor.
|
||||
|
||||
This extension includes several features such as:
|
||||
|
||||
* Dynamic RAM calculation
|
||||
* RAM Usage breakdown
|
||||
* Typescript definition files with jsdoc comments
|
||||
* Netscript syntax highlighting
|
||||
|
||||
You can find more information and download links
|
||||
`on this reddit post <https://www.reddit.com/r/Bitburner/comments/bh48y2/visual_studio_code_ram_calculator_extra/>`_.
|
52
index.html
52
index.html
@ -25,7 +25,7 @@
|
||||
ga('create', 'UA-100157497-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
<link rel="shortcut icon" href="favicon.ico"><link href="dist/vendor.css" rel="stylesheet"><link href="dist/engine.css" rel="stylesheet"></head>
|
||||
<link rel="shortcut icon" href="favicon.ico"><link href="dist/vendor.css" rel="stylesheet"><link href="dist/engineStyle.css" rel="stylesheet"></head>
|
||||
<body>
|
||||
<div id="entire-game-container" style="visibility:hidden;">
|
||||
<div id="mainmenu-container">
|
||||
@ -277,53 +277,7 @@
|
||||
</div>
|
||||
|
||||
<div id="stock-market-container" class="generic-menupage-container">
|
||||
<p>
|
||||
Welcome to the World Stock Exchange (WSE)! <br/><br/>
|
||||
|
||||
To begin trading, you must first purchase an account. WSE accounts will persist
|
||||
after you 'reset' by installing Augmentations.
|
||||
</p>
|
||||
<a id="stock-market-buy-account" class="a-link-button-inactive"> Buy WSE Account </a>
|
||||
<a id="stock-market-investopedia" class="a-link-button">Investopedia</a>
|
||||
|
||||
<h2> Trade Information eXchange (TIX) API </h2>
|
||||
<p>
|
||||
TIX, short for Trade Information eXchange, is the communications protocol supported by the WSE.
|
||||
Purchasing access to the TIX API lets you write code to create your own algorithmic/automated
|
||||
trading strategies.
|
||||
<br/><br/>
|
||||
If you purchase access to the TIX API, you will retain that access even after
|
||||
you 'reset' by installing Augmentations.
|
||||
</p>
|
||||
<a id="stock-market-buy-tix-api" class="a-link-button-inactive">Buy Trade Information eXchange (TIX) API Access</a>
|
||||
|
||||
<h2> Four Sigma (4S) Market Data Feed </h2>
|
||||
<p>
|
||||
Four Sigma's (4S) Market Data Feed provides information about stocks
|
||||
that will help your trading strategies.
|
||||
<br/><br/>
|
||||
If you purchase access to 4S Market Data and/or the 4S TIX API, you will
|
||||
retain that access even after you 'reset' by installing Augmentations.
|
||||
</p>
|
||||
|
||||
<a id="stock-market-buy-4s-data" class="a-link-button-inactive tooltip">
|
||||
Buy 4S Market Data Feed
|
||||
</a>
|
||||
<div class="help-tip-big" id="stock-market-4s-data-help-tip">?</div>
|
||||
|
||||
<a id="stock-market-buy-4s-tix-api" class="a-link-button-inactive tooltip">
|
||||
Buy 4S Market Data TIX API Access
|
||||
</a>
|
||||
|
||||
<p id="stock-market-commission"> </p>
|
||||
<a id="stock-market-mode" class="a-link-button tooltip"></a>
|
||||
<a id="stock-market-expand-tickers" class="a-link-button tooltip">Expand tickers</a>
|
||||
<a id="stock-market-collapse-tickers" class="a-link-button tooltip">Collapse tickers</a>
|
||||
<br/><br/>
|
||||
<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>
|
||||
<!-- React Component -->
|
||||
</div>
|
||||
|
||||
<!-- Log Box -->
|
||||
@ -634,7 +588,7 @@
|
||||
<p>If the game fails to load, consider <a href="?noScripts">killing all scripts</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="dist/vendor.bundle.js"></script><script type="text/javascript" src="dist/engine.bundle.js"></script></body>
|
||||
<script type="text/javascript" src="dist/vendor.bundle.js"></script><script type="text/javascript" src="dist/engine.bundle.js"></script><script type="text/javascript" src="dist/engineStyle.bundle.js"></script></body>
|
||||
|
||||
<!-- Misc Scripts -->
|
||||
<script src="src/ThirdParty/raphael.min.js"></script>
|
||||
|
1238
package-lock.json
generated
1238
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@ -9,8 +9,9 @@
|
||||
"@types/numeral": "0.0.25",
|
||||
"@types/react": "^16.8.6",
|
||||
"@types/react-dom": "^16.8.2",
|
||||
"acorn": "^5.0.0",
|
||||
"acorn": "^5.7.3",
|
||||
"acorn-dynamic-import": "^2.0.0",
|
||||
"acorn-walk": "^6.1.1",
|
||||
"ajv": "^5.1.5",
|
||||
"ajv-keywords": "^2.0.0",
|
||||
"async": "^2.6.1",
|
||||
@ -48,8 +49,7 @@
|
||||
"beautify-lint": "^1.0.3",
|
||||
"benchmark": "^2.1.1",
|
||||
"bundle-loader": "~0.5.0",
|
||||
"chai": "^4.1.2",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chai": "^4.2.0",
|
||||
"css-loader": "^0.28.11",
|
||||
"es6-promise-polyfill": "^1.1.1",
|
||||
"eslint": "^4.19.1",
|
||||
@ -59,15 +59,18 @@
|
||||
"i18n-webpack-plugin": "^1.0.0",
|
||||
"istanbul": "^0.4.5",
|
||||
"js-beautify": "^1.5.10",
|
||||
"jsdom": "^15.0.0",
|
||||
"jsdom-global": "^3.0.2",
|
||||
"json5": "^1.0.1",
|
||||
"less": "^3.9.0",
|
||||
"less-loader": "^4.1.0",
|
||||
"lodash": "^4.17.10",
|
||||
"mini-css-extract-plugin": "^0.4.1",
|
||||
"mkdirp": "^0.5.1",
|
||||
"mocha": "^5.2.0",
|
||||
"mocha-lcov-reporter": "^1.0.0",
|
||||
"mocha": "^6.1.4",
|
||||
"mochapack": "^1.1.1",
|
||||
"node-sass": "^4.10.0",
|
||||
"null-loader": "^1.0.0",
|
||||
"raw-loader": "~0.5.0",
|
||||
"sass-loader": "^7.0.3",
|
||||
"script-loader": "~0.7.0",
|
||||
@ -106,13 +109,15 @@
|
||||
"start:dev": "webpack-dev-server --progress --env.devServer --mode development",
|
||||
"build": "webpack --mode production",
|
||||
"build:dev": "webpack --mode development",
|
||||
"build:test": "webpack --config webpack.config-test.js",
|
||||
"lint": "npm run lint:typescript & npm run lint:javascript & npm run lint:style",
|
||||
"lint:javascript": "eslint *.js ./src/**/*.js ./tests/**/*.js ./utils/**/*.js",
|
||||
"lint:style": "stylelint ./css/*",
|
||||
"lint:typescript": "tslint --project . --exclude **/*.d.ts --format stylish src/**/*.ts utils/**/*.ts",
|
||||
"preinstall": "node ./scripts/engines-check.js",
|
||||
"test": "mochapack --webpack-config webpack.config-test.js -r jsdom-global/register ./test/index.js",
|
||||
"watch": "webpack --watch --mode production",
|
||||
"watch:dev": "webpack --watch --mode development"
|
||||
},
|
||||
"version": "0.46.2"
|
||||
"version": "0.47.0"
|
||||
}
|
||||
|
@ -242,7 +242,6 @@ function updateActiveScriptsItems(maxTasks=150) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!routing.isOn(Page.ActiveScripts)) { return; }
|
||||
let total = 0;
|
||||
for (var i = 0; i < workerScripts.length; ++i) {
|
||||
try {
|
||||
@ -252,6 +251,7 @@ function updateActiveScriptsItems(maxTasks=150) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!routing.isOn(Page.ActiveScripts)) { return total; }
|
||||
getElementById("active-scripts-total-production-active").innerText = numeralWrapper.formatMoney(total);
|
||||
getElementById("active-scripts-total-prod-aug-total").innerText = numeralWrapper.formatMoney(Player.scriptProdSinceLastAug);
|
||||
getElementById("active-scripts-total-prod-aug-avg").innerText = numeralWrapper.formatMoney(Player.scriptProdSinceLastAug / (Player.playtimeSinceLastAug/1000));
|
||||
|
@ -7,7 +7,6 @@ import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"
|
||||
import { CONSTANTS } from "../Constants";
|
||||
import { Factions,
|
||||
factionExists } from "../Faction/Factions";
|
||||
import { hasBladeburnerSF } from "../NetscriptFunctions";
|
||||
import { addWorkerScript } from "../NetscriptWorker";
|
||||
import { Player } from "../Player";
|
||||
import { prestigeAugmentation } from "../Prestige";
|
||||
@ -2094,7 +2093,7 @@ function installAugmentations(cbScript=null) {
|
||||
}
|
||||
var runningScriptObj = new RunningScript(script, []); //No args
|
||||
runningScriptObj.threads = 1; //Only 1 thread
|
||||
home.runScript(runningScriptObj, Player);
|
||||
home.runScript(runningScriptObj, Player.hacknet_node_money_mult);
|
||||
addWorkerScript(runningScriptObj, home);
|
||||
}
|
||||
}
|
||||
@ -2105,17 +2104,6 @@ function augmentationExists(name) {
|
||||
return Augmentations.hasOwnProperty(name);
|
||||
}
|
||||
|
||||
//Used for testing balance
|
||||
function giveAllAugmentations() {
|
||||
for (var name in Augmentations) {
|
||||
var aug = Augmentations[name];
|
||||
if (aug == null) {continue;}
|
||||
var ownedAug = new PlayerOwnedAugmentation(name);
|
||||
Player.augmentations.push(ownedAug);
|
||||
}
|
||||
Player.reapplyAllAugmentations();
|
||||
}
|
||||
|
||||
function displayAugmentationsContent(contentEl) {
|
||||
removeChildrenFromElement(contentEl);
|
||||
contentEl.appendChild(createElement("h1", {
|
||||
@ -2323,6 +2311,13 @@ function displaySourceFiles(listElement, sourceFiles) {
|
||||
}
|
||||
}
|
||||
|
||||
export function isRepeatableAug(aug) {
|
||||
const augName = (aug instanceof Augmentation) ? aug.name : aug;
|
||||
|
||||
if (augName === AugmentationNames.NeuroFluxGovernor) { return true; }
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export {installAugmentations,
|
||||
initAugmentations, applyAugmentation, augmentationExists,
|
||||
|
@ -7,6 +7,7 @@ import { Factions } from "./Faction/Factions";
|
||||
import { Player } from "./Player";
|
||||
import { AllServers } from "./Server/AllServers";
|
||||
import { GetServerByHostname } from "./Server/ServerHelpers";
|
||||
import { SpecialServerNames } from "./Server/SpecialServerIps";
|
||||
|
||||
import { getRandomInt } from "../utils/helpers/getRandomInt";
|
||||
|
||||
@ -162,7 +163,9 @@ function getRandomServer() {
|
||||
// An infinite loop shouldn't ever happen, but to be safe we'll use
|
||||
// a for loop with a limited number of tries
|
||||
for (let i = 0; i < 200; ++i) {
|
||||
if (randServer.purchasedByPlayer === false) { break; }
|
||||
if (!randServer.purchasedByPlayer && randServer.hostname !== SpecialServerNames.WorldDaemon) {
|
||||
break;
|
||||
}
|
||||
randIndex = getRandomInt(0, servers.length - 1);
|
||||
randServer = AllServers[servers[randIndex]];
|
||||
}
|
||||
|
@ -17,9 +17,6 @@ import { createElement } from "../utils/uiHelpers/createElement";
|
||||
import { createPopup } from "../utils/uiHelpers/createPopup";
|
||||
import { removeElementById } from "../utils/uiHelpers/removeElementById";
|
||||
|
||||
|
||||
|
||||
|
||||
/* tslint:disable:no-magic-numbers completed-docs max-classes-per-file no-console */
|
||||
|
||||
/* Represents different types of problems that a Coding Contract can have */
|
||||
@ -205,6 +202,7 @@ export class CodingContract {
|
||||
}
|
||||
},
|
||||
placeholder: "Enter Solution here",
|
||||
width: "50%",
|
||||
}) as HTMLInputElement;
|
||||
solveBtn = createElement("a", {
|
||||
class: "a-link-button",
|
||||
|
124
src/Constants.ts
124
src/Constants.ts
@ -6,7 +6,7 @@
|
||||
import { IMap } from "./types";
|
||||
|
||||
export let CONSTANTS: IMap<any> = {
|
||||
Version: "0.46.3",
|
||||
Version: "0.47.0",
|
||||
|
||||
/** Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience
|
||||
* and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then
|
||||
@ -38,60 +38,6 @@ export let CONSTANTS: IMap<any> = {
|
||||
// NeuroFlux Governor Augmentation cost multiplier
|
||||
NeuroFluxGovernorLevelMult: 1.14,
|
||||
|
||||
// RAM Costs for Netscript functions
|
||||
ScriptBaseRamCost: 1.6,
|
||||
ScriptDomRamCost: 25,
|
||||
ScriptWhileRamCost: 0,
|
||||
ScriptForRamCost: 0,
|
||||
ScriptIfRamCost: 0,
|
||||
ScriptHackRamCost: 0.1,
|
||||
ScriptHackAnalyzeRamCost: 1,
|
||||
ScriptGrowRamCost: 0.15,
|
||||
ScriptGrowthAnalyzeRamCost: 1,
|
||||
ScriptWeakenRamCost: 0.15,
|
||||
ScriptScanRamCost: 0.2,
|
||||
ScriptPortProgramRamCost: 0.05,
|
||||
ScriptRunRamCost: 1.0,
|
||||
ScriptExecRamCost: 1.3,
|
||||
ScriptSpawnRamCost: 2.0,
|
||||
ScriptScpRamCost: 0.6,
|
||||
ScriptKillRamCost: 0.5,
|
||||
ScriptHasRootAccessRamCost: 0.05,
|
||||
ScriptGetHostnameRamCost: 0.05,
|
||||
ScriptGetHackingLevelRamCost: 0.05,
|
||||
ScriptGetMultipliersRamCost: 4.0,
|
||||
ScriptGetServerRamCost: 0.1,
|
||||
ScriptFileExistsRamCost: 0.1,
|
||||
ScriptIsRunningRamCost: 0.1,
|
||||
ScriptHacknetNodesRamCost: 4.0,
|
||||
ScriptHNUpgLevelRamCost: 0.4,
|
||||
ScriptHNUpgRamRamCost: 0.6,
|
||||
ScriptHNUpgCoreRamCost: 0.8,
|
||||
ScriptGetStockRamCost: 2.0,
|
||||
ScriptBuySellStockRamCost: 2.5,
|
||||
ScriptGetPurchaseServerRamCost: 0.25,
|
||||
ScriptPurchaseServerRamCost: 2.25,
|
||||
ScriptGetPurchasedServerLimit: 0.05,
|
||||
ScriptGetPurchasedServerMaxRam: 0.05,
|
||||
ScriptRoundRamCost: 0.05,
|
||||
ScriptReadWriteRamCost: 1.0,
|
||||
ScriptArbScriptRamCost: 1.0,
|
||||
ScriptGetScriptRamCost: 0.1,
|
||||
ScriptGetHackTimeRamCost: 0.05,
|
||||
ScriptGetFavorToDonate: 0.10,
|
||||
ScriptCodingContractBaseRamCost: 10,
|
||||
ScriptSleeveBaseRamCost: 4,
|
||||
|
||||
ScriptSingularityFn1RamCost: 1,
|
||||
ScriptSingularityFn2RamCost: 2,
|
||||
ScriptSingularityFn3RamCost: 3,
|
||||
|
||||
ScriptSingularityFnRamMult: 2, // Multiplier for RAM cost outside of BN-4
|
||||
|
||||
ScriptGangApiBaseRamCost: 4,
|
||||
|
||||
ScriptBladeburnerApiBaseRamCost: 4,
|
||||
|
||||
NumNetscriptPorts: 20,
|
||||
|
||||
// Server-related constants
|
||||
@ -275,35 +221,47 @@ export let CONSTANTS: IMap<any> = {
|
||||
|
||||
LatestUpdate:
|
||||
`
|
||||
v0.46.3
|
||||
* Added a new Augmentation: The Shadow's Simulacrum
|
||||
* Improved tab autocompletion feature in Terminal so that it works better with directories
|
||||
* Bug Fix: Tech vendor location UI now properly refreshed when purchasing a TOR router
|
||||
* Bug Fix: Fixed UI issue with faction donations
|
||||
* Bug Fix: The money statistics & breakdown should now properly track money earned from Hacknet Server (hashes -> money)
|
||||
* Bug Fix: Fixed issue with changing input in 'Minimum Path Sum in a Triangle' coding contract problem
|
||||
* Fixed several typos in various places
|
||||
v0.47.0
|
||||
* Stock Market changes:
|
||||
** Implemented spread. Stock's now have bid and ask prices at which transactions occur
|
||||
** Large transactions will now influence a stock's price and forecast
|
||||
** This "influencing" can take effect in the middle of a transaction
|
||||
** See documentation for more details on these changes
|
||||
** Added getStockAskPrice(), getStockBidPrice() Netscript functions to the TIX API
|
||||
** Added getStockPurchaseCost(), getStockSaleGain() Netscript functions to the TIX API
|
||||
|
||||
v0.46.2
|
||||
* Source-File 2 now allows you to form gangs in other BitNodes when your karma reaches a very large negative value
|
||||
** (Karma is a hidden stat and is lowered by committing crimes)
|
||||
* Gang changes:
|
||||
** Bug Fix: Gangs can no longer clash with themselve
|
||||
** Bug Fix: Winning against another gang should properly reduce their power
|
||||
* Re-sleeves can no longer have the NeuroFlux Governor augmentation
|
||||
** This is just a temporary patch until the mechanic gets re-worked
|
||||
|
||||
* Bug Fix: Terminal 'wget' command now works properly
|
||||
* Bug Fix: Hacknet Server Hash upgrades now properly reset upon installing Augs/switching BitNodes
|
||||
* Bug Fix: Fixed button for creating Corporations
|
||||
|
||||
v0.46.1
|
||||
* Added a very rudimentary directory system to the Terminal
|
||||
** Details here: https://bitburner.readthedocs.io/en/latest/basicgameplay/terminal.html#filesystem-directories
|
||||
* Added numHashes(), hashCost(), and spendHashes() functions to the Netscript Hacknet Node API
|
||||
* 'Generate Coding Contract' hash upgrade is now more expensive
|
||||
* 'Generate Coding Contract' hash upgrade now generates the contract randomly on the server, rather than on home computer
|
||||
* The cost of selling hashes for money no longer increases each time
|
||||
* Selling hashes for money now costs 4 hashes (in exchange for $1m)
|
||||
* Bug Fix: Hacknet Node earnings should work properly when game is inactive/offline
|
||||
* Bug Fix: Duplicate Sleeve augmentations are now properly reset when switching to a new BitNode
|
||||
* hack(), grow(), and weaken() functions now take optional arguments for number of threads to use (by MasonD)
|
||||
* codingcontract.attempt() now takes an optional argument that allows you to configure the function to return a contract's reward
|
||||
* Adjusted RAM costs of Netscript Singularity functions (mostly increased)
|
||||
* Adjusted RAM cost of codingcontract.getNumTriesRemaining() Netscript function
|
||||
* Netscript Singularity functions no longer cost extra RAM outside of BitNode-4
|
||||
* Corporation employees no longer have an "age" stat
|
||||
* Gang Wanted level gain rate capped at 100 (per employee)
|
||||
* Script startup/kill is now processed every 3 seconds, instead of 6 seconds
|
||||
* getHackTime(), getGrowTime(), and getWeakenTime() now return Infinity if called on a Hacknet Server
|
||||
* Money/Income tracker now displays money lost from hospitalizations
|
||||
* Exported saves now have a unique filename based on current BitNode and timestamp
|
||||
* Maximum number of Hacknet Servers decreased from 25 to 20
|
||||
* Bug Fix: Corporation employees stats should no longer become negative
|
||||
* Bug Fix: Fixed sleeve.getInformation() throwing error in certain scenarios
|
||||
* Bug Fix: Coding contracts should no longer generate on the w0r1d_d43m0n server
|
||||
* Bug Fix: Duplicate Sleeves now properly have access to all Augmentations if you have a gang
|
||||
* Bug Fix: getAugmentationsFromFaction() & purchaseAugmentation() functions should now work properly if you have a gang
|
||||
* Bug Fix: Fixed issue that caused messages (.msg) to be sent when refreshing/reloading the game
|
||||
* Bug Fix: Purchasing hash upgrades for Bladeburner/Corporation when you don't actually have access to those mechanics no longer gives hashes
|
||||
* Bug Fix: run(), exec(), and spawn() Netscript functions now throw if called with 0 threads
|
||||
* Bug Fix: Faction UI should now automatically update reputation
|
||||
* Bug Fix: Fixed purchase4SMarketData()
|
||||
* Bug Fix: Netscript1.0 now works properly for multiple 'namespace' imports (import * as namespace from "script")
|
||||
* Bug Fix: Terminal 'wget' command now correctly evaluates directory paths
|
||||
* Bug Fix: wget(), write(), and scp() Netscript functions now fail if an invalid filepath is passed in
|
||||
* Bug Fix: Having Corporation warehouses at full capacity should no longer freeze game in certain conditions
|
||||
* Bug Fix: Prevented an exploit that allows you to buy multiple copies of an Augmentation by holding the 'Enter' button
|
||||
* Bug Fix: gang.getOtherGangInformation() now properly returns a deep copy
|
||||
* Bug Fix: Fixed getScriptIncome() returning an undefined value
|
||||
* Bug Fix: Fixed an issue with Hacknet Server hash rate not always updating
|
||||
`
|
||||
}
|
||||
|
@ -521,15 +521,18 @@ Industry.prototype.process = function(marketCycles=1, state, company) {
|
||||
}
|
||||
|
||||
// Process production, purchase, and import/export of materials
|
||||
var res = this.processMaterials(marketCycles, company);
|
||||
let res = this.processMaterials(marketCycles, company);
|
||||
if (Array.isArray(res)) {
|
||||
this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]);
|
||||
this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]);
|
||||
}
|
||||
|
||||
// Process creation, production & sale of products
|
||||
res = this.processProducts(marketCycles, company);
|
||||
if (Array.isArray(res)) {
|
||||
this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]);
|
||||
this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Process change in demand and competition for this industry's materials
|
||||
@ -846,7 +849,7 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
|
||||
sellAmt = (sellAmt * SecsPerMarketCycle * marketCycles);
|
||||
sellAmt = Math.min(mat.qty, sellAmt);
|
||||
if (sellAmt < 0) {
|
||||
console.log("sellAmt calculated to be negative");
|
||||
console.warn(`sellAmt calculated to be negative for ${matName} in ${city}`);
|
||||
mat.sll = 0;
|
||||
continue;
|
||||
}
|
||||
@ -901,7 +904,9 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
|
||||
|
||||
// Make sure theres enough space in warehouse
|
||||
if (expWarehouse.sizeUsed >= expWarehouse.size) {
|
||||
return; //Warehouse at capacity
|
||||
// Warehouse at capacity. Exporting doesnt
|
||||
// affect revenue so just return 0's
|
||||
return [0, 0];
|
||||
} else {
|
||||
var maxAmt = Math.floor((expWarehouse.size - expWarehouse.sizeUsed) / MaterialSizes[matName]);
|
||||
amt = Math.min(maxAmt, amt);
|
||||
@ -1463,7 +1468,6 @@ function Employee(params={}) {
|
||||
this.hap = params.happiness ? params.happiness : getRandomInt(50, 100);
|
||||
this.ene = params.energy ? params.energy : getRandomInt(50, 100);
|
||||
|
||||
this.age = params.age ? params.age : getRandomInt(20, 50);
|
||||
this.int = params.intelligence ? params.intelligence : getRandomInt(10, 50);
|
||||
this.cha = params.charisma ? params.charisma : getRandomInt(10, 50);
|
||||
this.exp = params.experience ? params.experience : getRandomInt(10, 50);
|
||||
@ -1482,13 +1486,7 @@ function Employee(params={}) {
|
||||
Employee.prototype.process = function(marketCycles=1, office) {
|
||||
var gain = 0.003 * marketCycles,
|
||||
det = gain * Math.random();
|
||||
this.age += gain;
|
||||
this.exp += gain;
|
||||
if (this.age > 150) {
|
||||
this.int -= det;
|
||||
this.eff -= det;
|
||||
this.cha -= det;
|
||||
}
|
||||
|
||||
// Employee salaries slowly go up over time
|
||||
this.cyclesUntilRaise -= marketCycles;
|
||||
@ -1577,7 +1575,6 @@ Employee.prototype.createUI = function(panel, corporation, industry) {
|
||||
innerHTML:"Morale: " + formatNumber(this.mor, 3) + "<br>" +
|
||||
"Happiness: " + formatNumber(this.hap, 3) + "<br>" +
|
||||
"Energy: " + formatNumber(this.ene, 3) + "<br>" +
|
||||
"Age: " + formatNumber(this.age, 3) + "<br>" +
|
||||
"Intelligence: " + formatNumber(effInt, 3) + "<br>" +
|
||||
"Charisma: " + formatNumber(effCha, 3) + "<br>" +
|
||||
"Experience: " + formatNumber(this.exp, 3) + "<br>" +
|
||||
|
@ -495,8 +495,6 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
<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)}
|
||||
|
@ -4,6 +4,7 @@ import ReactDOM from "react-dom";
|
||||
import { FactionRoot } from "./ui/Root";
|
||||
|
||||
import { Augmentations } from "../Augmentation/Augmentations";
|
||||
import { isRepeatableAug } from "../Augmentation/AugmentationHelpers";
|
||||
import { PlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
|
||||
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
|
||||
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
|
||||
@ -93,7 +94,12 @@ export function purchaseAugmentationBoxCreate(aug, fac) {
|
||||
const yesBtn = yesNoBoxGetYesButton();
|
||||
yesBtn.innerHTML = "Purchase";
|
||||
yesBtn.addEventListener("click", function() {
|
||||
if (!isRepeatableAug(aug) && Player.hasAugmentation(aug)) {
|
||||
return;
|
||||
}
|
||||
|
||||
purchaseAugmentation(aug, fac);
|
||||
yesNoBoxClose();
|
||||
});
|
||||
|
||||
const noBtn = yesNoBoxGetNoButton();
|
||||
@ -204,7 +210,6 @@ export function purchaseAugmentation(aug, fac, sing=false) {
|
||||
"Please report this to the game developer with an explanation of how to " +
|
||||
"reproduce this.");
|
||||
}
|
||||
yesNoBoxClose();
|
||||
}
|
||||
|
||||
export function getNextNeurofluxLevel() {
|
||||
|
@ -30,10 +30,24 @@ const infoStyleMarkup = {
|
||||
}
|
||||
|
||||
export class Info extends React.Component<IProps, any> {
|
||||
render() {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
const formattedRep = numeralWrapper.format(this.props.faction.playerReputation, "0.000a");
|
||||
this.getFavorGainText = this.getFavorGainText.bind(this);
|
||||
this.getReputationText = this.getReputationText.bind(this);
|
||||
}
|
||||
|
||||
getFavorGainText(): string {
|
||||
const favorGain = this.props.faction.getFavorGain()[0];
|
||||
return `You will earn ${numeralWrapper.format(favorGain, "0,0")} faction favor upon resetting after installing an Augmentation`
|
||||
}
|
||||
|
||||
getReputationText(): string {
|
||||
const formattedRep = numeralWrapper.format(this.props.faction.playerReputation, "0.000a");
|
||||
return `Reputation: ${formattedRep}`
|
||||
}
|
||||
|
||||
render() {
|
||||
const favorTooltip = "Faction favor increases the rate at which you earn reputation for " +
|
||||
"this faction by 1% per favor. Faction favor is gained whenever you " +
|
||||
"reset after installing an Augmentation. The amount of " +
|
||||
@ -51,8 +65,8 @@ export class Info extends React.Component<IProps, any> {
|
||||
<p style={blockStyleMarkup}>-------------------------</p>
|
||||
<AutoupdatingParagraph
|
||||
intervalTime={5e3}
|
||||
text={`Reputation: ${formattedRep}`}
|
||||
tooltip={`You will earn ${numeralWrapper.format(favorGain, "0,0")} faction favor upon resetting after installing an Augmentation`}
|
||||
getText={this.getReputationText}
|
||||
getTooltip={this.getFavorGainText}
|
||||
/>
|
||||
<p style={blockStyleMarkup}>-------------------------</p>
|
||||
<ParagraphWithTooltip
|
||||
|
@ -279,7 +279,7 @@ export class FactionRoot extends React.Component<IProps, IState> {
|
||||
{
|
||||
canPurchaseSleeves &&
|
||||
<Option
|
||||
buttonText={"Purchase Duplicate Sleeves"}
|
||||
buttonText={"Purchase & Upgrade Duplicate Sleeves"}
|
||||
infoText={sleevePurchasesInfo}
|
||||
onClick={this.sleevePurchases}
|
||||
/>
|
||||
|
10
src/Gang.js
10
src/Gang.js
@ -683,7 +683,7 @@ GangMember.prototype.calculateRespectGain = function(gang) {
|
||||
GangMember.prototype.calculateWantedLevelGain = function(gang) {
|
||||
const task = this.getTask();
|
||||
if (task == null || !(task instanceof GangMemberTask) || task.baseWanted === 0) { return 0; }
|
||||
var statWeight = (task.hackWeight/100) * this.hack +
|
||||
let statWeight = (task.hackWeight / 100) * this.hack +
|
||||
(task.strWeight / 100) * this.str +
|
||||
(task.defWeight / 100) * this.def +
|
||||
(task.dexWeight / 100) * this.dex +
|
||||
@ -694,9 +694,13 @@ GangMember.prototype.calculateWantedLevelGain = function(gang) {
|
||||
const territoryMult = Math.pow(AllGangs[gang.facName].territory * 100, task.territory.wanted) / 100;
|
||||
if (isNaN(territoryMult) || territoryMult <= 0) { return 0; }
|
||||
if (task.baseWanted < 0) {
|
||||
return 0.5 * task.baseWanted * statWeight * territoryMult;
|
||||
return 0.4 * task.baseWanted * statWeight * territoryMult;
|
||||
} else {
|
||||
return 7 * task.baseWanted / (Math.pow(3 * statWeight * territoryMult, 0.8));
|
||||
const calc = 7 * task.baseWanted / (Math.pow(3 * statWeight * territoryMult, 0.8));
|
||||
|
||||
// Put an arbitrary cap on this to prevent wanted level from rising too fast if the
|
||||
// denominator is very small. Might want to rethink formula later
|
||||
return Math.min(100, calc);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,34 +6,34 @@
|
||||
*/
|
||||
import { IReturnStatus } from "../types";
|
||||
|
||||
import { HacknetServer } from "../Hacknet/HacknetServer";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import { Server } from "../Server/Server";
|
||||
|
||||
function baseCheck(server: Server | HacknetServer, fnName: string): IReturnStatus {
|
||||
if (server instanceof HacknetServer) {
|
||||
function baseCheck(server: Server, fnName: string): IReturnStatus {
|
||||
const hostname = server.hostname;
|
||||
|
||||
if (!("requiredHackingSkill" in server)) {
|
||||
return {
|
||||
res: false,
|
||||
msg: `Cannot ${fnName} ${server.hostname} server because it is a Hacknet Node`
|
||||
msg: `Cannot ${fnName} ${hostname} server because it is a Hacknet Node`
|
||||
}
|
||||
}
|
||||
|
||||
if (server.hasAdminRights === false) {
|
||||
return {
|
||||
res: false,
|
||||
msg: `Cannot ${fnName} ${server.hostname} server because you do not have root access`,
|
||||
msg: `Cannot ${fnName} ${hostname} server because you do not have root access`,
|
||||
}
|
||||
}
|
||||
|
||||
return { res: true }
|
||||
}
|
||||
|
||||
export function netscriptCanHack(server: Server | HacknetServer, p: IPlayer): IReturnStatus {
|
||||
export function netscriptCanHack(server: Server, p: IPlayer): IReturnStatus {
|
||||
const initialCheck = baseCheck(server, "hack");
|
||||
if (!initialCheck.res) { return initialCheck; }
|
||||
|
||||
let s = <Server>server;
|
||||
|
||||
let s = server;
|
||||
if (s.requiredHackingSkill > p.hacking_skill) {
|
||||
return {
|
||||
res: false,
|
||||
@ -44,10 +44,10 @@ export function netscriptCanHack(server: Server | HacknetServer, p: IPlayer): IR
|
||||
return { res: true }
|
||||
}
|
||||
|
||||
export function netscriptCanGrow(server: Server | HacknetServer): IReturnStatus {
|
||||
export function netscriptCanGrow(server: Server): IReturnStatus {
|
||||
return baseCheck(server, "grow");
|
||||
}
|
||||
|
||||
export function netscriptCanWeaken(server: Server | HacknetServer): IReturnStatus {
|
||||
export function netscriptCanWeaken(server: Server): IReturnStatus {
|
||||
return baseCheck(server, "weaken");
|
||||
}
|
||||
|
@ -1,26 +1,32 @@
|
||||
import { HacknetNode,
|
||||
import {
|
||||
HacknetNode,
|
||||
BaseCostForHacknetNode,
|
||||
HacknetNodePurchaseNextMult,
|
||||
HacknetNodeMaxLevel,
|
||||
HacknetNodeMaxRam,
|
||||
HacknetNodeMaxCores } from "./HacknetNode";
|
||||
import { HacknetServer,
|
||||
HacknetNodeMaxCores
|
||||
} from "./HacknetNode";
|
||||
import {
|
||||
HacknetServer,
|
||||
BaseCostForHacknetServer,
|
||||
HacknetServerPurchaseMult,
|
||||
HacknetServerMaxLevel,
|
||||
HacknetServerMaxRam,
|
||||
HacknetServerMaxCores,
|
||||
HacknetServerMaxCache,
|
||||
MaxNumberHacknetServers } from "./HacknetServer";
|
||||
MaxNumberHacknetServers
|
||||
} from "./HacknetServer";
|
||||
import { HashManager } from "./HashManager";
|
||||
import { HashUpgrades } from "./HashUpgrades";
|
||||
|
||||
import { generateRandomContract } from "../CodingContractGenerator";
|
||||
import { iTutorialSteps, iTutorialNextStep,
|
||||
ITutorial} from "../InteractiveTutorial";
|
||||
import {
|
||||
iTutorialSteps,
|
||||
iTutorialNextStep,
|
||||
ITutorial
|
||||
} from "../InteractiveTutorial";
|
||||
import { Player } from "../Player";
|
||||
import { AddToAllServers,
|
||||
AllServers } from "../Server/AllServers";
|
||||
import { AddToAllServers, AllServers } from "../Server/AllServers";
|
||||
import { GetServerByHostname } from "../Server/ServerHelpers";
|
||||
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
|
||||
import { Page, routing } from "../ui/navigationTracking";
|
||||
@ -66,7 +72,7 @@ export function purchaseHacknet() {
|
||||
if (!Player.canAfford(cost)) { return -1; }
|
||||
Player.loseMoney(cost);
|
||||
const server = Player.createHacknetServer();
|
||||
Player.hashManager.updateCapacity(Player);
|
||||
updateHashManagerCapacity();
|
||||
|
||||
return numOwned;
|
||||
} else {
|
||||
@ -79,8 +85,7 @@ export function purchaseHacknet() {
|
||||
|
||||
// Auto generate a name for the Node
|
||||
const name = "hacknet-node-" + numOwned;
|
||||
const node = new HacknetNode(name);
|
||||
node.updateMoneyGainRate(Player);
|
||||
const node = new HacknetNode(name, Player.hacknet_node_money_mult);
|
||||
|
||||
Player.loseMoney(cost);
|
||||
Player.hacknetNodes.push(node);
|
||||
@ -116,26 +121,26 @@ export function getMaxNumberLevelUpgrades(nodeObj, maxLevel) {
|
||||
throw new Error(`getMaxNumberLevelUpgrades() called without maxLevel arg`);
|
||||
}
|
||||
|
||||
if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(1, Player))) {
|
||||
if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(1, Player.hacknet_node_level_cost_mult))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let min = 1;
|
||||
let max = maxLevel - 1;
|
||||
let levelsToMax = maxLevel - nodeObj.level;
|
||||
if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(levelsToMax, Player))) {
|
||||
if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(levelsToMax, Player.hacknet_node_level_cost_mult))) {
|
||||
return levelsToMax;
|
||||
}
|
||||
|
||||
while (min <= max) {
|
||||
var curr = (min + max) / 2 | 0;
|
||||
if (curr !== maxLevel &&
|
||||
Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, Player)) &&
|
||||
Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr + 1, Player))) {
|
||||
Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, Player.hacknet_node_level_cost_mult)) &&
|
||||
Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr + 1, Player.hacknet_node_level_cost_mult))) {
|
||||
return Math.min(levelsToMax, curr);
|
||||
} else if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr, Player))) {
|
||||
} else if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr, Player.hacknet_node_level_cost_mult))) {
|
||||
max = curr - 1;
|
||||
} else if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, Player))) {
|
||||
} else if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, Player.hacknet_node_level_cost_mult))) {
|
||||
min = curr + 1;
|
||||
} else {
|
||||
return Math.min(levelsToMax, curr);
|
||||
@ -149,7 +154,7 @@ export function getMaxNumberRamUpgrades(nodeObj, maxLevel) {
|
||||
throw new Error(`getMaxNumberRamUpgrades() called without maxLevel arg`);
|
||||
}
|
||||
|
||||
if (Player.money.lt(nodeObj.calculateRamUpgradeCost(1, Player))) {
|
||||
if (Player.money.lt(nodeObj.calculateRamUpgradeCost(1, Player.hacknet_node_ram_cost_mult))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -159,13 +164,13 @@ export function getMaxNumberRamUpgrades(nodeObj, maxLevel) {
|
||||
} else {
|
||||
levelsToMax = Math.round(Math.log2(maxLevel / nodeObj.ram));
|
||||
}
|
||||
if (Player.money.gt(nodeObj.calculateRamUpgradeCost(levelsToMax, Player))) {
|
||||
if (Player.money.gt(nodeObj.calculateRamUpgradeCost(levelsToMax, Player.hacknet_node_ram_cost_mult))) {
|
||||
return levelsToMax;
|
||||
}
|
||||
|
||||
//We'll just loop until we find the max
|
||||
for (let i = levelsToMax-1; i >= 0; --i) {
|
||||
if (Player.money.gt(nodeObj.calculateRamUpgradeCost(i, Player))) {
|
||||
if (Player.money.gt(nodeObj.calculateRamUpgradeCost(i, Player.hacknet_node_ram_cost_mult))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@ -177,14 +182,14 @@ export function getMaxNumberCoreUpgrades(nodeObj, maxLevel) {
|
||||
throw new Error(`getMaxNumberCoreUpgrades() called without maxLevel arg`);
|
||||
}
|
||||
|
||||
if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(1, Player))) {
|
||||
if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(1, Player.hacknet_node_core_cost_mult))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let min = 1;
|
||||
let max = maxLevel - 1;
|
||||
const levelsToMax = maxLevel - nodeObj.cores;
|
||||
if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(levelsToMax, Player))) {
|
||||
if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(levelsToMax, Player.hacknet_node_core_cost_mult))) {
|
||||
return levelsToMax;
|
||||
}
|
||||
|
||||
@ -192,12 +197,12 @@ export function getMaxNumberCoreUpgrades(nodeObj, maxLevel) {
|
||||
while (min <= max) {
|
||||
let curr = (min + max) / 2 | 0;
|
||||
if (curr != maxLevel &&
|
||||
Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, Player)) &&
|
||||
Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr + 1, Player))) {
|
||||
Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, Player.hacknet_node_core_cost_mult)) &&
|
||||
Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr + 1, Player.hacknet_node_core_cost_mult))) {
|
||||
return Math.min(levelsToMax, curr);
|
||||
} else if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr, Player))) {
|
||||
} else if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr, Player.hacknet_node_core_cost_mult))) {
|
||||
max = curr - 1;
|
||||
} else if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, Player))) {
|
||||
} else if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, Player.hacknet_node_core_cost_mult))) {
|
||||
min = curr + 1;
|
||||
} else {
|
||||
return Math.min(levelsToMax, curr);
|
||||
@ -242,7 +247,136 @@ export function getMaxNumberCacheUpgrades(nodeObj, maxLevel) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Initial construction of Hacknet Nodes UI
|
||||
export function purchaseLevelUpgrade(node, levels=1) {
|
||||
const sanitizedLevels = Math.round(levels);
|
||||
const cost = node.calculateLevelUpgradeCost(sanitizedLevels, Player.hacknet_node_level_cost_mult);
|
||||
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isServer = (node instanceof HacknetServer);
|
||||
|
||||
// If we're at max level, return false
|
||||
if (node.level >= (isServer ? HacknetServerMaxLevel : HacknetNodeMaxLevel)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the number of specified upgrades would exceed the max level, calculate
|
||||
// the maximum number of upgrades and use that
|
||||
if (node.level + sanitizedLevels > (isServer ? HacknetServerMaxLevel : HacknetNodeMaxLevel)) {
|
||||
const diff = Math.max(0, (isServer ? HacknetServerMaxLevel : HacknetNodeMaxLevel) - node.level);
|
||||
return purchaseLevelUpgrade(node, diff);
|
||||
}
|
||||
|
||||
if (!Player.canAfford(cost)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Player.loseMoney(cost);
|
||||
node.upgradeLevel(sanitizedLevels, Player.hacknet_node_money_mult);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function purchaseRamUpgrade(node, levels=1) {
|
||||
const sanitizedLevels = Math.round(levels);
|
||||
const cost = node.calculateRamUpgradeCost(sanitizedLevels, Player.hacknet_node_ram_cost_mult);
|
||||
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isServer = (node instanceof HacknetServer);
|
||||
|
||||
// Fail if we're already at max
|
||||
if (node.ram >= (isServer ? HacknetServerMaxRam : HacknetNodeMaxRam)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the number of specified upgrades would exceed the max RAM, calculate the
|
||||
// max possible number of upgrades and use that
|
||||
if (isServer) {
|
||||
if (node.maxRam * Math.pow(2, sanitizedLevels) > HacknetServerMaxRam) {
|
||||
const diff = Math.max(0, Math.log2(Math.round(HacknetServerMaxRam / node.maxRam)));
|
||||
return purchaseRamUpgrade(node, diff);
|
||||
}
|
||||
} else {
|
||||
if (node.ram * Math.pow(2, sanitizedLevels) > HacknetNodeMaxRam) {
|
||||
const diff = Math.max(0, Math.log2(Math.round(HacknetNodeMaxRam / node.ram)));
|
||||
return purchaseRamUpgrade(node, diff);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!Player.canAfford(cost)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Player.loseMoney(cost);
|
||||
node.upgradeRam(sanitizedLevels, Player.hacknet_node_money_mult);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function purchaseCoreUpgrade(node, levels=1) {
|
||||
const sanitizedLevels = Math.round(levels);
|
||||
const cost = node.calculateCoreUpgradeCost(sanitizedLevels, Player.hacknet_node_core_cost_mult);
|
||||
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isServer = (node instanceof HacknetServer);
|
||||
|
||||
// Fail if we're already at max
|
||||
if (node.cores >= (isServer ? HacknetServerMaxCores : HacknetNodeMaxCores)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the specified number of upgrades would exceed the max Cores, calculate
|
||||
// the max possible number of upgrades and use that
|
||||
if (node.cores + sanitizedLevels > (isServer ? HacknetServerMaxCores : HacknetNodeMaxCores)) {
|
||||
const diff = Math.max(0, (isServer ? HacknetServerMaxCores : HacknetNodeMaxCores) - node.cores);
|
||||
return purchaseCoreUpgrade(node, diff);
|
||||
}
|
||||
|
||||
if (!Player.canAfford(cost)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Player.loseMoney(cost);
|
||||
node.upgradeCore(sanitizedLevels, Player.hacknet_node_money_mult);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function purchaseCacheUpgrade(node, levels=1) {
|
||||
const sanitizedLevels = Math.round(levels);
|
||||
const cost = node.calculateCacheUpgradeCost(sanitizedLevels);
|
||||
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(node instanceof HacknetServer)) {
|
||||
console.warn(`purchaseCacheUpgrade() called for a non-HacknetNode`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fail if we're already at max
|
||||
if (node.cache + sanitizedLevels > HacknetServerMaxCache) {
|
||||
const diff = Math.max(0, HacknetServerMaxCache - node.cache);
|
||||
return purchaseCacheUpgrade(node, diff);
|
||||
}
|
||||
|
||||
if (!Player.canAfford(cost)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Player.loseMoney(cost);
|
||||
node.upgradeCache(sanitizedLevels);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create/Refresh Hacknet Nodes UI
|
||||
export function renderHacknetNodesUI() {
|
||||
if (!routing.isOn(Page.HacknetNodes)) { return; }
|
||||
|
||||
@ -294,7 +428,10 @@ function processAllHacknetServerEarnings(numCycles) {
|
||||
|
||||
let hashes = 0;
|
||||
for (let i = 0; i < Player.hacknetNodes.length; ++i) {
|
||||
const hserver = AllServers[Player.hacknetNodes[i]]; // hacknetNodes array only contains the IP addresses
|
||||
// hacknetNodes array only contains the IP addresses of the servers.
|
||||
// Also, update the hash rate before processing
|
||||
const hserver = AllServers[Player.hacknetNodes[i]];
|
||||
hserver.updateHashRate(Player.hacknet_node_money_mult);
|
||||
hashes += hserver.process(numCycles);
|
||||
}
|
||||
|
||||
@ -303,6 +440,37 @@ function processAllHacknetServerEarnings(numCycles) {
|
||||
return hashes;
|
||||
}
|
||||
|
||||
export function updateHashManagerCapacity() {
|
||||
if (!(Player.hashManager instanceof HashManager)) {
|
||||
console.error(`Player does not have a HashManager`);
|
||||
return;
|
||||
}
|
||||
|
||||
const nodes = Player.hacknetNodes;
|
||||
if (nodes.length === 0) {
|
||||
Player.hashManager.updateCapacity(0);
|
||||
return;
|
||||
}
|
||||
|
||||
let total = 0;
|
||||
for (let i = 0; i < nodes.length; ++i) {
|
||||
if (typeof nodes[i] !== "string") {
|
||||
Player.hashManager.updateCapacity(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const h = AllServers[nodes[i]];
|
||||
if (!(h instanceof HacknetServer)) {
|
||||
Player.hashManager.updateCapacity(0);
|
||||
return;
|
||||
}
|
||||
|
||||
total += h.hashCapacity;
|
||||
}
|
||||
|
||||
Player.hashManager.updateCapacity(total);
|
||||
}
|
||||
|
||||
export function purchaseHashUpgrade(upgName, upgTarget) {
|
||||
if (!(Player.hashManager instanceof HashManager)) {
|
||||
console.error(`Player does not have a HashManager`);
|
||||
|
@ -9,7 +9,6 @@ import { IHacknetNode } from "./IHacknetNode";
|
||||
import { CONSTANTS } from "../Constants";
|
||||
|
||||
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
|
||||
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||
import { Generic_fromJSON,
|
||||
@ -62,12 +61,14 @@ export class HacknetNode implements IHacknetNode {
|
||||
// Total money earned by this Node
|
||||
totalMoneyGenerated: number = 0;
|
||||
|
||||
constructor(name: string="") {
|
||||
constructor(name: string="", prodMult: number=1) {
|
||||
this.name = name;
|
||||
|
||||
this.updateMoneyGainRate(prodMult);
|
||||
}
|
||||
|
||||
// Get the cost to upgrade this Node's number of cores
|
||||
calculateCoreUpgradeCost(levels: number=1, p: IPlayer): number {
|
||||
calculateCoreUpgradeCost(levels: number=1, costMult: number): number {
|
||||
const sanitizedLevels = Math.round(levels);
|
||||
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
||||
return 0;
|
||||
@ -86,13 +87,13 @@ export class HacknetNode implements IHacknetNode {
|
||||
++currentCores;
|
||||
}
|
||||
|
||||
totalCost *= p.hacknet_node_core_cost_mult;
|
||||
totalCost *= costMult;
|
||||
|
||||
return totalCost;
|
||||
}
|
||||
|
||||
// Get the cost to upgrade this Node's level
|
||||
calculateLevelUpgradeCost(levels: number=1, p: IPlayer): number {
|
||||
calculateLevelUpgradeCost(levels: number=1, costMult: number): number {
|
||||
const sanitizedLevels = Math.round(levels);
|
||||
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
||||
return 0;
|
||||
@ -110,11 +111,11 @@ export class HacknetNode implements IHacknetNode {
|
||||
++currLevel;
|
||||
}
|
||||
|
||||
return BaseCostForHacknetNode / 2 * totalMultiplier * p.hacknet_node_level_cost_mult;
|
||||
return BaseCostForHacknetNode / 2 * totalMultiplier * costMult;
|
||||
}
|
||||
|
||||
// Get the cost to upgrade this Node's RAM
|
||||
calculateRamUpgradeCost(levels: number=1, p: IPlayer): number {
|
||||
calculateRamUpgradeCost(levels: number=1, costMult: number): number {
|
||||
const sanitizedLevels = Math.round(levels);
|
||||
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
||||
return 0;
|
||||
@ -138,7 +139,7 @@ export class HacknetNode implements IHacknetNode {
|
||||
++numUpgrades;
|
||||
}
|
||||
|
||||
totalCost *= p.hacknet_node_ram_cost_mult;
|
||||
totalCost *= costMult;
|
||||
|
||||
return totalCost;
|
||||
}
|
||||
@ -161,112 +162,37 @@ export class HacknetNode implements IHacknetNode {
|
||||
|
||||
// Upgrade this Node's number of cores, if possible
|
||||
// Returns a boolean indicating whether new cores were successfully bought
|
||||
purchaseCoreUpgrade(levels: number=1, p: IPlayer): boolean {
|
||||
const sanitizedLevels = Math.round(levels);
|
||||
const cost = this.calculateCoreUpgradeCost(sanitizedLevels, p);
|
||||
if (isNaN(cost) || sanitizedLevels < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fail if we're already at max
|
||||
if (this.cores >= HacknetNodeMaxCores) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the specified number of upgrades would exceed the max Cores, calculate
|
||||
// the max possible number of upgrades and use that
|
||||
if (this.cores + sanitizedLevels > HacknetNodeMaxCores) {
|
||||
const diff = Math.max(0, HacknetNodeMaxCores - this.cores);
|
||||
return this.purchaseCoreUpgrade(diff, p);
|
||||
}
|
||||
|
||||
if (!p.canAfford(cost)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
p.loseMoney(cost);
|
||||
this.cores = Math.round(this.cores + sanitizedLevels); // Just in case of floating point imprecision
|
||||
this.updateMoneyGainRate(p);
|
||||
|
||||
return true;
|
||||
upgradeCore(levels: number=1, prodMult: number): void {
|
||||
this.cores = Math.min(HacknetNodeMaxCores, Math.round(this.cores + levels));
|
||||
this.updateMoneyGainRate(prodMult);
|
||||
}
|
||||
|
||||
// Upgrade this Node's level, if possible
|
||||
// Returns a boolean indicating whether the level was successfully updated
|
||||
purchaseLevelUpgrade(levels: number=1, p: IPlayer): boolean {
|
||||
const sanitizedLevels = Math.round(levels);
|
||||
const cost = this.calculateLevelUpgradeCost(sanitizedLevels, p);
|
||||
if (isNaN(cost) || sanitizedLevels < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we're at max level, return false
|
||||
if (this.level >= HacknetNodeMaxLevel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the number of specified upgrades would exceed the max level, calculate
|
||||
// the maximum number of upgrades and use that
|
||||
if (this.level + sanitizedLevels > HacknetNodeMaxLevel) {
|
||||
var diff = Math.max(0, HacknetNodeMaxLevel - this.level);
|
||||
return this.purchaseLevelUpgrade(diff, p);
|
||||
}
|
||||
|
||||
if (!p.canAfford(cost)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
p.loseMoney(cost);
|
||||
this.level = Math.round(this.level + sanitizedLevels); // Just in case of floating point imprecision
|
||||
this.updateMoneyGainRate(p);
|
||||
|
||||
return true;
|
||||
upgradeLevel(levels: number=1, prodMult: number): void {
|
||||
this.level = Math.min(HacknetNodeMaxLevel, Math.round(this.level + levels));
|
||||
this.updateMoneyGainRate(prodMult);
|
||||
}
|
||||
|
||||
// Upgrade this Node's RAM, if possible
|
||||
// Returns a boolean indicating whether the RAM was successfully upgraded
|
||||
purchaseRamUpgrade(levels: number=1, p: IPlayer): boolean {
|
||||
const sanitizedLevels = Math.round(levels);
|
||||
const cost = this.calculateRamUpgradeCost(sanitizedLevels, p);
|
||||
if (isNaN(cost) || sanitizedLevels < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fail if we're already at max
|
||||
if (this.ram >= HacknetNodeMaxRam) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the number of specified upgrades would exceed the max RAM, calculate the
|
||||
// max possible number of upgrades and use that
|
||||
if (this.ram * Math.pow(2, sanitizedLevels) > HacknetNodeMaxRam) {
|
||||
var diff = Math.max(0, Math.log2(Math.round(HacknetNodeMaxRam / this.ram)));
|
||||
return this.purchaseRamUpgrade(diff, p);
|
||||
}
|
||||
|
||||
if (!p.canAfford(cost)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
p.loseMoney(cost);
|
||||
for (let i = 0; i < sanitizedLevels; ++i) {
|
||||
upgradeRam(levels: number=1, prodMult: number): void {
|
||||
for (let i = 0; i < levels; ++i) {
|
||||
this.ram *= 2; // Ram is always doubled
|
||||
}
|
||||
this.ram = Math.round(this.ram); // Handle any floating point precision issues
|
||||
this.updateMoneyGainRate(p);
|
||||
|
||||
return true;
|
||||
this.updateMoneyGainRate(prodMult);
|
||||
}
|
||||
|
||||
// Re-calculate this Node's production and update the moneyGainRatePerSecond prop
|
||||
updateMoneyGainRate(p: IPlayer): void {
|
||||
updateMoneyGainRate(prodMult: number): void {
|
||||
//How much extra $/s is gained per level
|
||||
var gainPerLevel = HacknetNodeMoneyGainPerLevel;
|
||||
|
||||
this.moneyGainRatePerSecond = (this.level * gainPerLevel) *
|
||||
Math.pow(1.035, this.ram - 1) *
|
||||
((this.cores + 5) / 6) *
|
||||
p.hacknet_node_money_mult *
|
||||
prodMult *
|
||||
BitNodeMultipliers.HacknetNodeMoney;
|
||||
if (isNaN(this.moneyGainRatePerSecond)) {
|
||||
this.moneyGainRatePerSecond = 0;
|
||||
|
@ -6,15 +6,16 @@ import { CONSTANTS } from "../Constants";
|
||||
import { IHacknetNode } from "./IHacknetNode";
|
||||
|
||||
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import { BaseServer } from "../Server/BaseServer";
|
||||
import { RunningScript } from "../Script/RunningScript";
|
||||
|
||||
import { createRandomIp } from "../../utils/IPAddress";
|
||||
|
||||
import { Generic_fromJSON,
|
||||
import {
|
||||
Generic_fromJSON,
|
||||
Generic_toJSON,
|
||||
Reviver } from "../../utils/JSONReviver";
|
||||
Reviver
|
||||
} from "../../utils/JSONReviver";
|
||||
|
||||
// Constants for Hacknet Server stats/production
|
||||
export const HacknetServerHashesPerLevel: number = 0.001;
|
||||
@ -31,7 +32,7 @@ export const HacknetServerUpgradeRamMult: number = 1.4; // Multiplier for co
|
||||
export const HacknetServerUpgradeCoreMult: number = 1.55; // Multiplier for cost when buying another core
|
||||
export const HacknetServerUpgradeCacheMult: number = 1.85; // Multiplier for cost when upgrading cache
|
||||
|
||||
export const MaxNumberHacknetServers: number = 25; // Max number of Hacknet Servers you can own
|
||||
export const MaxNumberHacknetServers: number = 20; // Max number of Hacknet Servers you can own
|
||||
|
||||
// Constants for max upgrade levels for Hacknet Server
|
||||
export const HacknetServerMaxLevel: number = 300;
|
||||
@ -46,7 +47,6 @@ interface IConstructorParams {
|
||||
isConnectedTo?: boolean;
|
||||
maxRam?: number;
|
||||
organizationName?: string;
|
||||
player?: IPlayer;
|
||||
}
|
||||
|
||||
export class HacknetServer extends BaseServer implements IHacknetNode {
|
||||
@ -81,10 +81,6 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
|
||||
|
||||
this.maxRam = 1;
|
||||
this.updateHashCapacity();
|
||||
|
||||
if (params.player) {
|
||||
this.updateHashRate(params.player);
|
||||
}
|
||||
}
|
||||
|
||||
calculateCacheUpgradeCost(levels: number): number {
|
||||
@ -109,7 +105,7 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
|
||||
return totalCost;
|
||||
}
|
||||
|
||||
calculateCoreUpgradeCost(levels: number, p: IPlayer): number {
|
||||
calculateCoreUpgradeCost(levels: number, costMult: number): number {
|
||||
const sanitizedLevels = Math.round(levels);
|
||||
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
||||
return 0;
|
||||
@ -127,12 +123,12 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
|
||||
++currentCores;
|
||||
}
|
||||
totalCost *= BaseCostForHacknetServerCore;
|
||||
totalCost *= p.hacknet_node_core_cost_mult;
|
||||
totalCost *= costMult;
|
||||
|
||||
return totalCost;
|
||||
}
|
||||
|
||||
calculateLevelUpgradeCost(levels: number, p: IPlayer): number {
|
||||
calculateLevelUpgradeCost(levels: number, costMult: number): number {
|
||||
const sanitizedLevels = Math.round(levels);
|
||||
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
||||
return 0;
|
||||
@ -150,10 +146,10 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
|
||||
++currLevel;
|
||||
}
|
||||
|
||||
return 10 * BaseCostForHacknetServer * totalMultiplier * p.hacknet_node_level_cost_mult;
|
||||
return 10 * BaseCostForHacknetServer * totalMultiplier * costMult;
|
||||
}
|
||||
|
||||
calculateRamUpgradeCost(levels: number, p: IPlayer): number {
|
||||
calculateRamUpgradeCost(levels: number, costMult: number): number {
|
||||
const sanitizedLevels = Math.round(levels);
|
||||
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
||||
return 0;
|
||||
@ -175,7 +171,7 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
|
||||
currentRam *= 2;
|
||||
++numUpgrades;
|
||||
}
|
||||
totalCost *= p.hacknet_node_ram_cost_mult;
|
||||
totalCost *= costMult;
|
||||
|
||||
return totalCost;
|
||||
}
|
||||
@ -189,124 +185,30 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
|
||||
}
|
||||
|
||||
// Returns a boolean indicating whether the cache was successfully upgraded
|
||||
purchaseCacheUpgrade(levels: number, p: IPlayer): boolean {
|
||||
const sanitizedLevels = Math.round(levels);
|
||||
const cost = this.calculateCacheUpgradeCost(levels);
|
||||
if (isNaN(cost) || cost <= 0 || sanitizedLevels <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.cache >= HacknetServerMaxCache) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the specified number of upgrades would exceed the max, try to purchase
|
||||
// the maximum possible
|
||||
if (this.cache + levels > HacknetServerMaxCache) {
|
||||
const diff = Math.max(0, HacknetServerMaxCache - this.cache);
|
||||
return this.purchaseCacheUpgrade(diff, p);
|
||||
}
|
||||
|
||||
if (!p.canAfford(cost)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
p.loseMoney(cost);
|
||||
this.cache = Math.round(this.cache + sanitizedLevels);
|
||||
upgradeCache(levels: number): void {
|
||||
this.cache = Math.min(HacknetServerMaxCache, Math.round(this.cache + levels));
|
||||
this.updateHashCapacity();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns a boolean indicating whether the number of cores was successfully upgraded
|
||||
purchaseCoreUpgrade(levels: number, p: IPlayer): boolean {
|
||||
const sanitizedLevels = Math.round(levels);
|
||||
const cost = this.calculateCoreUpgradeCost(sanitizedLevels, p);
|
||||
if (isNaN(cost) || cost <= 0 || sanitizedLevels <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.cores >= HacknetServerMaxCores) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the specified number of upgrades would exceed the max, try to purchase
|
||||
// the maximum possible
|
||||
if (this.cores + sanitizedLevels > HacknetServerMaxCores) {
|
||||
const diff = Math.max(0, HacknetServerMaxCores - this.cores);
|
||||
return this.purchaseCoreUpgrade(diff, p);
|
||||
}
|
||||
|
||||
if (!p.canAfford(cost)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
p.loseMoney(cost);
|
||||
this.cores = Math.round(this.cores + sanitizedLevels);
|
||||
this.updateHashRate(p);
|
||||
|
||||
return true;
|
||||
upgradeCore(levels: number, prodMult: number): void {
|
||||
this.cores = Math.min(HacknetServerMaxCores, Math.round(this.cores + levels));
|
||||
this.updateHashRate(prodMult);
|
||||
}
|
||||
|
||||
// Returns a boolean indicating whether the level was successfully upgraded
|
||||
purchaseLevelUpgrade(levels: number, p: IPlayer): boolean {
|
||||
const sanitizedLevels = Math.round(levels);
|
||||
const cost = this.calculateLevelUpgradeCost(sanitizedLevels, p);
|
||||
if (isNaN(cost) || cost <= 0 || sanitizedLevels <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.level >= HacknetServerMaxLevel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the specified number of upgrades would exceed the max, try to purchase the
|
||||
// maximum possible
|
||||
if (this.level + sanitizedLevels > HacknetServerMaxLevel) {
|
||||
const diff = Math.max(0, HacknetServerMaxLevel - this.level);
|
||||
return this.purchaseLevelUpgrade(diff, p);
|
||||
}
|
||||
|
||||
if (!p.canAfford(cost)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
p.loseMoney(cost);
|
||||
this.level = Math.round(this.level + sanitizedLevels);
|
||||
this.updateHashRate(p);
|
||||
|
||||
return true;
|
||||
upgradeLevel(levels: number, prodMult: number): void {
|
||||
this.level = Math.min(HacknetServerMaxLevel, Math.round(this.level + levels));
|
||||
this.updateHashRate(prodMult);
|
||||
}
|
||||
|
||||
// Returns a boolean indicating whether the RAM was successfully upgraded
|
||||
purchaseRamUpgrade(levels: number, p: IPlayer): boolean {
|
||||
const sanitizedLevels = Math.round(levels);
|
||||
const cost = this.calculateRamUpgradeCost(sanitizedLevels, p);
|
||||
if(isNaN(cost) || cost <= 0 || sanitizedLevels <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.maxRam >= HacknetServerMaxRam) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the specified number of upgrades would exceed the max, try to purchase
|
||||
// just the maximum possible
|
||||
if (this.maxRam * Math.pow(2, sanitizedLevels) > HacknetServerMaxRam) {
|
||||
const diff = Math.max(0, Math.log2(Math.round(HacknetServerMaxRam / this.maxRam)));
|
||||
return this.purchaseRamUpgrade(diff, p);
|
||||
}
|
||||
|
||||
if (!p.canAfford(cost)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
p.loseMoney(cost);
|
||||
for (let i = 0; i < sanitizedLevels; ++i) {
|
||||
upgradeRam(levels: number, prodMult: number): boolean {
|
||||
for (let i = 0; i < levels; ++i) {
|
||||
this.maxRam *= 2;
|
||||
}
|
||||
this.maxRam = Math.round(this.maxRam);
|
||||
this.updateHashRate(p);
|
||||
this.maxRam = Math.min(HacknetServerMaxRam, Math.round(this.maxRam));
|
||||
this.updateHashRate(prodMult);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -314,10 +216,10 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
|
||||
/**
|
||||
* Whenever a script is run, we must update this server's hash rate
|
||||
*/
|
||||
runScript(script: RunningScript, p?: IPlayer): void {
|
||||
runScript(script: RunningScript, prodMult?: number): void {
|
||||
super.runScript(script);
|
||||
if (p) {
|
||||
this.updateHashRate(p);
|
||||
if (prodMult != null && typeof prodMult === "number") {
|
||||
this.updateHashRate(prodMult);
|
||||
}
|
||||
}
|
||||
|
||||
@ -325,7 +227,7 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
|
||||
this.hashCapacity = 32 * Math.pow(2, this.cache);
|
||||
}
|
||||
|
||||
updateHashRate(p: IPlayer): void {
|
||||
updateHashRate(prodMult: number): void {
|
||||
const baseGain = HacknetServerHashesPerLevel * this.level;
|
||||
const ramMultiplier = Math.pow(1.07, Math.log2(this.maxRam));
|
||||
const coreMultiplier = 1 + (this.cores - 1) / 5;
|
||||
@ -333,7 +235,7 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
|
||||
|
||||
const hashRate = baseGain * ramMultiplier * coreMultiplier * ramRatio;
|
||||
|
||||
this.hashRate = hashRate * p.hacknet_node_money_mult * BitNodeMultipliers.HacknetNodeMoney;
|
||||
this.hashRate = hashRate * prodMult * BitNodeMultipliers.HacknetNodeMoney;
|
||||
|
||||
if (isNaN(this.hashRate)) {
|
||||
this.hashRate = 0;
|
||||
|
@ -6,12 +6,9 @@
|
||||
* his hashes, and contains method for grabbing the data/multipliers from
|
||||
* those upgrades
|
||||
*/
|
||||
import { HacknetServer } from "./HacknetServer";
|
||||
import { HashUpgrades } from "./HashUpgrades";
|
||||
|
||||
import { IMap } from "../types";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import { AllServers } from "../Server/AllServers";
|
||||
import { Generic_fromJSON,
|
||||
Generic_toJSON,
|
||||
Reviver } from "../../utils/JSONReviver";
|
||||
@ -84,14 +81,14 @@ export class HashManager {
|
||||
return upg.getCost(currLevel);
|
||||
}
|
||||
|
||||
prestige(p: IPlayer): void {
|
||||
prestige(): void {
|
||||
for (const name in HashUpgrades) {
|
||||
this.upgrades[name] = 0;
|
||||
}
|
||||
this.hashes = 0;
|
||||
if (p != null) {
|
||||
this.updateCapacity(p);
|
||||
}
|
||||
|
||||
// When prestiging, player's hacknet nodes are always reset. So capacity = 0
|
||||
this.updateCapacity(0);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -99,14 +96,16 @@ export class HashManager {
|
||||
*/
|
||||
refundUpgrade(upgName: string): void {
|
||||
const upg = HashUpgrades[upgName];
|
||||
|
||||
// Reduce the level first, so we get the right cost
|
||||
--this.upgrades[upgName];
|
||||
|
||||
const currLevel = this.upgrades[upgName];
|
||||
if (upg == null || currLevel == null || currLevel === 0) {
|
||||
if (upg == null || currLevel == null || currLevel < 0) {
|
||||
console.error(`Invalid Upgrade Name given to HashManager.upgrade(): ${upgName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Reduce the level first, so we get the right cost
|
||||
--this.upgrades[upgName];
|
||||
const cost = upg.getCost(currLevel);
|
||||
this.hashes += cost;
|
||||
}
|
||||
@ -116,33 +115,11 @@ export class HashManager {
|
||||
this.hashes = Math.min(this.hashes, this.capacity);
|
||||
}
|
||||
|
||||
updateCapacity(p: IPlayer): void {
|
||||
if (p.hacknetNodes.length <= 0) {
|
||||
updateCapacity(newCap: number): void {
|
||||
if (newCap < 0) {
|
||||
this.capacity = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the Player's `hacknetNodes` property actually holds Hacknet Servers
|
||||
const ip: string = <string>p.hacknetNodes[0];
|
||||
if (typeof ip !== "string") {
|
||||
this.capacity = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const hserver = <HacknetServer>AllServers[ip];
|
||||
if (!(hserver instanceof HacknetServer)) {
|
||||
this.capacity = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
let total: number = 0;
|
||||
for (let i = 0; i < p.hacknetNodes.length; ++i) {
|
||||
const h = <HacknetServer>AllServers[<string>p.hacknetNodes[i]];
|
||||
total += h.hashCapacity;
|
||||
}
|
||||
|
||||
this.capacity = total;
|
||||
this.capacity = Math.max(newCap, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,16 +1,14 @@
|
||||
// Interface for a Hacknet Node. Implemented by both a basic Hacknet Node,
|
||||
// and the upgraded Hacknet Server in BitNode-9
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
|
||||
export interface IHacknetNode {
|
||||
cores: number;
|
||||
level: number;
|
||||
onlineTimeSeconds: number;
|
||||
|
||||
calculateCoreUpgradeCost: (levels: number, p: IPlayer) => number;
|
||||
calculateLevelUpgradeCost: (levels: number, p: IPlayer) => number;
|
||||
calculateRamUpgradeCost: (levels: number, p: IPlayer) => number;
|
||||
purchaseCoreUpgrade: (levels: number, p: IPlayer) => boolean;
|
||||
purchaseLevelUpgrade: (levels: number, p: IPlayer) => boolean;
|
||||
purchaseRamUpgrade: (levels: number, p: IPlayer) => boolean;
|
||||
calculateCoreUpgradeCost: (levels: number, costMult: number) => number;
|
||||
calculateLevelUpgradeCost: (levels: number, costMult: number) => number;
|
||||
calculateRamUpgradeCost: (levels: number, costMult: number) => number;
|
||||
upgradeCore: (levels: number, prodMult: number) => void;
|
||||
upgradeLevel: (levels: number, prodMult: number) => void;
|
||||
upgradeRam: (levels: number, prodMult: number) => void;
|
||||
}
|
||||
|
@ -4,12 +4,19 @@
|
||||
*/
|
||||
import React from "react";
|
||||
|
||||
import { HacknetNodeMaxLevel,
|
||||
import {
|
||||
HacknetNodeMaxLevel,
|
||||
HacknetNodeMaxRam,
|
||||
HacknetNodeMaxCores } from "../HacknetNode";
|
||||
import { getMaxNumberLevelUpgrades,
|
||||
HacknetNodeMaxCores
|
||||
} from "../HacknetNode";
|
||||
import {
|
||||
getMaxNumberLevelUpgrades,
|
||||
getMaxNumberRamUpgrades,
|
||||
getMaxNumberCoreUpgrades } from "../HacknetHelpers";
|
||||
getMaxNumberCoreUpgrades,
|
||||
purchaseLevelUpgrade,
|
||||
purchaseRamUpgrade,
|
||||
purchaseCoreUpgrade,
|
||||
} from "../HacknetHelpers";
|
||||
|
||||
import { Player } from "../../Player";
|
||||
|
||||
@ -35,7 +42,7 @@ export class HacknetNode extends React.Component {
|
||||
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||
}
|
||||
|
||||
const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, Player);
|
||||
const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, Player.hacknet_node_level_cost_mult);
|
||||
upgradeLevelText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeLevelCost)}`;
|
||||
if (Player.money.lt(upgradeLevelCost)) {
|
||||
upgradeLevelClass = "std-button-disabled";
|
||||
@ -48,7 +55,7 @@ export class HacknetNode extends React.Component {
|
||||
if (purchaseMult === "MAX") {
|
||||
numUpgrades = getMaxNumberLevelUpgrades(node, HacknetNodeMaxLevel);
|
||||
}
|
||||
node.purchaseLevelUpgrade(numUpgrades, Player);
|
||||
purchaseLevelUpgrade(node, numUpgrades);
|
||||
recalculate();
|
||||
return false;
|
||||
}
|
||||
@ -66,7 +73,7 @@ export class HacknetNode extends React.Component {
|
||||
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||
}
|
||||
|
||||
const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, Player);
|
||||
const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, Player.hacknet_node_ram_cost_mult);
|
||||
upgradeRamText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeRamCost)}`;
|
||||
if (Player.money.lt(upgradeRamCost)) {
|
||||
upgradeRamClass = "std-button-disabled";
|
||||
@ -79,7 +86,7 @@ export class HacknetNode extends React.Component {
|
||||
if (purchaseMult === "MAX") {
|
||||
numUpgrades = getMaxNumberRamUpgrades(node, HacknetNodeMaxRam);
|
||||
}
|
||||
node.purchaseRamUpgrade(numUpgrades, Player);
|
||||
purchaseRamUpgrade(node, numUpgrades);
|
||||
recalculate();
|
||||
return false;
|
||||
}
|
||||
@ -97,7 +104,7 @@ export class HacknetNode extends React.Component {
|
||||
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||
}
|
||||
|
||||
const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, Player);
|
||||
const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, Player.hacknet_node_core_cost_mult);
|
||||
upgradeCoresText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeCoreCost)}`;
|
||||
if (Player.money.lt(upgradeCoreCost)) {
|
||||
upgradeCoresClass = "std-button-disabled";
|
||||
@ -110,7 +117,7 @@ export class HacknetNode extends React.Component {
|
||||
if (purchaseMult === "MAX") {
|
||||
numUpgrades = getMaxNumberCoreUpgrades(node, HacknetNodeMaxCores);
|
||||
}
|
||||
node.purchaseCoreUpgrade(numUpgrades, Player);
|
||||
purchaseCoreUpgrade(node, numUpgrades);
|
||||
recalculate();
|
||||
return false;
|
||||
}
|
||||
|
@ -4,14 +4,23 @@
|
||||
*/
|
||||
import React from "react";
|
||||
|
||||
import { HacknetServerMaxLevel,
|
||||
import {
|
||||
HacknetServerMaxLevel,
|
||||
HacknetServerMaxRam,
|
||||
HacknetServerMaxCores,
|
||||
HacknetServerMaxCache } from "../HacknetServer";
|
||||
import { getMaxNumberLevelUpgrades,
|
||||
HacknetServerMaxCache
|
||||
} from "../HacknetServer";
|
||||
import {
|
||||
getMaxNumberLevelUpgrades,
|
||||
getMaxNumberRamUpgrades,
|
||||
getMaxNumberCoreUpgrades,
|
||||
getMaxNumberCacheUpgrades } from "../HacknetHelpers";
|
||||
getMaxNumberCacheUpgrades,
|
||||
purchaseLevelUpgrade,
|
||||
purchaseRamUpgrade,
|
||||
purchaseCoreUpgrade,
|
||||
purchaseCacheUpgrade,
|
||||
updateHashManagerCapacity,
|
||||
} from "../HacknetHelpers";
|
||||
|
||||
import { Player } from "../../Player";
|
||||
|
||||
@ -37,7 +46,7 @@ export class HacknetServer extends React.Component {
|
||||
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||
}
|
||||
|
||||
const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, Player);
|
||||
const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, Player.hacknet_node_level_cost_mult);
|
||||
upgradeLevelText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeLevelCost)}`;
|
||||
if (Player.money.lt(upgradeLevelCost)) {
|
||||
upgradeLevelClass = "std-button-disabled";
|
||||
@ -50,7 +59,7 @@ export class HacknetServer extends React.Component {
|
||||
if (purchaseMult === "MAX") {
|
||||
numUpgrades = getMaxNumberLevelUpgrades(node, HacknetServerMaxLevel);
|
||||
}
|
||||
node.purchaseLevelUpgrade(numUpgrades, Player);
|
||||
purchaseLevelUpgrade(node, numUpgrades);
|
||||
recalculate();
|
||||
return false;
|
||||
}
|
||||
@ -69,7 +78,7 @@ export class HacknetServer extends React.Component {
|
||||
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||
}
|
||||
|
||||
const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, Player);
|
||||
const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, Player.hacknet_node_ram_cost_mult);
|
||||
upgradeRamText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeRamCost)}`;
|
||||
if (Player.money.lt(upgradeRamCost)) {
|
||||
upgradeRamClass = "std-button-disabled";
|
||||
@ -82,7 +91,7 @@ export class HacknetServer extends React.Component {
|
||||
if (purchaseMult === "MAX") {
|
||||
numUpgrades = getMaxNumberRamUpgrades(node, HacknetServerMaxRam);
|
||||
}
|
||||
node.purchaseRamUpgrade(numUpgrades, Player);
|
||||
purchaseRamUpgrade(node, numUpgrades);
|
||||
recalculate();
|
||||
return false;
|
||||
}
|
||||
@ -101,7 +110,7 @@ export class HacknetServer extends React.Component {
|
||||
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||
}
|
||||
|
||||
const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, Player);
|
||||
const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, Player.hacknet_node_core_cost_mult);
|
||||
upgradeCoresText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeCoreCost)}`;
|
||||
if (Player.money.lt(upgradeCoreCost)) {
|
||||
upgradeCoresClass = "std-button-disabled";
|
||||
@ -114,7 +123,7 @@ export class HacknetServer extends React.Component {
|
||||
if (purchaseMult === "MAX") {
|
||||
numUpgrades = getMaxNumberCoreUpgrades(node, HacknetServerMaxCores);
|
||||
}
|
||||
node.purchaseCoreUpgrade(numUpgrades, Player);
|
||||
purchaseCoreUpgrade(node, numUpgrades);
|
||||
recalculate();
|
||||
return false;
|
||||
}
|
||||
@ -146,9 +155,9 @@ export class HacknetServer extends React.Component {
|
||||
if (purchaseMult === "MAX") {
|
||||
numUpgrades = getMaxNumberCacheUpgrades(node, HacknetServerMaxCache);
|
||||
}
|
||||
node.purchaseCacheUpgrade(numUpgrades, Player);
|
||||
purchaseCacheUpgrade(node, numUpgrades);
|
||||
recalculate();
|
||||
Player.hashManager.updateCapacity(Player);
|
||||
updateHashManagerCapacity();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -39,6 +39,8 @@ export class HacknetRoot extends React.Component {
|
||||
}
|
||||
|
||||
this.createHashUpgradesPopup = this.createHashUpgradesPopup.bind(this);
|
||||
this.handlePurchaseButtonClick = this.handlePurchaseButtonClick.bind(this);
|
||||
this.recalculateTotalProduction = this.recalculateTotalProduction.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -50,6 +52,12 @@ export class HacknetRoot extends React.Component {
|
||||
createPopup(id, HashUpgradePopup, { popupId: id, rerender: this.createHashUpgradesPopup });
|
||||
}
|
||||
|
||||
handlePurchaseButtonClick() {
|
||||
if (purchaseHacknet() >= 0) {
|
||||
this.recalculateTotalProduction();
|
||||
}
|
||||
}
|
||||
|
||||
recalculateTotalProduction() {
|
||||
let total = 0;
|
||||
for (let i = 0; i < Player.hacknetNodes.length; ++i) {
|
||||
@ -85,13 +93,6 @@ export class HacknetRoot extends React.Component {
|
||||
purchaseCost = getCostOfNextHacknetNode();
|
||||
}
|
||||
|
||||
// onClick event handler for purchase button
|
||||
const purchaseOnClick = () => {
|
||||
if (purchaseHacknet() >= 0) {
|
||||
this.recalculateTotalProduction();
|
||||
}
|
||||
}
|
||||
|
||||
// onClick event handlers for purchase multiplier buttons
|
||||
const purchaseMultiplierOnClicks = [
|
||||
this.setPurchaseMultiplier.bind(this, PurchaseMultipliers.x1),
|
||||
@ -112,7 +113,7 @@ export class HacknetRoot extends React.Component {
|
||||
key={hserver.hostname}
|
||||
node={hserver}
|
||||
purchaseMultiplier={this.state.purchaseMultiplier}
|
||||
recalculate={this.recalculateTotalProduction.bind(this)}
|
||||
recalculate={this.recalculateTotalProduction}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
@ -121,7 +122,7 @@ export class HacknetRoot extends React.Component {
|
||||
key={node.name}
|
||||
node={node}
|
||||
purchaseMultiplier={this.state.purchaseMultiplier}
|
||||
recalculate={this.recalculateTotalProduction.bind(this)}
|
||||
recalculate={this.recalculateTotalProduction}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -132,7 +133,7 @@ export class HacknetRoot extends React.Component {
|
||||
<h1>Hacknet Nodes</h1>
|
||||
<GeneralInfo />
|
||||
|
||||
<PurchaseButton cost={purchaseCost} multiplier={this.state.purchaseMultiplier} onClick={purchaseOnClick} />
|
||||
<PurchaseButton cost={purchaseCost} multiplier={this.state.purchaseMultiplier} onClick={this.handlePurchaseButtonClick} />
|
||||
|
||||
<br />
|
||||
<div id={"hacknet-nodes-money-multipliers-div"}>
|
||||
|
@ -7,27 +7,32 @@ import { CONSTANTS } from "../Constants";
|
||||
import { CityName } from "./data/CityNames";
|
||||
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import { AllServers,
|
||||
AddToAllServers } from "../Server/AllServers";
|
||||
import { Server } from "../Server/Server";
|
||||
import { getPurchaseServerCost,
|
||||
import {
|
||||
AddToAllServers,
|
||||
createUniqueRandomIp,
|
||||
} from "../Server/AllServers";
|
||||
import { safetlyCreateUniqueServer } from "../Server/ServerHelpers";
|
||||
import {
|
||||
getPurchaseServerCost,
|
||||
purchaseRamForHomeComputer,
|
||||
purchaseServer } from "../Server/ServerPurchases";
|
||||
purchaseServer
|
||||
} from "../Server/ServerPurchases";
|
||||
import { SpecialServerIps } from "../Server/SpecialServerIps";
|
||||
import { Settings } from "../Settings/Settings";
|
||||
|
||||
import { numeralWrapper } from "../ui/numeralFormat";
|
||||
|
||||
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||
import { createRandomIp } from "../../utils/IPAddress";
|
||||
import { yesNoBoxGetYesButton,
|
||||
import {
|
||||
yesNoBoxGetYesButton,
|
||||
yesNoBoxGetNoButton,
|
||||
yesNoBoxClose,
|
||||
yesNoBoxCreate,
|
||||
yesNoTxtInpBoxGetYesButton,
|
||||
yesNoTxtInpBoxGetNoButton,
|
||||
yesNoTxtInpBoxClose,
|
||||
yesNoTxtInpBoxCreate } from "../../utils/YesNoBox";
|
||||
yesNoTxtInpBoxCreate
|
||||
} from "../../utils/YesNoBox";
|
||||
|
||||
import { createElement } from "../../utils/uiHelpers/createElement";
|
||||
import { createPopup } from "../../utils/uiHelpers/createPopup";
|
||||
@ -271,8 +276,8 @@ export function purchaseTorRouter(p: IPlayer) {
|
||||
}
|
||||
p.loseMoney(CONSTANTS.TorRouterCost);
|
||||
|
||||
const darkweb = new Server({
|
||||
ip: createRandomIp(), hostname:"darkweb", organizationName:"",
|
||||
const darkweb = safetlyCreateUniqueServer({
|
||||
ip: createUniqueRandomIp(), hostname:"darkweb", organizationName:"",
|
||||
isConnectedTo:false, adminRights:false, purchasedByPlayer:false, maxRam:1
|
||||
});
|
||||
AddToAllServers(darkweb);
|
||||
|
@ -8,8 +8,8 @@ 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 { dialogBoxCreate, dialogBoxOpened} from "../../utils/DialogBox";
|
||||
import { Reviver } from "../../utils/JSONReviver";
|
||||
|
||||
//Sends message to player, including a pop up
|
||||
function sendMessage(msg, forced=false) {
|
||||
@ -31,7 +31,7 @@ function showMessage(msg) {
|
||||
function addMessageToServer(msg, serverHostname) {
|
||||
var server = GetServerByHostname(serverHostname);
|
||||
if (server == null) {
|
||||
console.log("WARNING: Did not locate " + serverHostname);
|
||||
console.warn(`Could not find server ${serverHostname}`);
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < server.messages.length; ++i) {
|
||||
|
72
src/Netscript/Environment.ts
Normal file
72
src/Netscript/Environment.ts
Normal file
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* The environment in which a script runs. The environment holds
|
||||
* Netscript functions and arguments for that script.
|
||||
*/
|
||||
import { IMap } from "../types";
|
||||
|
||||
export class Environment {
|
||||
/**
|
||||
* Parent environment. Used to implement "scope"
|
||||
*/
|
||||
parent: Environment | null = null;
|
||||
|
||||
/**
|
||||
* Whether or not the script that uses this Environment should stop running
|
||||
*/
|
||||
stopFlag: boolean = false;
|
||||
|
||||
/**
|
||||
* Environment variables (currently only Netscript functions)
|
||||
*/
|
||||
vars: IMap<any> = {};
|
||||
|
||||
constructor(parent: Environment | null) {
|
||||
if (parent instanceof Environment) {
|
||||
this.vars = Object.assign({}, parent.vars);
|
||||
}
|
||||
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the scope where the variable with the given name is defined
|
||||
*/
|
||||
lookup(name: string): Environment | null {
|
||||
let scope: Environment | null = this;
|
||||
while (scope) {
|
||||
if (Object.prototype.hasOwnProperty.call(scope.vars, name)) {
|
||||
return scope;
|
||||
}
|
||||
scope = scope.parent;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
//Get the current value of a variable
|
||||
get(name: string): any {
|
||||
if (name in this.vars) {
|
||||
return this.vars[name];
|
||||
}
|
||||
|
||||
throw new Error(`Undefined variable ${name}`);
|
||||
}
|
||||
|
||||
//Sets the value of a variable in any scope
|
||||
set(name: string, value: any) {
|
||||
var scope = this.lookup(name);
|
||||
|
||||
//If scope has a value, then this variable is already set in a higher scope, so
|
||||
//set is there. Otherwise, create a new variable in the local scope
|
||||
if (scope !== null) {
|
||||
return scope.vars[name] = value;
|
||||
} else {
|
||||
return this.vars[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
//Creates (or overwrites) a variable in the current scope
|
||||
def(name: string, value: any) {
|
||||
return this.vars[name] = value;
|
||||
}
|
||||
}
|
332
src/Netscript/RamCostGenerator.ts
Normal file
332
src/Netscript/RamCostGenerator.ts
Normal file
@ -0,0 +1,332 @@
|
||||
import { IMap } from "../types";
|
||||
|
||||
// TODO remember to update RamCalculations.js and WorkerScript.js
|
||||
|
||||
// RAM costs for Netscript functions
|
||||
export const RamCostConstants: IMap<number> = {
|
||||
ScriptBaseRamCost: 1.6,
|
||||
ScriptDomRamCost: 25,
|
||||
ScriptHackRamCost: 0.1,
|
||||
ScriptHackAnalyzeRamCost: 1,
|
||||
ScriptGrowRamCost: 0.15,
|
||||
ScriptGrowthAnalyzeRamCost: 1,
|
||||
ScriptWeakenRamCost: 0.15,
|
||||
ScriptScanRamCost: 0.2,
|
||||
ScriptPortProgramRamCost: 0.05,
|
||||
ScriptRunRamCost: 1.0,
|
||||
ScriptExecRamCost: 1.3,
|
||||
ScriptSpawnRamCost: 2.0,
|
||||
ScriptScpRamCost: 0.6,
|
||||
ScriptKillRamCost: 0.5,
|
||||
ScriptHasRootAccessRamCost: 0.05,
|
||||
ScriptGetHostnameRamCost: 0.05,
|
||||
ScriptGetHackingLevelRamCost: 0.05,
|
||||
ScriptGetMultipliersRamCost: 4.0,
|
||||
ScriptGetServerRamCost: 0.1,
|
||||
ScriptFileExistsRamCost: 0.1,
|
||||
ScriptIsRunningRamCost: 0.1,
|
||||
ScriptHacknetNodesRamCost: 4.0,
|
||||
ScriptHNUpgLevelRamCost: 0.4,
|
||||
ScriptHNUpgRamRamCost: 0.6,
|
||||
ScriptHNUpgCoreRamCost: 0.8,
|
||||
ScriptGetStockRamCost: 2.0,
|
||||
ScriptBuySellStockRamCost: 2.5,
|
||||
ScriptGetPurchaseServerRamCost: 0.25,
|
||||
ScriptPurchaseServerRamCost: 2.25,
|
||||
ScriptGetPurchasedServerLimit: 0.05,
|
||||
ScriptGetPurchasedServerMaxRam: 0.05,
|
||||
ScriptRoundRamCost: 0.05,
|
||||
ScriptReadWriteRamCost: 1.0,
|
||||
ScriptArbScriptRamCost: 1.0,
|
||||
ScriptGetScriptRamCost: 0.1,
|
||||
ScriptGetHackTimeRamCost: 0.05,
|
||||
ScriptGetFavorToDonate: 0.10,
|
||||
ScriptCodingContractBaseRamCost: 10,
|
||||
ScriptSleeveBaseRamCost: 4,
|
||||
|
||||
ScriptSingularityFn1RamCost: 2,
|
||||
ScriptSingularityFn2RamCost: 3,
|
||||
ScriptSingularityFn3RamCost: 5,
|
||||
|
||||
ScriptGangApiBaseRamCost: 4,
|
||||
|
||||
ScriptBladeburnerApiBaseRamCost: 4,
|
||||
}
|
||||
|
||||
export const RamCosts: IMap<any> = {
|
||||
hacknet: {
|
||||
numNodes: () => 0,
|
||||
purchaseNode: () => 0,
|
||||
getPurchaseNodeCost: () => 0,
|
||||
getNodeStats: () => 0,
|
||||
upgradeLevel: () => 0,
|
||||
upgradeRam: () => 0,
|
||||
upgradeCore: () => 0,
|
||||
upgradeCache: () => 0,
|
||||
getLevelUpgradeCost: () => 0,
|
||||
getRamUpgradeCost: () => 0,
|
||||
getCoreUpgradeCost: () => 0,
|
||||
getCacheUpgradeCost: () => 0,
|
||||
numHashes: () => 0,
|
||||
hashCost: () => 0,
|
||||
spendHashes: () => 0,
|
||||
},
|
||||
sprintf: () => 0,
|
||||
vsprintf: () => 0,
|
||||
scan: () => RamCostConstants.ScriptScanRamCost,
|
||||
hack: () => RamCostConstants.ScriptHackRamCost,
|
||||
hackAnalyzeThreads: () => RamCostConstants.ScriptHackAnalyzeRamCost,
|
||||
hackAnalyzePercent: () => RamCostConstants.ScriptHackAnalyzeRamCost,
|
||||
hackChance: () => RamCostConstants.ScriptHackAnalyzeRamCost,
|
||||
sleep: () => 0,
|
||||
grow: () => RamCostConstants.ScriptGrowRamCost,
|
||||
growthAnalyze: () => RamCostConstants.ScriptGrowthAnalyzeRamCost,
|
||||
weaken: () => RamCostConstants.ScriptWeakenRamCost,
|
||||
print: () => 0,
|
||||
tprint: () => 0,
|
||||
clearLog: () => 0,
|
||||
disableLog: () => 0,
|
||||
enableLog: () => 0,
|
||||
isLogEnabled: () => 0,
|
||||
getScriptLogs: () => 0,
|
||||
nuke: () => RamCostConstants.ScriptPortProgramRamCost,
|
||||
brutessh: () => RamCostConstants.ScriptPortProgramRamCost,
|
||||
ftpcrack: () => RamCostConstants.ScriptPortProgramRamCost,
|
||||
relaysmtp: () => RamCostConstants.ScriptPortProgramRamCost,
|
||||
httpworm: () => RamCostConstants.ScriptPortProgramRamCost,
|
||||
sqlinject: () => RamCostConstants.ScriptPortProgramRamCost,
|
||||
run: () => RamCostConstants.ScriptRunRamCost,
|
||||
exec: () => RamCostConstants.ScriptExecRamCost,
|
||||
spawn: () => RamCostConstants.ScriptSpawnRamCost,
|
||||
kill: () => RamCostConstants.ScriptKillRamCost,
|
||||
killall: () => RamCostConstants.ScriptKillRamCost,
|
||||
exit: () => 0,
|
||||
scp: () => RamCostConstants.ScriptScpRamCost,
|
||||
ls: () => RamCostConstants.ScriptScanRamCost,
|
||||
ps: () => RamCostConstants.ScriptScanRamCost,
|
||||
hasRootAccess: () => RamCostConstants.ScriptHasRootAccessRamCost,
|
||||
getIp: () => RamCostConstants.ScriptGetHostnameRamCost,
|
||||
getHostname: () => RamCostConstants.ScriptGetHostnameRamCost,
|
||||
getHackingLevel: () => RamCostConstants.ScriptGetHackingLevelRamCost,
|
||||
getHackingMultipliers: () => RamCostConstants.ScriptGetMultipliersRamCost,
|
||||
getHacknetMultipliers: () => RamCostConstants.ScriptGetMultipliersRamCost,
|
||||
getBitNodeMultipliers: () => RamCostConstants.ScriptGetMultipliersRamCost,
|
||||
getServerMoneyAvailable: () => RamCostConstants.ScriptGetServerRamCost,
|
||||
getServerSecurityLevel: () => RamCostConstants.ScriptGetServerRamCost,
|
||||
getServerBaseSecurityLevel: () => RamCostConstants.ScriptGetServerRamCost,
|
||||
getServerMinSecurityLevel: () => RamCostConstants.ScriptGetServerRamCost,
|
||||
getServerRequiredHackingLevel: () => RamCostConstants.ScriptGetServerRamCost,
|
||||
getServerMaxMoney: () => RamCostConstants.ScriptGetServerRamCost,
|
||||
getServerGrowth: () => RamCostConstants.ScriptGetServerRamCost,
|
||||
getServerNumPortsRequired: () => RamCostConstants.ScriptGetServerRamCost,
|
||||
getServerRam: () => RamCostConstants.ScriptGetServerRamCost,
|
||||
serverExists: () => RamCostConstants.ScriptGetServerRamCost,
|
||||
fileExists: () => RamCostConstants.ScriptFileExistsRamCost,
|
||||
isRunning: () => RamCostConstants.ScriptIsRunningRamCost,
|
||||
getStockSymbols: () => RamCostConstants.ScriptGetStockRamCost,
|
||||
getStockPrice: () => RamCostConstants.ScriptGetStockRamCost,
|
||||
getStockAskPrice: () => RamCostConstants.ScriptGetStockRamCost,
|
||||
getStockBidPrice: () => RamCostConstants.ScriptGetStockRamCost,
|
||||
getStockPosition: () => RamCostConstants.ScriptGetStockRamCost,
|
||||
getStockMaxShares: () => RamCostConstants.ScriptGetStockRamCost,
|
||||
getStockPurchaseCost: () => RamCostConstants.ScriptGetStockRamCost,
|
||||
getStockSaleGain: () => RamCostConstants.ScriptGetStockRamCost,
|
||||
buyStock: () => RamCostConstants.ScriptBuySellStockRamCost,
|
||||
sellStock: () => RamCostConstants.ScriptBuySellStockRamCost,
|
||||
shortStock: () => RamCostConstants.ScriptBuySellStockRamCost,
|
||||
sellShort: () => RamCostConstants.ScriptBuySellStockRamCost,
|
||||
placeOrder: () => RamCostConstants.ScriptBuySellStockRamCost,
|
||||
cancelOrder: () => RamCostConstants.ScriptBuySellStockRamCost,
|
||||
getOrders: () => RamCostConstants.ScriptBuySellStockRamCost,
|
||||
getStockVolatility: () => RamCostConstants.ScriptBuySellStockRamCost,
|
||||
getStockForecast: () => RamCostConstants.ScriptBuySellStockRamCost,
|
||||
purchase4SMarketData: () => RamCostConstants.ScriptBuySellStockRamCost,
|
||||
purchase4SMarketDataTixApi: () => RamCostConstants.ScriptBuySellStockRamCost,
|
||||
getPurchasedServerLimit: () => RamCostConstants.ScriptGetPurchasedServerLimit,
|
||||
getPurchasedServerMaxRam: () => RamCostConstants.ScriptGetPurchasedServerMaxRam,
|
||||
getPurchasedServerCost: () => RamCostConstants.ScriptGetPurchaseServerRamCost,
|
||||
purchaseServer: () => RamCostConstants.ScriptPurchaseServerRamCost,
|
||||
deleteServer: () => RamCostConstants.ScriptPurchaseServerRamCost,
|
||||
getPurchasedServers: () => RamCostConstants.ScriptPurchaseServerRamCost,
|
||||
write: () => RamCostConstants.ScriptReadWriteRamCost,
|
||||
tryWrite: () => RamCostConstants.ScriptReadWriteRamCost,
|
||||
read: () => RamCostConstants.ScriptReadWriteRamCost,
|
||||
peek: () => RamCostConstants.ScriptReadWriteRamCost,
|
||||
clear: () => RamCostConstants.ScriptReadWriteRamCost,
|
||||
getPortHandle: () => RamCostConstants.ScriptReadWriteRamCost * 10,
|
||||
rm: () => RamCostConstants.ScriptReadWriteRamCost,
|
||||
scriptRunning: () => RamCostConstants.ScriptArbScriptRamCost,
|
||||
scriptKill: () => RamCostConstants.ScriptArbScriptRamCost,
|
||||
getScriptName: () => 0,
|
||||
getScriptRam: () => RamCostConstants.ScriptGetScriptRamCost,
|
||||
getHackTime: () => RamCostConstants.ScriptGetHackTimeRamCost,
|
||||
getGrowTime: () => RamCostConstants.ScriptGetHackTimeRamCost,
|
||||
getWeakenTime: () => RamCostConstants.ScriptGetHackTimeRamCost,
|
||||
getScriptIncome: () => RamCostConstants.ScriptGetScriptRamCost,
|
||||
getScriptExpGain: () => RamCostConstants.ScriptGetScriptRamCost,
|
||||
nFormat: () => 0,
|
||||
getTimeSinceLastAug: () => RamCostConstants.ScriptGetHackTimeRamCost,
|
||||
prompt: () => 0,
|
||||
wget: () => 0,
|
||||
getFavorToDonate: () => RamCostConstants.ScriptGetFavorToDonate,
|
||||
|
||||
// Singularity Functions
|
||||
universityCourse: () => RamCostConstants.ScriptSingularityFn1RamCost,
|
||||
gymWorkout: () => RamCostConstants.ScriptSingularityFn1RamCost,
|
||||
travelToCity: () => RamCostConstants.ScriptSingularityFn1RamCost,
|
||||
purchaseTor: () => RamCostConstants.ScriptSingularityFn1RamCost,
|
||||
purchaseProgram: () => RamCostConstants.ScriptSingularityFn1RamCost,
|
||||
getStats: () => RamCostConstants.ScriptSingularityFn1RamCost / 4,
|
||||
getCharacterInformation: () => RamCostConstants.ScriptSingularityFn1RamCost / 4,
|
||||
isBusy: () => RamCostConstants.ScriptSingularityFn1RamCost / 4,
|
||||
stopAction: () => RamCostConstants.ScriptSingularityFn1RamCost / 2,
|
||||
upgradeHomeRam: () => RamCostConstants.ScriptSingularityFn2RamCost,
|
||||
getUpgradeHomeRamCost: () => RamCostConstants.ScriptSingularityFn2RamCost / 2,
|
||||
workForCompany: () => RamCostConstants.ScriptSingularityFn2RamCost,
|
||||
applyToCompany: () => RamCostConstants.ScriptSingularityFn2RamCost,
|
||||
getCompanyRep: () => RamCostConstants.ScriptSingularityFn2RamCost / 3,
|
||||
getCompanyFavor: () => RamCostConstants.ScriptSingularityFn2RamCost / 3,
|
||||
getCompanyFavorGain: () => RamCostConstants.ScriptSingularityFn2RamCost / 4,
|
||||
checkFactionInvitations: () => RamCostConstants.ScriptSingularityFn2RamCost,
|
||||
joinFaction: () => RamCostConstants.ScriptSingularityFn2RamCost,
|
||||
workForFaction: () => RamCostConstants.ScriptSingularityFn2RamCost,
|
||||
getFactionRep: () => RamCostConstants.ScriptSingularityFn2RamCost / 3,
|
||||
getFactionFavor: () => RamCostConstants.ScriptSingularityFn2RamCost / 3,
|
||||
getFactionFavorGain: () => RamCostConstants.ScriptSingularityFn2RamCost / 4,
|
||||
donateToFaction: () => RamCostConstants.ScriptSingularityFn3RamCost,
|
||||
createProgram: () => RamCostConstants.ScriptSingularityFn3RamCost,
|
||||
commitCrime: () => RamCostConstants.ScriptSingularityFn3RamCost,
|
||||
getCrimeChance: () => RamCostConstants.ScriptSingularityFn3RamCost,
|
||||
getOwnedAugmentations: () => RamCostConstants.ScriptSingularityFn3RamCost,
|
||||
getOwnedSourceFiles: () => RamCostConstants.ScriptSingularityFn3RamCost,
|
||||
getAugmentationsFromFaction: () => RamCostConstants.ScriptSingularityFn3RamCost,
|
||||
getAugmentationPrereq: () => RamCostConstants.ScriptSingularityFn3RamCost,
|
||||
getAugmentationCost: () => RamCostConstants.ScriptSingularityFn3RamCost,
|
||||
purchaseAugmentation: () => RamCostConstants.ScriptSingularityFn3RamCost,
|
||||
installAugmentations: () => RamCostConstants.ScriptSingularityFn3RamCost,
|
||||
|
||||
// Gang API
|
||||
gang : {
|
||||
getMemberNames: () => RamCostConstants.ScriptGangApiBaseRamCost / 4,
|
||||
getGangInformation: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,
|
||||
getOtherGangInformation: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,
|
||||
getMemberInformation: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,
|
||||
canRecruitMember: () => RamCostConstants.ScriptGangApiBaseRamCost / 4,
|
||||
recruitMember: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,
|
||||
getTaskNames: () => RamCostConstants.ScriptGangApiBaseRamCost / 4,
|
||||
setMemberTask: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,
|
||||
getEquipmentNames: () => RamCostConstants.ScriptGangApiBaseRamCost / 4,
|
||||
getEquipmentCost: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,
|
||||
getEquipmentType: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,
|
||||
purchaseEquipment: () => RamCostConstants.ScriptGangApiBaseRamCost,
|
||||
ascendMember: () => RamCostConstants.ScriptGangApiBaseRamCost,
|
||||
setTerritoryWarfare: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,
|
||||
getChanceToWinClash: () => RamCostConstants.ScriptGangApiBaseRamCost,
|
||||
getBonusTime: () => 0,
|
||||
},
|
||||
|
||||
// Bladeburner API
|
||||
bladeburner : {
|
||||
getContractNames: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10,
|
||||
getOperationNames: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10,
|
||||
getBlackOpNames: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10,
|
||||
getBlackOpRank: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 2,
|
||||
getGeneralActionNames: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10,
|
||||
getSkillNames: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10,
|
||||
startAction: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
stopBladeburnerAction: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 2,
|
||||
getCurrentAction: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 4,
|
||||
getActionTime: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
getActionEstimatedSuccessChance: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
getActionRepGain: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
getActionCountRemaining: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
getActionMaxLevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
getActionCurrentLevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
getActionAutolevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
setActionAutolevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
setActionLevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
getRank: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
getSkillPoints: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
getSkillLevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
getSkillUpgradeCost: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
upgradeSkill: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
getTeamSize: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
setTeamSize: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
getCityEstimatedPopulation: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
getCityEstimatedCommunities: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
getCityChaos: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
getCity: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
switchCity: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
getStamina: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
joinBladeburnerFaction: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
joinBladeburnerDivision: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
|
||||
getBonusTime: () => 0,
|
||||
},
|
||||
|
||||
// Coding Contract API
|
||||
codingcontract : {
|
||||
attempt: () => RamCostConstants.ScriptCodingContractBaseRamCost,
|
||||
getContractType: () => RamCostConstants.ScriptCodingContractBaseRamCost / 2,
|
||||
getData: () => RamCostConstants.ScriptCodingContractBaseRamCost / 2,
|
||||
getDescription: () => RamCostConstants.ScriptCodingContractBaseRamCost / 2,
|
||||
getNumTriesRemaining: () => RamCostConstants.ScriptCodingContractBaseRamCost / 5,
|
||||
},
|
||||
|
||||
// Duplicate Sleeve API
|
||||
sleeve : {
|
||||
getNumSleeves: () => RamCostConstants.ScriptSleeveBaseRamCost,
|
||||
setToShockRecovery: () => RamCostConstants.ScriptSleeveBaseRamCost,
|
||||
setToSynchronize: () => RamCostConstants.ScriptSleeveBaseRamCost,
|
||||
setToCommitCrime: () => RamCostConstants.ScriptSleeveBaseRamCost,
|
||||
setToUniversityCourse: () => RamCostConstants.ScriptSleeveBaseRamCost,
|
||||
travel: () => RamCostConstants.ScriptSleeveBaseRamCost,
|
||||
setToCompanyWork: () => RamCostConstants.ScriptSleeveBaseRamCost,
|
||||
setToFactionWork: () => RamCostConstants.ScriptSleeveBaseRamCost,
|
||||
setToGymWorkout: () => RamCostConstants.ScriptSleeveBaseRamCost,
|
||||
getSleeveStats: () => RamCostConstants.ScriptSleeveBaseRamCost,
|
||||
getTask: () => RamCostConstants.ScriptSleeveBaseRamCost,
|
||||
getInformation: () => RamCostConstants.ScriptSleeveBaseRamCost,
|
||||
getSleeveAugmentations: () => RamCostConstants.ScriptSleeveBaseRamCost,
|
||||
getSleevePurchasableAugs: () => RamCostConstants.ScriptSleeveBaseRamCost,
|
||||
purchaseSleeveAug: () => RamCostConstants.ScriptSleeveBaseRamCost,
|
||||
},
|
||||
|
||||
heart: {
|
||||
// Easter egg function
|
||||
break : () => 0,
|
||||
}
|
||||
}
|
||||
|
||||
export function getRamCost(...args: string[]): number {
|
||||
if (args.length === 0) {
|
||||
console.warn(`No arguments passed to getRamCost()`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
let curr = RamCosts[args[0]];
|
||||
for (let i = 1; i < args.length; ++i) {
|
||||
if (curr == null) {
|
||||
console.warn(`Invalid function passed to getRamCost: ${args}`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const currType = typeof curr;
|
||||
if (currType === "function" || currType === "number") {
|
||||
break;
|
||||
}
|
||||
|
||||
curr = curr[args[i]];
|
||||
}
|
||||
|
||||
const currType = typeof curr;
|
||||
if (currType === "function") {
|
||||
return curr();
|
||||
}
|
||||
|
||||
if (currType === "number") {
|
||||
return curr;
|
||||
}
|
||||
|
||||
console.warn(`Unexpected type (${currType}) for value [${args}]`);
|
||||
return 0;
|
||||
}
|
176
src/Netscript/WorkerScript.ts
Normal file
176
src/Netscript/WorkerScript.ts
Normal file
@ -0,0 +1,176 @@
|
||||
/**
|
||||
* The worker agent for running a script instance. Each running script instance
|
||||
* has its own underlying WorkerScript object.
|
||||
*
|
||||
* Note that these objects are not saved and re-loaded when the game is refreshed.
|
||||
* Instead, whenever the game is opened, WorkerScripts are re-created from
|
||||
* RunningScript objects
|
||||
*/
|
||||
import { Environment } from "./Environment";
|
||||
import { RamCostConstants } from "./RamCostGenerator";
|
||||
|
||||
import { RunningScript } from "../Script/RunningScript";
|
||||
import { Script } from "../Script/Script";
|
||||
import { AllServers } from "../Server/AllServers";
|
||||
import { BaseServer } from "../Server/BaseServer";
|
||||
import { IMap } from "../types";
|
||||
|
||||
export class WorkerScript {
|
||||
/**
|
||||
* Script's arguments
|
||||
*/
|
||||
args: any[];
|
||||
|
||||
/**
|
||||
* Copy of the script's code
|
||||
*/
|
||||
code: string = "";
|
||||
|
||||
/**
|
||||
* Holds the timeoutID (numeric value) for whenever this script is blocked by a
|
||||
* timed Netscript function. i.e. Holds the return value of setTimeout()
|
||||
*/
|
||||
delay: number | null = null;
|
||||
|
||||
/**
|
||||
* Stores names of all functions that have logging disabled
|
||||
*/
|
||||
disableLogs: IMap<string> = {};
|
||||
|
||||
/**
|
||||
* Used for dynamic RAM calculation. Stores names of all functions that have
|
||||
* already been checked by this script.
|
||||
* TODO: Could probably just combine this with loadedFns?
|
||||
*/
|
||||
dynamicLoadedFns: IMap<string> = {};
|
||||
|
||||
/**
|
||||
* Tracks dynamic RAM usage
|
||||
*/
|
||||
dynamicRamUsage: number = RamCostConstants.ScriptBaseRamCost;
|
||||
|
||||
/**
|
||||
* Netscript Environment for this script
|
||||
*/
|
||||
env: Environment;
|
||||
|
||||
/**
|
||||
* Status message in case of script error. Currently unused I think
|
||||
*/
|
||||
errorMessage: string = "";
|
||||
|
||||
/**
|
||||
* Used for static RAM calculation. Stores names of all functions that have
|
||||
* already been checked by this script
|
||||
*/
|
||||
loadedFns: IMap<string> = {};
|
||||
|
||||
/**
|
||||
* Filename of script
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Script's output/return value. Currently not used or implemented
|
||||
*/
|
||||
output: string = "";
|
||||
|
||||
/**
|
||||
* Script's Static RAM usage. Equivalent to underlying script's RAM usage
|
||||
*/
|
||||
ramUsage: number = 0;
|
||||
|
||||
/**
|
||||
* Whether or not this workerScript is currently running
|
||||
*/
|
||||
running: boolean = false;
|
||||
|
||||
/**
|
||||
* Reference to underlying RunningScript object
|
||||
*/
|
||||
scriptRef: RunningScript;
|
||||
|
||||
/**
|
||||
* IP Address on which this script is running
|
||||
*/
|
||||
serverIp: string;
|
||||
|
||||
constructor(runningScriptObj: RunningScript, nsFuncsGenerator?: (ws: WorkerScript) => object) {
|
||||
this.name = runningScriptObj.filename;
|
||||
this.serverIp = runningScriptObj.server;
|
||||
|
||||
// Get the underlying script's code
|
||||
const server = AllServers[this.serverIp];
|
||||
if (server == null) {
|
||||
throw new Error(`WorkerScript constructed with invalid server ip: ${this.serverIp}`);
|
||||
}
|
||||
let found = false;
|
||||
for (let i = 0; i < server.scripts.length; ++i) {
|
||||
if (server.scripts[i].filename === this.name) {
|
||||
found = true;
|
||||
this.code = server.scripts[i].code;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
throw new Error(`WorkerScript constructed with invalid script filename: ${this.name}`);
|
||||
}
|
||||
|
||||
this.env = new Environment(null);
|
||||
if (typeof nsFuncsGenerator === "function") {
|
||||
this.env.vars = nsFuncsGenerator(this);
|
||||
}
|
||||
this.env.set("args", runningScriptObj.args.slice());
|
||||
|
||||
this.scriptRef = runningScriptObj;
|
||||
this.args = runningScriptObj.args.slice();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Server on which this script is running
|
||||
*/
|
||||
getServer() {
|
||||
return AllServers[this.serverIp];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Script object for the underlying script.
|
||||
* Returns null if it cannot be found (which would be a bug)
|
||||
*/
|
||||
getScript(): Script | null {
|
||||
let server = this.getServer();
|
||||
for (let i = 0; i < server.scripts.length; ++i) {
|
||||
if (server.scripts[i].filename === this.name) {
|
||||
return server.scripts[i];
|
||||
}
|
||||
}
|
||||
|
||||
console.error("Failed to find underlying Script object in WorkerScript.getScript(). This probably means somethings wrong");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the script with the specified filename on the specified server,
|
||||
* or null if it cannot be found
|
||||
*/
|
||||
getScriptOnServer(fn: string, server: BaseServer): Script | null {
|
||||
if (server == null) {
|
||||
server = this.getServer();
|
||||
}
|
||||
|
||||
for (let i = 0; i < server.scripts.length; ++i) {
|
||||
if (server.scripts[i].filename === fn) {
|
||||
return server.scripts[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
shouldLog(fn: string): boolean {
|
||||
return (this.disableLogs.ALL == null && this.disableLogs[fn] == null);
|
||||
}
|
||||
|
||||
log(txt: string): void {
|
||||
this.scriptRef.log(txt);
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
import { NetscriptFunctions } from "./NetscriptFunctions";
|
||||
import { NetscriptPort } from "./NetscriptPort";
|
||||
|
||||
/* Environment
|
||||
* NetScript program environment
|
||||
*/
|
||||
function Environment(workerScript,parent) {
|
||||
if (parent){
|
||||
//Create a copy of parent's variables
|
||||
//this.vars = parent.vars;
|
||||
this.vars = Object.assign({}, parent.vars);
|
||||
} else {
|
||||
this.vars = NetscriptFunctions(workerScript);
|
||||
}
|
||||
this.parent = parent;
|
||||
this.stopFlag = false;
|
||||
}
|
||||
Environment.prototype = {
|
||||
//Create a "subscope", which is a new new "sub-environment"
|
||||
//The subscope is linked to this through its parent variable
|
||||
extend: function() {
|
||||
return new Environment(null, this);
|
||||
},
|
||||
|
||||
//Finds the scope where the variable with the given name is defined
|
||||
lookup: function(name) {
|
||||
var scope = this;
|
||||
while (scope) {
|
||||
if (Object.prototype.hasOwnProperty.call(scope.vars, name)) {
|
||||
return scope;
|
||||
}
|
||||
scope = scope.parent;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
//Get the current value of a variable
|
||||
get: function(name) {
|
||||
if (name in this.vars) {
|
||||
return this.vars[name];
|
||||
}
|
||||
throw new Error("Undefined variable " + name);
|
||||
},
|
||||
|
||||
//Sets the value of a variable in any scope
|
||||
set: function(name, value) {
|
||||
var scope = this.lookup(name);
|
||||
|
||||
//If scope has a value, then this variable is already set in a higher scope, so
|
||||
//set is there. Otherwise, create a new variable in the local scope
|
||||
if (scope !== null) {
|
||||
return scope.vars[name] = value;
|
||||
} else {
|
||||
return this.vars[name] = value;
|
||||
}
|
||||
},
|
||||
|
||||
//Creates (or overwrites) a variable in the current scope
|
||||
def: function(name, value) {
|
||||
return this.vars[name] = value;
|
||||
}
|
||||
};
|
||||
|
||||
export {Environment};
|
@ -1,132 +1,12 @@
|
||||
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
|
||||
import { CONSTANTS } from "./Constants";
|
||||
import { Player } from "./Player";
|
||||
import { Environment } from "./NetscriptEnvironment";
|
||||
import { WorkerScript, addWorkerScript } from "./NetscriptWorker";
|
||||
import { Server } from "./Server/Server";
|
||||
import { WorkerScript } from "./Netscript/WorkerScript";
|
||||
import { getServer } from "./Server/ServerHelpers";
|
||||
import { Settings } from "./Settings/Settings";
|
||||
import { RunningScript } from "./Script/RunningScript";
|
||||
import { Script } from "./Script/Script";
|
||||
import { findRunningScript } from "./Script/ScriptHelpers";
|
||||
|
||||
import { setTimeoutRef } from "./utils/SetTimeoutRef";
|
||||
import { parse, Node } from "../utils/acorn";
|
||||
|
||||
import { arrayToString } from "../utils/helpers/arrayToString";
|
||||
import { isValidIPAddress } from "../utils/helpers/isValidIPAddress";
|
||||
import { isString } from "../utils/helpers/isString";
|
||||
|
||||
export function evaluateImport(exp, workerScript, checkingRam=false) {
|
||||
//When its checking RAM, it exports an array of nodes for each imported function
|
||||
var ramCheckRes = [];
|
||||
|
||||
var env = workerScript.env;
|
||||
if (env.stopFlag) {
|
||||
if (checkingRam) {return ramCheckRes;}
|
||||
return Promise.reject(workerScript);
|
||||
}
|
||||
|
||||
//Get source script and name of all functions to import
|
||||
var scriptName = exp.source.value;
|
||||
var namespace, namespaceObj, allFns = false, fnNames = [];
|
||||
if (exp.specifiers.length === 1 && exp.specifiers[0].type === "ImportNamespaceSpecifier") {
|
||||
allFns = true;
|
||||
namespace = exp.specifiers[0].local.name;
|
||||
} else {
|
||||
for (var i = 0; i < exp.specifiers.length; ++i) {
|
||||
fnNames.push(exp.specifiers[i].local.name);
|
||||
}
|
||||
}
|
||||
|
||||
//Get the code
|
||||
var server = getServer(workerScript.serverIp), code = "";
|
||||
if (server == null) {
|
||||
if (checkingRam) {return ramCheckRes;}
|
||||
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Failed to identify server. This is a bug please report to dev", exp));
|
||||
}
|
||||
for (var i = 0; i < server.scripts.length; ++i) {
|
||||
if (server.scripts[i].filename === scriptName) {
|
||||
code = server.scripts[i].code;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (code === "") {
|
||||
if (checkingRam) {return ramCheckRes;}
|
||||
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Could not find script " + scriptName + " to import", exp));
|
||||
}
|
||||
|
||||
//Create the AST
|
||||
try {
|
||||
var ast = parse(code, {sourceType:"module"});
|
||||
} catch(e) {
|
||||
console.log("Failed to parse import script");
|
||||
if (checkingRam) {return ramCheckRes;}
|
||||
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Failed to import functions from " + scriptName +
|
||||
" This is most likely due to a syntax error in the imported script", exp));
|
||||
}
|
||||
|
||||
if (allFns) {
|
||||
//A namespace is implemented as a JS obj
|
||||
env.set(namespace, {});
|
||||
namespaceObj = env.get(namespace);
|
||||
}
|
||||
|
||||
//Search through the AST for all imported functions
|
||||
var queue = [ast];
|
||||
while (queue.length != 0) {
|
||||
var node = queue.shift();
|
||||
switch (node.type) {
|
||||
case "BlockStatement":
|
||||
case "Program":
|
||||
for (var i = 0; i < node.body.length; ++i) {
|
||||
if (node.body[i] instanceof Node) {
|
||||
queue.push(node.body[i]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "FunctionDeclaration":
|
||||
if (node.id && node.id.name) {
|
||||
if (allFns) {
|
||||
//Import all functions under this namespace
|
||||
if (checkingRam) {
|
||||
ramCheckRes.push(node);
|
||||
} else {
|
||||
namespaceObj[node.id.name] = node;
|
||||
}
|
||||
} else {
|
||||
//Only import specified functions
|
||||
if (fnNames.includes(node.id.name)) {
|
||||
if (checkingRam) {
|
||||
ramCheckRes.push(node);
|
||||
} else {
|
||||
env.set(node.id.name, node);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (checkingRam) {return ramCheckRes;}
|
||||
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Invalid function declaration in imported script " + scriptName, exp));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
for (var prop in node) {
|
||||
if (node.hasOwnProperty(prop)) {
|
||||
if (node[prop] instanceof Node) {
|
||||
queue.push(node[prop]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!checkingRam) {workerScript.scriptRef.log("Imported functions from " + scriptName);}
|
||||
if (checkingRam) {return ramCheckRes;}
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
export function killNetscriptDelay(workerScript) {
|
||||
if (workerScript instanceof WorkerScript) {
|
||||
if (workerScript.delay) {
|
||||
@ -155,59 +35,21 @@ export function makeRuntimeRejectMsg(workerScript, msg, exp=null) {
|
||||
return "|"+workerScript.serverIp+"|"+workerScript.name+"|" + msg + lineNum;
|
||||
}
|
||||
|
||||
//Run a script from inside a script using run() command
|
||||
export function runScriptFromScript(server, scriptname, args, workerScript, threads=1) {
|
||||
//Check if the script is already running
|
||||
var runningScriptObj = findRunningScript(scriptname, args, server);
|
||||
if (runningScriptObj != null) {
|
||||
workerScript.scriptRef.log(scriptname + " is already running on " + server.hostname);
|
||||
return Promise.resolve(false);
|
||||
export function resolveNetscriptRequestedThreads(workerScript, functionName, requestedThreads) {
|
||||
const threads = workerScript.scriptRef.threads;
|
||||
if (!requestedThreads) {
|
||||
return (isNaN(threads) || threads < 1) ? 1 : threads;
|
||||
}
|
||||
const requestedThreadsAsInt = requestedThreads|0;
|
||||
if (isNaN(requestedThreads) || requestedThreadsAsInt < 1) {
|
||||
throw makeRuntimeRejectMsg(workerScript, `Invalid thread count passed to ${functionName}: ${requestedThreads}. Threads must be a positive number.`);
|
||||
}
|
||||
if (requestedThreads > threads) {
|
||||
throw makeRuntimeRejectMsg(workerScript, `Too many threads requested by ${functionName}. Requested: ${requestedThreads}. Has: ${threads}.`);
|
||||
}
|
||||
return requestedThreadsAsInt;
|
||||
}
|
||||
|
||||
//'null/undefined' arguments are not allowed
|
||||
for (var i = 0; i < args.length; ++i) {
|
||||
if (args[i] == null) {
|
||||
workerScript.scriptRef.log("ERROR: Cannot execute a script with null/undefined as an argument");
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
//Check if the script exists and if it does run it
|
||||
for (var i = 0; i < server.scripts.length; ++i) {
|
||||
if (server.scripts[i].filename == scriptname) {
|
||||
//Check for admin rights and that there is enough RAM availble to run
|
||||
var script = server.scripts[i];
|
||||
var ramUsage = script.ramUsage;
|
||||
threads = Math.round(Number(threads)); //Convert to number and round
|
||||
if (threads === 0) { return Promise.resolve(false); }
|
||||
ramUsage = ramUsage * threads;
|
||||
var ramAvailable = server.maxRam - server.ramUsed;
|
||||
|
||||
if (server.hasAdminRights == false) {
|
||||
workerScript.scriptRef.log("Cannot run script " + scriptname + " on " + server.hostname + " because you do not have root access!");
|
||||
return Promise.resolve(false);
|
||||
} else if (ramUsage > ramAvailable){
|
||||
workerScript.scriptRef.log("Cannot run script " + scriptname + "(t=" + threads + ") on " + server.hostname + " because there is not enough available RAM!");
|
||||
return Promise.resolve(false);
|
||||
} else {
|
||||
//Able to run script
|
||||
if(workerScript.disableLogs.ALL == null && workerScript.disableLogs.exec == null && workerScript.disableLogs.run == null && workerScript.disableLogs.spawn == null) {
|
||||
workerScript.scriptRef.log("Running script: " + scriptname + " on " + server.hostname + " with " + threads + " threads and args: " + arrayToString(args) + ". May take a few seconds to start up...");
|
||||
}
|
||||
var runningScriptObj = new RunningScript(script, args);
|
||||
runningScriptObj.threads = threads;
|
||||
addWorkerScript(runningScriptObj, server);
|
||||
|
||||
// Push onto runningScripts.
|
||||
// This has to come after addWorkerScript() because that fn updates RAM usage
|
||||
server.runScript(runningScriptObj, Player);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
workerScript.scriptRef.log("Could not find script " + scriptname + " on " + server.hostname);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
export function getErrorLineNumber(exp, workerScript) {
|
||||
var code = workerScript.scriptRef.codeCode();
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* Functions for handling WorkerScripts, which are the underlying mechanism
|
||||
* that allows for scripts to run
|
||||
*/
|
||||
import { WorkerScript } from "./Netscript/WorkerScript";
|
||||
|
||||
import {
|
||||
addActiveScriptsItem,
|
||||
deleteActiveScriptsItem,
|
||||
@ -6,9 +12,7 @@ import {
|
||||
import { CONSTANTS } from "./Constants";
|
||||
import { Engine } from "./engine";
|
||||
import { Interpreter } from "./JSInterpreter";
|
||||
import { Environment } from "./NetscriptEnvironment";
|
||||
import {
|
||||
evaluate,
|
||||
isScriptErrorMessage,
|
||||
makeRuntimeRejectMsg,
|
||||
killNetscriptDelay
|
||||
@ -16,6 +20,13 @@ import {
|
||||
import { NetscriptFunctions } from "./NetscriptFunctions";
|
||||
import { executeJSScript } from "./NetscriptJSEvaluator";
|
||||
import { NetscriptPort } from "./NetscriptPort";
|
||||
import { Player } from "./Player";
|
||||
import { RunningScript } from "./Script/RunningScript";
|
||||
import { getRamUsageFromRunningScript } from "./Script/RunningScriptHelpers";
|
||||
import {
|
||||
findRunningScript,
|
||||
scriptCalculateOfflineProduction,
|
||||
} from "./Script/ScriptHelpers";
|
||||
import { AllServers } from "./Server/AllServers";
|
||||
import { Settings } from "./Settings/Settings";
|
||||
import { setTimeoutRef } from "./utils/SetTimeoutRef";
|
||||
@ -29,79 +40,17 @@ import { arrayToString } from "../utils/helpers/arrayToString";
|
||||
import { roundToTwo } from "../utils/helpers/roundToTwo";
|
||||
import { isString } from "../utils/StringHelperFunctions";
|
||||
|
||||
|
||||
const walk = require("acorn/dist/walk");
|
||||
|
||||
function WorkerScript(runningScriptObj) {
|
||||
this.name = runningScriptObj.filename;
|
||||
this.running = false;
|
||||
this.serverIp = runningScriptObj.server;
|
||||
this.code = runningScriptObj.getCode();
|
||||
this.env = new Environment(this);
|
||||
this.env.set("args", runningScriptObj.args.slice());
|
||||
this.output = "";
|
||||
this.ramUsage = 0;
|
||||
this.scriptRef = runningScriptObj;
|
||||
this.errorMessage = "";
|
||||
this.args = runningScriptObj.args.slice();
|
||||
this.delay = null;
|
||||
this.fnWorker = null; //Workerscript for a function call
|
||||
this.checkingRam = false;
|
||||
this.loadedFns = {}; //Stores names of fns that are "loaded" by this script, thus using RAM. Used for static RAM evaluation
|
||||
this.disableLogs = {}; //Stores names of fns that should have logs disabled
|
||||
|
||||
//Properties used for dynamic RAM evaluation
|
||||
this.dynamicRamUsage = CONSTANTS.ScriptBaseRamCost;
|
||||
this.dynamicLoadedFns = {};
|
||||
}
|
||||
|
||||
//Returns the server on which the workerScript is running
|
||||
WorkerScript.prototype.getServer = function() {
|
||||
return AllServers[this.serverIp];
|
||||
}
|
||||
|
||||
//Returns the Script object for the underlying script
|
||||
WorkerScript.prototype.getScript = function() {
|
||||
let server = this.getServer();
|
||||
for (let i = 0; i < server.scripts.length; ++i) {
|
||||
if (server.scripts[i].filename === this.name) {
|
||||
return server.scripts[i];
|
||||
}
|
||||
}
|
||||
console.log("ERROR: Failed to find underlying Script object in WorkerScript.getScript(). This probably means somethings wrong");
|
||||
return null;
|
||||
}
|
||||
|
||||
//Returns the Script object for the specified script
|
||||
WorkerScript.prototype.getScriptOnServer = function(fn, server) {
|
||||
if (server == null) {
|
||||
server = this.getServer();
|
||||
}
|
||||
for (let i = 0; i < server.scripts.length; ++i) {
|
||||
if (server.scripts[i].filename === fn) {
|
||||
return server.scripts[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
WorkerScript.prototype.shouldLog = function(fn) {
|
||||
return (this.disableLogs.ALL == null && this.disableLogs[fn] == null);
|
||||
}
|
||||
|
||||
WorkerScript.prototype.log = function(txt) {
|
||||
this.scriptRef.log(txt);
|
||||
}
|
||||
|
||||
//Array containing all scripts that are running across all servers, to easily run them all
|
||||
let workerScripts = [];
|
||||
export const workerScripts = [];
|
||||
|
||||
var NetscriptPorts = [];
|
||||
export const NetscriptPorts = [];
|
||||
for (var i = 0; i < CONSTANTS.NumNetscriptPorts; ++i) {
|
||||
NetscriptPorts.push(new NetscriptPort());
|
||||
}
|
||||
|
||||
function prestigeWorkerScripts() {
|
||||
export function prestigeWorkerScripts() {
|
||||
for (var i = 0; i < workerScripts.length; ++i) {
|
||||
deleteActiveScriptsItem(workerScripts[i]);
|
||||
workerScripts[i].env.stopFlag = true;
|
||||
@ -219,9 +168,16 @@ function startNetscript1Script(workerScript) {
|
||||
name === "prompt" || name === "run" || name === "exec") {
|
||||
let tempWrapper = function() {
|
||||
let fnArgs = [];
|
||||
|
||||
//All of the Object/array elements are in JSInterpreter format, so
|
||||
//we have to convert them back to native format to pass them to these fns
|
||||
for (let i = 0; i < arguments.length-1; ++i) {
|
||||
if (typeof arguments[i] === 'object' || arguments[i].constructor === Array) {
|
||||
fnArgs.push(int.pseudoToNative(arguments[i]));
|
||||
} else {
|
||||
fnArgs.push(arguments[i]);
|
||||
}
|
||||
}
|
||||
let cb = arguments[arguments.length-1];
|
||||
let fnPromise = entry.apply(null, fnArgs);
|
||||
fnPromise.then(function(res) {
|
||||
@ -332,7 +288,7 @@ function startNetscript1Script(workerScript) {
|
||||
*/
|
||||
function processNetscript1Imports(code, workerScript) {
|
||||
//allowReserved prevents 'import' from throwing error in ES5
|
||||
var ast = parse(code, {ecmaVersion:6, allowReserved:true, sourceType:"module"});
|
||||
const ast = parse(code, { ecmaVersion: 6, allowReserved: true, sourceType: "module" });
|
||||
|
||||
var server = workerScript.getServer();
|
||||
if (server == null) {
|
||||
@ -348,8 +304,8 @@ function processNetscript1Imports(code, workerScript) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var generatedCode = ""; //Generated Javascript Code
|
||||
var hasImports = false;
|
||||
let generatedCode = ""; // Generated Javascript Code
|
||||
let hasImports = false;
|
||||
|
||||
// Walk over the tree and process ImportDeclaration nodes
|
||||
walk.simple(ast, {
|
||||
@ -378,7 +334,7 @@ function processNetscript1Imports(code, workerScript) {
|
||||
});
|
||||
|
||||
//Now we have to generate the code that would create the namespace
|
||||
generatedCode =
|
||||
generatedCode +=
|
||||
"var " + namespace + ";\n" +
|
||||
"(function (namespace) {\n";
|
||||
|
||||
@ -449,6 +405,7 @@ function processNetscript1Imports(code, workerScript) {
|
||||
|
||||
//Add the imported code and re-generate in ES5 (JS Interpreter for NS1 only supports ES5);
|
||||
code = generatedCode + code;
|
||||
|
||||
var res = {
|
||||
code: code,
|
||||
lineOffset: lineOffset
|
||||
@ -457,25 +414,25 @@ function processNetscript1Imports(code, workerScript) {
|
||||
}
|
||||
|
||||
// Loop through workerScripts and run every script that is not currently running
|
||||
function runScriptsLoop() {
|
||||
var scriptDeleted = false;
|
||||
export function runScriptsLoop() {
|
||||
let scriptDeleted = false;
|
||||
|
||||
// Delete any scripts that finished or have been killed. Loop backwards bc removing items screws up indexing
|
||||
for (var i = workerScripts.length - 1; i >= 0; i--) {
|
||||
for (let i = workerScripts.length - 1; i >= 0; i--) {
|
||||
if (workerScripts[i].running == false && workerScripts[i].env.stopFlag == true) {
|
||||
scriptDeleted = true;
|
||||
// Delete script from the runningScripts array on its host serverIp
|
||||
var ip = workerScripts[i].serverIp;
|
||||
var name = workerScripts[i].name;
|
||||
const ip = workerScripts[i].serverIp;
|
||||
const name = workerScripts[i].name;
|
||||
|
||||
//recalculate ram used
|
||||
// Recalculate ram used
|
||||
AllServers[ip].ramUsed = 0;
|
||||
for (let j = 0; j < workerScripts.length; j++) {
|
||||
if (workerScripts[j].serverIp !== ip) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
if (j === i) { // not this one
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
AllServers[ip].ramUsed += workerScripts[j].ramUsage;
|
||||
}
|
||||
@ -483,7 +440,7 @@ function runScriptsLoop() {
|
||||
// Delete script from Active Scripts
|
||||
deleteActiveScriptsItem(workerScripts[i]);
|
||||
|
||||
for (var j = 0; j < AllServers[ip].runningScripts.length; j++) {
|
||||
for (let j = 0; j < AllServers[ip].runningScripts.length; j++) {
|
||||
if (AllServers[ip].runningScripts[j].filename == name &&
|
||||
compareArrays(AllServers[ip].runningScripts[j].args, workerScripts[i].args)) {
|
||||
AllServers[ip].runningScripts.splice(j, 1);
|
||||
@ -499,7 +456,7 @@ function runScriptsLoop() {
|
||||
|
||||
|
||||
// Run any scripts that haven't been started
|
||||
for (var i = 0; i < workerScripts.length; i++) {
|
||||
for (let i = 0; i < workerScripts.length; i++) {
|
||||
// If it isn't running, start the script
|
||||
if (workerScripts[i].running == false && workerScripts[i].env.stopFlag == false) {
|
||||
let p = null; // p is the script's result promise.
|
||||
@ -560,33 +517,31 @@ function runScriptsLoop() {
|
||||
}
|
||||
}
|
||||
|
||||
setTimeoutRef(runScriptsLoop, 6000);
|
||||
setTimeoutRef(runScriptsLoop, 3e3);
|
||||
}
|
||||
|
||||
//Queues a script to be killed by settings its stop flag to true. Then, the code will reject
|
||||
//all of its promises recursively, and when it does so it will no longer be running.
|
||||
//The runScriptsLoop() will then delete the script from worker scripts
|
||||
function killWorkerScript(runningScriptObj, serverIp) {
|
||||
/**
|
||||
* Queues a script to be killed by setting its stop flag to true. This
|
||||
* kills and timed/blocking Netscript functions (like hack(), sleep(), etc.) and
|
||||
* prevents any further execution of Netscript functions.
|
||||
* The runScriptsLoop() handles the actual deletion of the WorkerScript
|
||||
*/
|
||||
export function killWorkerScript(runningScriptObj, serverIp) {
|
||||
for (var i = 0; i < workerScripts.length; i++) {
|
||||
if (workerScripts[i].name == runningScriptObj.filename && workerScripts[i].serverIp == serverIp &&
|
||||
compareArrays(workerScripts[i].args, runningScriptObj.args)) {
|
||||
workerScripts[i].env.stopFlag = true;
|
||||
killNetscriptDelay(workerScripts[i]);
|
||||
//Recursively kill all functions
|
||||
var curr = workerScripts[i];
|
||||
while (curr.fnWorker) {
|
||||
curr.fnWorker.env.stopFlag = true;
|
||||
killNetscriptDelay(curr.fnWorker);
|
||||
curr = curr.fnWorker;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//Queues a script to be run
|
||||
function addWorkerScript(runningScriptObj, server) {
|
||||
/**
|
||||
* Given a RunningScript object, queues that script to be run
|
||||
*/
|
||||
export function addWorkerScript(runningScriptObj, server) {
|
||||
var filename = runningScriptObj.filename;
|
||||
|
||||
//Update server's ram usage
|
||||
@ -596,7 +551,7 @@ function addWorkerScript(runningScriptObj, server) {
|
||||
} else {
|
||||
runningScriptObj.threads = 1;
|
||||
}
|
||||
var ramUsage = roundToTwo(runningScriptObj.getRamUsage() * threads);
|
||||
var ramUsage = roundToTwo(getRamUsageFromRunningScript(runningScriptObj) * threads);
|
||||
var ramAvailable = server.maxRam - server.ramUsed;
|
||||
if (ramUsage > ramAvailable) {
|
||||
dialogBoxCreate("Not enough RAM to run script " + runningScriptObj.filename + " with args " +
|
||||
@ -608,7 +563,7 @@ function addWorkerScript(runningScriptObj, server) {
|
||||
server.ramUsed = roundToTwo(server.ramUsed + ramUsage);
|
||||
|
||||
//Create the WorkerScript
|
||||
var s = new WorkerScript(runningScriptObj);
|
||||
var s = new WorkerScript(runningScriptObj, NetscriptFunctions);
|
||||
s.ramUsage = ramUsage;
|
||||
|
||||
//Add the WorkerScript to the Active Scripts list
|
||||
@ -619,14 +574,105 @@ function addWorkerScript(runningScriptObj, server) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Updates the online running time stat of all running scripts
|
||||
function updateOnlineScriptTimes(numCycles = 1) {
|
||||
/**
|
||||
* Updates the online running time stat of all running scripts
|
||||
*/
|
||||
export function updateOnlineScriptTimes(numCycles = 1) {
|
||||
var time = (numCycles * Engine._idleSpeed) / 1000; //seconds
|
||||
for (var i = 0; i < workerScripts.length; ++i) {
|
||||
workerScripts[i].scriptRef.onlineRunningTime += time;
|
||||
}
|
||||
}
|
||||
|
||||
export {WorkerScript, workerScripts, NetscriptPorts, runScriptsLoop,
|
||||
killWorkerScript, addWorkerScript, updateOnlineScriptTimes,
|
||||
prestigeWorkerScripts};
|
||||
/**
|
||||
* Called when the game is loaded. Loads all running scripts (from all servers)
|
||||
* into worker scripts so that they will start running
|
||||
*/
|
||||
export function loadAllRunningScripts() {
|
||||
var total = 0;
|
||||
let skipScriptLoad = (window.location.href.toLowerCase().indexOf("?noscripts") !== -1);
|
||||
if (skipScriptLoad) { console.info("Skipping the load of any scripts during startup"); }
|
||||
for (const property in AllServers) {
|
||||
if (AllServers.hasOwnProperty(property)) {
|
||||
const server = AllServers[property];
|
||||
|
||||
// Reset each server's RAM usage to 0
|
||||
server.ramUsed = 0;
|
||||
|
||||
// Reset modules on all scripts
|
||||
for (let i = 0; i < server.scripts.length; ++i) {
|
||||
server.scripts[i].module = "";
|
||||
}
|
||||
|
||||
if (skipScriptLoad) {
|
||||
// Start game with no scripts
|
||||
server.runningScripts.length = 0;
|
||||
} else {
|
||||
for (let j = 0; j < server.runningScripts.length; ++j) {
|
||||
addWorkerScript(server.runningScripts[j], server);
|
||||
|
||||
// Offline production
|
||||
total += scriptCalculateOfflineProduction(server.runningScripts[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a script from inside another script (run(), exec(), spawn(), etc.)
|
||||
*/
|
||||
export function runScriptFromScript(server, scriptname, args, workerScript, threads=1) {
|
||||
//Check if the script is already running
|
||||
let runningScriptObj = findRunningScript(scriptname, args, server);
|
||||
if (runningScriptObj != null) {
|
||||
workerScript.scriptRef.log(scriptname + " is already running on " + server.hostname);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
//'null/undefined' arguments are not allowed
|
||||
for (let i = 0; i < args.length; ++i) {
|
||||
if (args[i] == null) {
|
||||
workerScript.scriptRef.log("ERROR: Cannot execute a script with null/undefined as an argument");
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
//Check if the script exists and if it does run it
|
||||
for (let i = 0; i < server.scripts.length; ++i) {
|
||||
if (server.scripts[i].filename == scriptname) {
|
||||
//Check for admin rights and that there is enough RAM availble to run
|
||||
var script = server.scripts[i];
|
||||
var ramUsage = script.ramUsage;
|
||||
threads = Math.round(Number(threads)); //Convert to number and round
|
||||
if (threads === 0) { return Promise.resolve(false); }
|
||||
ramUsage = ramUsage * threads;
|
||||
var ramAvailable = server.maxRam - server.ramUsed;
|
||||
|
||||
if (server.hasAdminRights == false) {
|
||||
workerScript.scriptRef.log("Cannot run script " + scriptname + " on " + server.hostname + " because you do not have root access!");
|
||||
return Promise.resolve(false);
|
||||
} else if (ramUsage > ramAvailable){
|
||||
workerScript.scriptRef.log("Cannot run script " + scriptname + "(t=" + threads + ") on " + server.hostname + " because there is not enough available RAM!");
|
||||
return Promise.resolve(false);
|
||||
} else {
|
||||
//Able to run script
|
||||
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.exec == null && workerScript.disableLogs.run == null && workerScript.disableLogs.spawn == null) {
|
||||
workerScript.scriptRef.log(`Running script: ${scriptname} on ${server.hostname} with ${threads} threads and args: ${arrayToString(args)}. May take a few seconds to start up...`);
|
||||
}
|
||||
let runningScriptObj = new RunningScript(script, args);
|
||||
runningScriptObj.threads = threads;
|
||||
addWorkerScript(runningScriptObj, server);
|
||||
|
||||
// Push onto runningScripts.
|
||||
// This has to come after addWorkerScript() because that fn updates RAM usage
|
||||
server.runScript(runningScriptObj, Player.hacknet_node_money_mult);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
workerScript.scriptRef.log("Could not find script " + scriptname + " on " + server.hostname);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
@ -32,7 +32,10 @@ export interface IPlayer {
|
||||
factions: string[];
|
||||
firstTimeTraveled: boolean;
|
||||
hacknetNodes: (HacknetNode | string)[]; // HacknetNode object or IP of Hacknet Server
|
||||
has4SData: boolean;
|
||||
has4SDataTixApi: boolean;
|
||||
hashManager: HashManager;
|
||||
hasTixApiAccess: boolean;
|
||||
hasWseAccount: boolean;
|
||||
homeComputer: string;
|
||||
hp: number;
|
||||
@ -48,6 +51,7 @@ export interface IPlayer {
|
||||
purchasedServers: any[];
|
||||
queuedAugmentations: IPlayerOwnedAugmentation[];
|
||||
resleeves: Resleeve[];
|
||||
scriptProdSinceLastAug: number;
|
||||
sleeves: Sleeve[];
|
||||
sleevesFromCovenant: number;
|
||||
sourceFiles: IPlayerOwnedSourceFile[];
|
||||
@ -126,12 +130,14 @@ export interface IPlayer {
|
||||
gainCharismaExp(exp: number): void;
|
||||
gainMoney(money: number): void;
|
||||
getCurrentServer(): Server;
|
||||
getGangFaction(): Faction;
|
||||
getGangName(): string;
|
||||
getHomeComputer(): Server;
|
||||
getNextCompanyPosition(company: Company, entryPosType: CompanyPosition): CompanyPosition;
|
||||
getUpgradeHomeRamCost(): number;
|
||||
gotoLocation(to: LocationName): boolean;
|
||||
hasCorporation(): boolean;
|
||||
hasGangWith(facName: string): boolean;
|
||||
hasTorRouter(): boolean;
|
||||
inBladeburner(): boolean;
|
||||
inGang(): boolean;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import * as augmentationMethods from "./PlayerObjectAugmentationMethods";
|
||||
import * as bladeburnerMethods from "./PlayerObjectBladeburnerMethods";
|
||||
import * as corporationMethods from "./PlayerObjectCorporationMethods";
|
||||
import * as gangMethods from "./PlayerObjectGangMethods";
|
||||
@ -209,7 +210,8 @@ Object.assign(
|
||||
serverMethods,
|
||||
bladeburnerMethods,
|
||||
corporationMethods,
|
||||
gangMethods
|
||||
gangMethods,
|
||||
augmentationMethods
|
||||
);
|
||||
|
||||
PlayerObject.prototype.toJSON = function() {
|
||||
|
20
src/PersonObjects/Player/PlayerObjectAugmentationMethods.ts
Normal file
20
src/PersonObjects/Player/PlayerObjectAugmentationMethods.ts
Normal file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Augmentation-related methods for the Player class (PlayerObject)
|
||||
*/
|
||||
import { IPlayer } from "../IPlayer";
|
||||
|
||||
import { Augmentation } from "../../Augmentation/Augmentation";
|
||||
|
||||
export function hasAugmentation(this: IPlayer, aug: string | Augmentation): boolean {
|
||||
const augName: string = (aug instanceof Augmentation) ? aug.name : aug;
|
||||
|
||||
for (const owned of this.augmentations) {
|
||||
if (owned.name === augName) { return true; }
|
||||
}
|
||||
|
||||
for (const owned of this.queuedAugmentations) {
|
||||
if (owned.name === augName) { return true; }
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
@ -13,8 +13,21 @@ export function canAccessGang() {
|
||||
return (this.karma <= GangKarmaRequirement);
|
||||
}
|
||||
|
||||
export function getGangFaction() {
|
||||
const fac = Factions[this.gang.facName];
|
||||
if (fac == null) {
|
||||
throw new Error(`Gang has invalid faction name: ${this.gang.facName}`);
|
||||
}
|
||||
|
||||
return fac;
|
||||
}
|
||||
|
||||
export function getGangName() {
|
||||
return this.gang.facName;
|
||||
return this.inGang() ? this.gang.facName : "";
|
||||
}
|
||||
|
||||
export function hasGangWith(facName) {
|
||||
return this.inGang() && this.gang.facName === facName;
|
||||
}
|
||||
|
||||
export function inGang() {
|
||||
|
@ -27,11 +27,13 @@ import { Cities } from "../../Locations/Cities";
|
||||
import { Locations } from "../../Locations/Locations";
|
||||
import { CityName } from "../../Locations/data/CityNames";
|
||||
import { LocationName } from "../../Locations/data/LocationNames";
|
||||
import {hasBn11SF, hasWallStreetSF,hasAISF} from "../../NetscriptFunctions";
|
||||
import { Sleeve } from "../../PersonObjects/Sleeve/Sleeve";
|
||||
import { AllServers,
|
||||
AddToAllServers } from "../../Server/AllServers";
|
||||
import { Server } from "../../Server/Server";
|
||||
import {
|
||||
AllServers,
|
||||
AddToAllServers,
|
||||
createUniqueRandomIp,
|
||||
} from "../../Server/AllServers";
|
||||
import { safetlyCreateUniqueServer } from "../../Server/ServerHelpers";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { SpecialServerIps, SpecialServerNames } from "../../Server/SpecialServerIps";
|
||||
import { SourceFiles, applySourceFile } from "../../SourceFile";
|
||||
@ -43,18 +45,25 @@ import {numeralWrapper} from "../../ui/numeralFormat";
|
||||
import { MoneySourceTracker } from "../../utils/MoneySourceTracker";
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
import { clearEventListeners } from "../../../utils/uiHelpers/clearEventListeners";
|
||||
import {createRandomIp} from "../../../utils/IPAddress";
|
||||
import {Reviver, Generic_toJSON,
|
||||
Generic_fromJSON} from "../../../utils/JSONReviver";
|
||||
import {
|
||||
Reviver,
|
||||
Generic_toJSON,
|
||||
Generic_fromJSON,
|
||||
} from "../../../utils/JSONReviver";
|
||||
import {convertTimeMsToTimeElapsedString} from "../../../utils/StringHelperFunctions";
|
||||
|
||||
const CYCLES_PER_SEC = 1000 / CONSTANTS.MilliPerCycle;
|
||||
|
||||
export function init() {
|
||||
/* Initialize Player's home computer */
|
||||
var t_homeComp = new Server({
|
||||
ip:createRandomIp(), hostname:"home", organizationName:"Home PC",
|
||||
isConnectedTo:true, adminRights:true, purchasedByPlayer:true, maxRam:8
|
||||
var t_homeComp = safetlyCreateUniqueServer({
|
||||
adminRights: true,
|
||||
hostname: "home",
|
||||
ip: createUniqueRandomIp(),
|
||||
isConnectedTo: true,
|
||||
maxRam: 8,
|
||||
organizationName: "Home PC",
|
||||
purchasedByPlayer: true,
|
||||
});
|
||||
this.homeComputer = t_homeComp.ip;
|
||||
this.currentServer = t_homeComp.ip;
|
||||
@ -150,7 +159,7 @@ export function prestigeAugmentation() {
|
||||
this.moneySourceA.reset();
|
||||
|
||||
this.hacknetNodes.length = 0;
|
||||
this.hashManager.prestige(this);
|
||||
this.hashManager.prestige();
|
||||
|
||||
// Re-calculate skills and reset HP
|
||||
this.updateSkillLevels();
|
||||
@ -210,6 +219,11 @@ export function prestigeSourceFile() {
|
||||
}
|
||||
}
|
||||
|
||||
const characterMenuHeader = document.getElementById("character-menu-header");
|
||||
if (characterMenuHeader instanceof HTMLElement) {
|
||||
characterMenuHeader.click(); characterMenuHeader.click();
|
||||
}
|
||||
|
||||
this.isWorking = false;
|
||||
this.currentWorkFactionName = "";
|
||||
this.currentWorkFactionDescription = "";
|
||||
@ -240,7 +254,7 @@ export function prestigeSourceFile() {
|
||||
this.lastUpdate = new Date().getTime();
|
||||
|
||||
this.hacknetNodes.length = 0;
|
||||
this.hashManager.prestige(this);
|
||||
this.hashManager.prestige();
|
||||
|
||||
// Gang
|
||||
this.gang = null;
|
||||
@ -350,21 +364,24 @@ export function hasProgram(programName) {
|
||||
|
||||
export function setMoney(money) {
|
||||
if (isNaN(money)) {
|
||||
console.log("ERR: NaN passed into Player.setMoney()"); return;
|
||||
console.error("NaN passed into Player.setMoney()");
|
||||
return;
|
||||
}
|
||||
this.money = money;
|
||||
this.money = new Decimal(money);
|
||||
}
|
||||
|
||||
export function gainMoney(money) {
|
||||
if (isNaN(money)) {
|
||||
console.log("ERR: NaN passed into Player.gainMoney()"); return;
|
||||
console.error("NaN passed into Player.gainMoney()");
|
||||
return;
|
||||
}
|
||||
this.money = this.money.plus(money);
|
||||
}
|
||||
|
||||
export function loseMoney(money) {
|
||||
if (isNaN(money)) {
|
||||
console.log("ERR: NaN passed into Player.loseMoney()"); return;
|
||||
console.error("NaN passed into Player.loseMoney()");
|
||||
return;
|
||||
}
|
||||
this.money = this.money.minus(money);
|
||||
}
|
||||
@ -454,7 +471,7 @@ export function gainIntelligenceExp(exp) {
|
||||
if (isNaN(exp)) {
|
||||
console.log("ERROR: NaN passed into Player.gainIntelligenceExp()"); return;
|
||||
}
|
||||
if (hasAISF || this.intelligence > 0) {
|
||||
if (SourceFileFlags[5] > 0 || this.intelligence > 0) {
|
||||
this.intelligence_exp += exp;
|
||||
}
|
||||
}
|
||||
@ -943,7 +960,7 @@ export function getWorkMoneyGain() {
|
||||
// If player has SF-11, calculate salary multiplier from favor
|
||||
let bn11Mult = 1;
|
||||
const company = Companies[this.companyName];
|
||||
if (hasBn11SF) { bn11Mult = 1 + (company.favor / 100); }
|
||||
if (SourceFileFlags[11] > 0) { bn11Mult = 1 + (company.favor / 100); }
|
||||
|
||||
// Get base salary
|
||||
const companyPositionName = this.jobs[this.companyName];
|
||||
@ -1555,7 +1572,9 @@ export function hospitalize() {
|
||||
);
|
||||
}
|
||||
|
||||
this.loseMoney(this.max_hp * CONSTANTS.HospitalCostPerHp);
|
||||
const cost = this.max_hp * CONSTANTS.HospitalCostPerHp
|
||||
this.loseMoney(cost);
|
||||
this.recordMoneySource(-1 * cost, "hospitalization");
|
||||
this.hp = this.max_hp;
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,11 @@ import { CONSTANTS } from "../../Constants";
|
||||
|
||||
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
||||
import { HacknetServer } from "../../Hacknet/HacknetServer";
|
||||
import { AddToAllServers,
|
||||
AllServers } from "../../Server/AllServers";
|
||||
import {
|
||||
AddToAllServers,
|
||||
AllServers,
|
||||
createUniqueRandomIp,
|
||||
} from "../../Server/AllServers";
|
||||
import { SpecialServerIps } from "../../Server/SpecialServerIps";
|
||||
|
||||
export function hasTorRouter(this: IPlayer) {
|
||||
@ -41,7 +44,8 @@ export function createHacknetServer(this: IPlayer): HacknetServer {
|
||||
const server = new HacknetServer({
|
||||
adminRights: true,
|
||||
hostname: name,
|
||||
player: this,
|
||||
ip: createUniqueRandomIp(),
|
||||
// player: this,
|
||||
});
|
||||
this.hacknetNodes.push(server.ip);
|
||||
|
||||
|
@ -101,9 +101,12 @@ export function generateResleeves(): Resleeve[] {
|
||||
// Get a random aug
|
||||
const randIndex: number = getRandomInt(0, augKeys.length - 1)
|
||||
const randKey: string = augKeys[randIndex];
|
||||
if (randKey === AugmentationNames.TheRedPill) {
|
||||
continue; // A sleeve can't have The Red Pill
|
||||
|
||||
// Forbidden augmentations
|
||||
if (randKey === AugmentationNames.TheRedPill || randKey === AugmentationNames.NeuroFluxGovernor) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const randAug: Augmentation | null = Augmentations[randKey];
|
||||
r.augmentations.push({name: randAug!.name, level: 1});
|
||||
r.applyAugmentation(Augmentations[randKey]);
|
||||
|
@ -14,8 +14,6 @@ import { Person,
|
||||
createTaskTracker } from "../Person";
|
||||
|
||||
import { Augmentation } from "../../Augmentation/Augmentation";
|
||||
import { Augmentations } from "../../Augmentation/Augmentations";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
|
||||
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
||||
|
||||
@ -884,32 +882,4 @@ export class Sleeve extends Person {
|
||||
}
|
||||
}
|
||||
|
||||
export function findSleevePurchasableAugs(sleeve: Sleeve, p: IPlayer): Augmentation[] {
|
||||
// You can only purchase Augmentations that are actually available from
|
||||
// your factions. I.e. you must be in a faction that has the Augmentation
|
||||
// and you must also have enough rep in that faction in order to purchase it.
|
||||
|
||||
const ownedAugNames: string[] = sleeve.augmentations.map((e) => {return e.name});
|
||||
const availableAugs: Augmentation[] = [];
|
||||
|
||||
for (const facName of p.factions) {
|
||||
if (facName === "Bladeburners") { continue; }
|
||||
if (facName === "Netburners") { continue; }
|
||||
const fac: Faction | null = Factions[facName];
|
||||
if (fac == null) { continue; }
|
||||
|
||||
for (const augName of fac.augmentations) {
|
||||
if (augName === AugmentationNames.NeuroFluxGovernor) { continue; }
|
||||
if (ownedAugNames.includes(augName)) { continue; }
|
||||
const aug: Augmentation | null = Augmentations[augName];
|
||||
|
||||
if (fac.playerReputation > aug.baseRepRequirement && !availableAugs.includes(aug)) {
|
||||
availableAugs.push(aug);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return availableAugs;
|
||||
}
|
||||
|
||||
Reviver.constructors.Sleeve = Sleeve;
|
||||
|
@ -2,16 +2,13 @@
|
||||
* Module for handling the UI for purchasing Sleeve Augmentations
|
||||
* This UI is a popup, not a full page
|
||||
*/
|
||||
import { Sleeve, findSleevePurchasableAugs } from "./Sleeve";
|
||||
import { Sleeve } from "./Sleeve";
|
||||
import { findSleevePurchasableAugs } from "./SleeveHelpers";
|
||||
|
||||
import { IPlayer } from "../IPlayer";
|
||||
|
||||
import { Augmentation } from "../../Augmentation/Augmentation";
|
||||
import { Augmentations } from "../../Augmentation/Augmentations";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
|
||||
import { Faction } from "../../Faction/Faction";
|
||||
import { Factions } from "../../Faction/Factions";
|
||||
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
|
||||
|
64
src/PersonObjects/Sleeve/SleeveHelpers.ts
Normal file
64
src/PersonObjects/Sleeve/SleeveHelpers.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { Sleeve } from "./Sleeve";
|
||||
|
||||
import { IPlayer } from "../IPlayer";
|
||||
|
||||
import { Augmentation } from "../../Augmentation/Augmentation";
|
||||
import { Augmentations } from "../../Augmentation/Augmentations";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
import { Faction } from "../../Faction/Faction";
|
||||
import { Factions } from "../../Faction/Factions";
|
||||
|
||||
export function findSleevePurchasableAugs(sleeve: Sleeve, p: IPlayer): Augmentation[] {
|
||||
// You can only purchase Augmentations that are actually available from
|
||||
// your factions. I.e. you must be in a faction that has the Augmentation
|
||||
// and you must also have enough rep in that faction in order to purchase it.
|
||||
|
||||
const ownedAugNames: string[] = sleeve.augmentations.map((e) => {return e.name});
|
||||
const availableAugs: Augmentation[] = [];
|
||||
|
||||
// Helper function that helps filter out augs that are already owned
|
||||
// and augs that aren't allowed for sleeves
|
||||
function isAvailableForSleeve(aug: Augmentation): boolean {
|
||||
if (aug.name === AugmentationNames.NeuroFluxGovernor) { return false; }
|
||||
if (ownedAugNames.includes(aug.name)) { return false; }
|
||||
if (availableAugs.includes(aug)) { return false; }
|
||||
if (aug.isSpecial) { return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// If player is in a gang, then we return all augs that the player
|
||||
// has enough reputation for (since that gang offers all augs)
|
||||
if (p.inGang()) {
|
||||
const fac = p.getGangFaction();
|
||||
|
||||
for (const augName in Augmentations) {
|
||||
const aug = Augmentations[augName];
|
||||
if (!isAvailableForSleeve(aug)) { continue; }
|
||||
|
||||
if (fac.playerReputation > aug.baseRepRequirement) {
|
||||
availableAugs.push(aug);
|
||||
}
|
||||
}
|
||||
|
||||
return availableAugs;
|
||||
}
|
||||
|
||||
for (const facName of p.factions) {
|
||||
if (facName === "Bladeburners") { continue; }
|
||||
if (facName === "Netburners") { continue; }
|
||||
const fac: Faction | null = Factions[facName];
|
||||
if (fac == null) { continue; }
|
||||
|
||||
for (const augName of fac.augmentations) {
|
||||
const aug: Augmentation = Augmentations[augName];
|
||||
if (!isAvailableForSleeve(aug)) { continue; }
|
||||
|
||||
if (fac.playerReputation > aug.baseRepRequirement) {
|
||||
availableAugs.push(aug);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return availableAugs;
|
||||
}
|
3
src/Player.d.ts
vendored
Normal file
3
src/Player.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
import { IPlayer } from "./PersonObjects/IPlayer";
|
||||
|
||||
export declare let Player: IPlayer;
|
@ -16,14 +16,10 @@ import { Faction } from "./Faction/Faction";
|
||||
import { Factions, initFactions } from "./Faction/Factions";
|
||||
import { joinFaction } from "./Faction/FactionHelpers";
|
||||
import { deleteGangDisplayContent } from "./Gang";
|
||||
import { updateHashManagerCapacity } from "./Hacknet/HacknetHelpers";
|
||||
import { Message } from "./Message/Message";
|
||||
import { initMessages, Messages } from "./Message/MessageHelpers";
|
||||
import { initSingularitySFFlags, hasWallStreetSF } from "./NetscriptFunctions";
|
||||
import {
|
||||
WorkerScript,
|
||||
workerScripts,
|
||||
prestigeWorkerScripts
|
||||
} from "./NetscriptWorker";
|
||||
import { prestigeWorkerScripts } from "./NetscriptWorker";
|
||||
import { Player } from "./Player";
|
||||
|
||||
import {
|
||||
@ -45,10 +41,9 @@ import {
|
||||
SpecialServerNames
|
||||
} from "./Server/SpecialServerIps";
|
||||
import {
|
||||
deleteStockMarket,
|
||||
initStockMarket,
|
||||
initSymbolToStockMap,
|
||||
stockMarketContentCreated,
|
||||
setStockMarketContentCreated
|
||||
} from "./StockMarket/StockMarket";
|
||||
import { Terminal, postNetburnerText } from "./Terminal";
|
||||
|
||||
@ -102,7 +97,7 @@ function prestigeAugmentation() {
|
||||
}
|
||||
if (augmentationExists(AugmentationNames.CashRoot) &&
|
||||
Augmentations[AugmentationNames.CashRoot].owned) {
|
||||
Player.setMoney(new Decimal(1000000));
|
||||
Player.setMoney(1e6);
|
||||
homeComp.programs.push(Programs.BruteSSHProgram.name);
|
||||
}
|
||||
|
||||
@ -153,7 +148,7 @@ function prestigeAugmentation() {
|
||||
|
||||
// BitNode 8: Ghost of Wall Street
|
||||
if (Player.bitNodeN === 8) {Player.money = new Decimal(BitNode8StartingMoney);}
|
||||
if (Player.bitNodeN === 8 || hasWallStreetSF) {
|
||||
if (Player.bitNodeN === 8 || SourceFileFlags[8] > 0) {
|
||||
Player.hasWseAccount = true;
|
||||
Player.hasTixApiAccess = true;
|
||||
}
|
||||
@ -163,13 +158,6 @@ function prestigeAugmentation() {
|
||||
initStockMarket();
|
||||
initSymbolToStockMap();
|
||||
}
|
||||
setStockMarketContentCreated(false);
|
||||
var stockMarketList = document.getElementById("stock-market-list");
|
||||
while(stockMarketList.firstChild) {
|
||||
stockMarketList.removeChild(stockMarketList.firstChild);
|
||||
}
|
||||
var watchlist = document.getElementById("stock-market-watchlist-filter");
|
||||
watchlist.value = ""; // Reset watchlist filter
|
||||
|
||||
// Refresh Main Menu (the 'World' menu, specifically)
|
||||
document.getElementById("world-menu-header").click();
|
||||
@ -265,9 +253,6 @@ function prestigeSourceFile() {
|
||||
Terminal.resetTerminalInput();
|
||||
Engine.loadTerminalContent();
|
||||
|
||||
// Reinitialize Bit Node flags
|
||||
initSingularitySFFlags();
|
||||
|
||||
// BitNode 3: Corporatocracy
|
||||
if (Player.bitNodeN === 3) {
|
||||
homeComp.messages.push("corporation-management-handbook.lit");
|
||||
@ -321,7 +306,7 @@ function prestigeSourceFile() {
|
||||
|
||||
// BitNode 8: Ghost of Wall Street
|
||||
if (Player.bitNodeN === 8) {Player.money = new Decimal(BitNode8StartingMoney);}
|
||||
if (Player.bitNodeN === 8 || hasWallStreetSF) {
|
||||
if (Player.bitNodeN === 8 || SourceFileFlags[8] > 0) {
|
||||
Player.hasWseAccount = true;
|
||||
Player.hasTixApiAccess = true;
|
||||
}
|
||||
@ -335,11 +320,8 @@ function prestigeSourceFile() {
|
||||
if (Player.hasWseAccount) {
|
||||
initStockMarket();
|
||||
initSymbolToStockMap();
|
||||
}
|
||||
setStockMarketContentCreated(false);
|
||||
var stockMarketList = document.getElementById("stock-market-list");
|
||||
while(stockMarketList.firstChild) {
|
||||
stockMarketList.removeChild(stockMarketList.firstChild);
|
||||
} else {
|
||||
deleteStockMarket();
|
||||
}
|
||||
|
||||
if (Player.inGang()) { Player.gang.clearUI(); }
|
||||
@ -354,9 +336,9 @@ function prestigeSourceFile() {
|
||||
hserver.level = 100;
|
||||
hserver.cores = 10;
|
||||
hserver.cache = 5;
|
||||
hserver.updateHashRate(Player);
|
||||
hserver.updateHashRate(Player.hacknet_node_money_mult);
|
||||
hserver.updateHashCapacity();
|
||||
Player.hashManager.updateCapacity(Player);
|
||||
updateHashManagerCapacity();
|
||||
}
|
||||
|
||||
// Refresh Main Menu (the 'World' menu, specifically)
|
||||
|
@ -18,14 +18,15 @@ import {
|
||||
processHacknetEarnings
|
||||
} from "./Hacknet/HacknetHelpers";
|
||||
import { loadMessages, initMessages, Messages } from "./Message/MessageHelpers";
|
||||
import { loadAllRunningScripts } from "./NetscriptWorker";
|
||||
import { Player, loadPlayer } from "./Player";
|
||||
import { loadAllRunningScripts } from "./Script/ScriptHelpers";
|
||||
import { AllServers, loadAllServers } from "./Server/AllServers";
|
||||
import { Settings } from "./Settings/Settings";
|
||||
import {
|
||||
loadSpecialServerIps,
|
||||
SpecialServerIps
|
||||
} from "./Server/SpecialServerIps";
|
||||
import { SourceFileFlags } from "./SourceFile/SourceFileFlags";
|
||||
import { loadStockMarket, StockMarket } from "./StockMarket/StockMarket";
|
||||
|
||||
import { createStatusText } from "./ui/createStatusText";
|
||||
@ -204,27 +205,33 @@ function loadGame(saveString) {
|
||||
try {
|
||||
loadAliases(saveObj.AliasesSave);
|
||||
} catch(e) {
|
||||
console.warn(`Could not load Aliases from save`);
|
||||
loadAliases("");
|
||||
}
|
||||
} else {
|
||||
console.warn(`Save file did not contain an Aliases property`);
|
||||
loadAliases("");
|
||||
}
|
||||
if (saveObj.hasOwnProperty("GlobalAliasesSave")) {
|
||||
try {
|
||||
loadGlobalAliases(saveObj.GlobalAliasesSave);
|
||||
} catch(e) {
|
||||
console.warn(`Could not load GlobalAliases from save`);
|
||||
loadGlobalAliases("");
|
||||
}
|
||||
} else {
|
||||
console.warn(`Save file did not contain a GlobalAliases property`);
|
||||
loadGlobalAliases("");
|
||||
}
|
||||
if (saveObj.hasOwnProperty("MessagesSave")) {
|
||||
try {
|
||||
loadMessages(saveObj.MessagesSave);
|
||||
} catch(e) {
|
||||
console.warn(`Could not load Messages from save`);
|
||||
initMessages();
|
||||
}
|
||||
} else {
|
||||
console.warn(`Save file did not contain a Messages property`);
|
||||
initMessages();
|
||||
}
|
||||
if (saveObj.hasOwnProperty("StockMarketSave")) {
|
||||
@ -535,23 +542,12 @@ function loadImportedGame(saveObj, saveString) {
|
||||
}
|
||||
|
||||
BitburnerSaveObject.prototype.exportGame = function() {
|
||||
this.PlayerSave = JSON.stringify(Player);
|
||||
this.AllServersSave = JSON.stringify(AllServers);
|
||||
this.CompaniesSave = JSON.stringify(Companies);
|
||||
this.FactionsSave = JSON.stringify(Factions);
|
||||
this.SpecialServerIpsSave = JSON.stringify(SpecialServerIps);
|
||||
this.AliasesSave = JSON.stringify(Aliases);
|
||||
this.GlobalAliasesSave = JSON.stringify(GlobalAliases);
|
||||
this.MessagesSave = JSON.stringify(Messages);
|
||||
this.StockMarketSave = JSON.stringify(StockMarket);
|
||||
this.SettingsSave = JSON.stringify(Settings);
|
||||
this.VersionSave = JSON.stringify(CONSTANTS.Version);
|
||||
if (Player.inGang()) {
|
||||
this.AllGangsSave = JSON.stringify(AllGangs);
|
||||
}
|
||||
const saveString = this.getSaveString();
|
||||
|
||||
var saveString = btoa(unescape(encodeURIComponent(JSON.stringify(this))));
|
||||
var filename = "bitburnerSave.json";
|
||||
// Save file name is based on current timestamp and BitNode
|
||||
const epochTime = Math.round(Date.now() / 1000);
|
||||
const bn = Player.bitNodeN;
|
||||
const filename = `bitburnerSave_BN${bn}x${SourceFileFlags[bn]}_${epochTime}.json`;
|
||||
var file = new Blob([saveString], {type: 'text/plain'});
|
||||
if (window.navigator.msSaveOrOpenBlob) {// IE10+
|
||||
window.navigator.msSaveOrOpenBlob(file, filename);
|
||||
@ -559,7 +555,7 @@ BitburnerSaveObject.prototype.exportGame = function() {
|
||||
var a = document.createElement("a"),
|
||||
url = URL.createObjectURL(file);
|
||||
a.href = url;
|
||||
a.download = "bitburnerSave.json";
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
setTimeoutRef(function() {
|
||||
|
5
src/Script/RamCalculationErrorCodes.ts
Normal file
5
src/Script/RamCalculationErrorCodes.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export enum RamCalculationErrorCode {
|
||||
SyntaxError = -1,
|
||||
ImportError = -2,
|
||||
URLImportError = -3,
|
||||
}
|
4
src/Script/RamCalculations.d.ts
vendored
4
src/Script/RamCalculations.d.ts
vendored
@ -1 +1,3 @@
|
||||
export declare function calculateRamUsage(codeCopy: string): number;
|
||||
import { Script } from "./Script";
|
||||
|
||||
export declare function calculateRamUsage(codeCopy: string, otherScripts: Script[]): number;
|
||||
|
@ -1,10 +1,9 @@
|
||||
// Calculate a script's RAM usage
|
||||
const walk = require("acorn/dist/walk"); // Importing this doesn't work for some reason.
|
||||
import * as walk from "acorn-walk";
|
||||
|
||||
import { CONSTANTS } from "../Constants";
|
||||
import {evaluateImport} from "../NetscriptEvaluator";
|
||||
import { WorkerScript } from "../NetscriptWorker";
|
||||
import { Player } from "../Player";
|
||||
import { RamCalculationErrorCode } from "./RamCalculationErrorCodes";
|
||||
|
||||
import { RamCosts, RamCostConstants } from "../Netscript/RamCostGenerator";
|
||||
import { parse, Node } from "../../utils/acorn";
|
||||
|
||||
// These special strings are used to reference the presence of a given logical
|
||||
@ -19,7 +18,7 @@ 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) {
|
||||
async function parseOnlyRamCalculate(otherScripts, code, workerScript) {
|
||||
try {
|
||||
// Maps dependent identifiers to their dependencies.
|
||||
//
|
||||
@ -74,14 +73,27 @@ async function parseOnlyRamCalculate(server, code, workerScript) {
|
||||
}
|
||||
} catch(e) {
|
||||
console.error(`Error dynamically importing module from ${nextModule} for RAM calculations: ${e}`);
|
||||
return -1;
|
||||
return RamCalculationErrorCode.URLImportError;
|
||||
}
|
||||
} 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.
|
||||
if (!Array.isArray(otherScripts)) {
|
||||
console.warn(`parseOnlyRamCalculate() not called with array of scripts`);
|
||||
return RamCalculationErrorCode.ImportError;
|
||||
}
|
||||
|
||||
let script = null;
|
||||
let fn = nextModule.startsWith("./") ? nextModule.slice(2) : nextModule;
|
||||
for (const s of otherScripts) {
|
||||
if (s.filename === fn) {
|
||||
script = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (script == null) {
|
||||
return RamCalculationErrorCode.ImportError; // No such script on the server
|
||||
}
|
||||
|
||||
code = script.code;
|
||||
}
|
||||
|
||||
@ -90,7 +102,7 @@ async function parseOnlyRamCalculate(server, code, workerScript) {
|
||||
|
||||
// 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;
|
||||
let ram = RamCostConstants.ScriptBaseRamCost;
|
||||
const unresolvedRefs = Object.keys(dependencyMap).filter(s => s.startsWith(initialModule));
|
||||
const resolvedRefs = new Set();
|
||||
while (unresolvedRefs.length > 0) {
|
||||
@ -98,13 +110,13 @@ async function parseOnlyRamCalculate(server, code, workerScript) {
|
||||
|
||||
// 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;
|
||||
ram += RamCostConstants.ScriptHacknetNodesRamCost;
|
||||
}
|
||||
if (ref === "document" && !resolvedRefs.has("document")) {
|
||||
ram += CONSTANTS.ScriptDomRamCost;
|
||||
ram += RamCostConstants.ScriptDomRamCost;
|
||||
}
|
||||
if (ref === "window" && !resolvedRefs.has("window")) {
|
||||
ram += CONSTANTS.ScriptDomRamCost;
|
||||
ram += RamCostConstants.ScriptDomRamCost;
|
||||
}
|
||||
|
||||
resolvedRefs.add(ref);
|
||||
@ -124,9 +136,8 @@ async function parseOnlyRamCalculate(server, code, workerScript) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Check if this identifier is a function in the workerscript env.
|
||||
// If it is, then we need to get its RAM cost.
|
||||
//
|
||||
// TODO it would be simpler to just reference a dictionary.
|
||||
try {
|
||||
@ -152,8 +163,15 @@ async function parseOnlyRamCalculate(server, code, workerScript) {
|
||||
}
|
||||
}
|
||||
|
||||
//Special logic for namespaces (Bladeburner, CodingCOntract)
|
||||
var func;
|
||||
// Only count each function once
|
||||
if (workerScript.loadedFns[ref]) {
|
||||
continue;
|
||||
} else {
|
||||
workerScript.loadedFns[ref] = true;
|
||||
}
|
||||
|
||||
// This accounts for namespaces (Bladeburner, CodingCOntract)
|
||||
let func;
|
||||
if (ref in workerScript.env.vars.bladeburner) {
|
||||
func = workerScript.env.vars.bladeburner[ref];
|
||||
} else if (ref in workerScript.env.vars.codingcontract) {
|
||||
@ -163,7 +181,7 @@ async function parseOnlyRamCalculate(server, code, workerScript) {
|
||||
} else if (ref in workerScript.env.vars.sleeve) {
|
||||
func = workerScript.env.vars.sleeve[ref];
|
||||
} else {
|
||||
func = workerScript.env.get(ref);
|
||||
func = workerScript.env.vars[ref];
|
||||
}
|
||||
ram += applyFuncRam(func);
|
||||
} catch (error) {continue;}
|
||||
@ -174,7 +192,7 @@ async function parseOnlyRamCalculate(server, code, workerScript) {
|
||||
// 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;
|
||||
return RamCalculationErrorCode.SyntaxError;
|
||||
}
|
||||
}
|
||||
|
||||
@ -248,36 +266,6 @@ function parseOnlyCalculateDeps(code, currentModule) {
|
||||
}
|
||||
}
|
||||
|
||||
//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;
|
||||
@ -308,104 +296,23 @@ function parseOnlyCalculateDeps(code, currentModule) {
|
||||
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;
|
||||
export async function calculateRamUsage(codeCopy, otherScripts) {
|
||||
// We don't need a real WorkerScript for this. Just an object that keeps
|
||||
// track of whatever's needed for RAM calculations
|
||||
const workerScript = {
|
||||
loadedFns: {},
|
||||
env: {
|
||||
vars: RamCosts,
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return await parseOnlyRamCalculate(currServ, codeCopy, workerScript);
|
||||
return await parseOnlyRamCalculate(otherScripts, codeCopy, workerScript);
|
||||
} catch (e) {
|
||||
console.log("Failed to parse ram using new method. Falling back.", e);
|
||||
console.error(`Failed to parse script for RAM calculations:`);
|
||||
console.error(e);
|
||||
return RamCalculationErrorCode.SyntaxError;
|
||||
}
|
||||
|
||||
// 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;
|
||||
return RamCalculationErrorCode.SyntaxError;
|
||||
}
|
||||
|
@ -2,14 +2,15 @@
|
||||
// A Script can have multiple active instances
|
||||
import { Script } from "./Script";
|
||||
import { FconfSettings } from "../Fconf/FconfSettings";
|
||||
import { AllServers } from "../Server/AllServers";
|
||||
import { Settings } from "../Settings/Settings";
|
||||
import { IMap } from "../types";
|
||||
import { post } from "../ui/postToTerminal";
|
||||
|
||||
import { Generic_fromJSON,
|
||||
import {
|
||||
Generic_fromJSON,
|
||||
Generic_toJSON,
|
||||
Reviver } from "../../utils/JSONReviver";
|
||||
Reviver
|
||||
} from "../../utils/JSONReviver";
|
||||
import { getTimestamp } from "../../utils/helpers/getTimestamp";
|
||||
|
||||
export class RunningScript {
|
||||
@ -73,35 +74,6 @@ export class RunningScript {
|
||||
this.ramUsage = script.ramUsage;
|
||||
}
|
||||
|
||||
getCode(): string {
|
||||
const server = AllServers[this.server];
|
||||
if (server == null) { return ""; }
|
||||
for (let i = 0; i < server.scripts.length; ++i) {
|
||||
if (server.scripts[i].filename === this.filename) {
|
||||
return server.scripts[i].code;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
getRamUsage(): number {
|
||||
if (this.ramUsage != null && this.ramUsage > 0) { return this.ramUsage; } // Use cached value
|
||||
|
||||
const server = AllServers[this.server];
|
||||
if (server == null) { return 0; }
|
||||
for (let i = 0; i < server.scripts.length; ++i) {
|
||||
if (server.scripts[i].filename === this.filename) {
|
||||
// Cache the ram usage for the next call
|
||||
this.ramUsage = server.scripts[i].ramUsage;
|
||||
return this.ramUsage;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
log(txt: string): void {
|
||||
if (this.logs.length > Settings.MaxLogCapacity) {
|
||||
//Delete first element and add new log entry to the end.
|
||||
|
20
src/Script/RunningScriptHelpers.ts
Normal file
20
src/Script/RunningScriptHelpers.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { AllServers } from "../Server/AllServers";
|
||||
import { RunningScript } from "./RunningScript";
|
||||
|
||||
export function getRamUsageFromRunningScript(script: RunningScript): number {
|
||||
if (script.ramUsage != null && script.ramUsage > 0) {
|
||||
return script.ramUsage; // Use cached value
|
||||
}
|
||||
|
||||
const server = AllServers[script.server];
|
||||
if (server == null) { return 0; }
|
||||
for (let i = 0; i < server.scripts.length; ++i) {
|
||||
if (server.scripts[i].filename === script.filename) {
|
||||
// Cache the ram usage for the next call
|
||||
script.ramUsage = server.scripts[i].ramUsage;
|
||||
return script.ramUsage;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
@ -2,14 +2,14 @@
|
||||
// This does NOT represent a script that is actively running and
|
||||
// being evaluated. See RunningScript for that
|
||||
import { calculateRamUsage } from "./RamCalculations";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import { Page,
|
||||
routing } from "../ui/navigationTracking";
|
||||
import { Page, routing } from "../ui/navigationTracking";
|
||||
|
||||
import { setTimeoutRef } from "../utils/SetTimeoutRef";
|
||||
import { Generic_fromJSON,
|
||||
import {
|
||||
Generic_fromJSON,
|
||||
Generic_toJSON,
|
||||
Reviver } from "../../utils/JSONReviver";
|
||||
Reviver
|
||||
} from "../../utils/JSONReviver";
|
||||
import { roundToTwo } from "../../utils/helpers/roundToTwo";
|
||||
|
||||
export class Script {
|
||||
@ -35,13 +35,13 @@ export class Script {
|
||||
server: string = "";
|
||||
|
||||
|
||||
constructor(fn: string = "", code: string = "", server: string = "") {
|
||||
constructor(fn: string="", code: string="", server: string="", otherScripts: Script[]=[]) {
|
||||
this.filename = fn;
|
||||
this.code = code;
|
||||
this.ramUsage = 0;
|
||||
this.server = server; // IP of server this script is on
|
||||
this.module = "";
|
||||
if (this.code !== "") {this.updateRamUsage();}
|
||||
if (this.code !== "") { this.updateRamUsage(otherScripts); }
|
||||
};
|
||||
|
||||
download(): void {
|
||||
@ -64,7 +64,7 @@ export class Script {
|
||||
}
|
||||
|
||||
// Save a script FROM THE SCRIPT EDITOR
|
||||
saveScript(code: string, p: IPlayer): void {
|
||||
saveScript(code: string, serverIp: string, otherScripts: Script[]): void {
|
||||
if (routing.isOn(Page.ScriptEditor)) {
|
||||
//Update code and filename
|
||||
this.code = code.replace(/^\s+|\s+$/g, '');
|
||||
@ -77,22 +77,19 @@ export class Script {
|
||||
this.filename = filenameElem!.value;
|
||||
|
||||
// Server
|
||||
this.server = p.currentServer;
|
||||
this.server = serverIp;
|
||||
|
||||
//Calculate/update ram usage, execution time, etc.
|
||||
this.updateRamUsage();
|
||||
this.updateRamUsage(otherScripts);
|
||||
|
||||
this.module = "";
|
||||
}
|
||||
}
|
||||
|
||||
// Updates the script's RAM usage based on its code
|
||||
async updateRamUsage() {
|
||||
// TODO Commented this out because I think its unnecessary
|
||||
// DOuble check/Test
|
||||
// var codeCopy = this.code.repeat(1);
|
||||
var res = await calculateRamUsage(this.code);
|
||||
if (res !== -1) {
|
||||
async updateRamUsage(otherScripts: Script[]) {
|
||||
var res = await calculateRamUsage(this.code, otherScripts);
|
||||
if (res > 0) {
|
||||
this.ramUsage = roundToTwo(res);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Script } from "./Script";
|
||||
|
||||
import { RamCalculationErrorCode } from "./RamCalculationErrorCodes";
|
||||
import { calculateRamUsage } from "./RamCalculations";
|
||||
import { isScriptFilename } from "./ScriptHelpersTS";
|
||||
|
||||
@ -7,9 +8,11 @@ import {CONSTANTS} from "../Constants";
|
||||
import {Engine} from "../engine";
|
||||
import { parseFconfSettings } from "../Fconf/Fconf";
|
||||
import { FconfSettings } from "../Fconf/FconfSettings";
|
||||
import {iTutorialSteps, iTutorialNextStep,
|
||||
ITutorial} from "../InteractiveTutorial";
|
||||
import { addWorkerScript } from "../NetscriptWorker";
|
||||
import {
|
||||
iTutorialSteps,
|
||||
iTutorialNextStep,
|
||||
ITutorial
|
||||
} from "../InteractiveTutorial";
|
||||
import { Player } from "../Player";
|
||||
import { AceEditor } from "../ScriptEditor/Ace";
|
||||
import { CodeMirrorEditor } from "../ScriptEditor/CodeMirror";
|
||||
@ -24,8 +27,11 @@ import {Page, routing} from "../ui/navigationTracking";
|
||||
import { numeralWrapper } from "../ui/numeralFormat";
|
||||
|
||||
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||
import {Reviver, Generic_toJSON,
|
||||
Generic_fromJSON} from "../../utils/JSONReviver";
|
||||
import {
|
||||
Reviver,
|
||||
Generic_toJSON,
|
||||
Generic_fromJSON
|
||||
} from "../../utils/JSONReviver";
|
||||
import { compareArrays } from "../../utils/helpers/compareArrays";
|
||||
import { createElement } from "../../utils/uiHelpers/createElement";
|
||||
|
||||
@ -127,20 +133,22 @@ export function scriptEditorInit() {
|
||||
editorSelector.onchange = () => {
|
||||
const opt = editorSelector.value;
|
||||
switch (opt) {
|
||||
case EditorSetting.Ace:
|
||||
case EditorSetting.Ace: {
|
||||
const codeMirrorCode = CodeMirrorEditor.getCode();
|
||||
const codeMirrorFn = CodeMirrorEditor.getFilename();
|
||||
AceEditor.create();
|
||||
CodeMirrorEditor.setInvisible();
|
||||
AceEditor.openScript(codeMirrorFn, codeMirrorCode);
|
||||
break;
|
||||
case EditorSetting.CodeMirror:
|
||||
}
|
||||
case EditorSetting.CodeMirror: {
|
||||
const aceCode = AceEditor.getCode();
|
||||
const aceFn = AceEditor.getFilename();
|
||||
CodeMirrorEditor.create();
|
||||
AceEditor.setInvisible();
|
||||
CodeMirrorEditor.openScript(aceFn, aceCode);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.error(`Unrecognized Editor Setting: ${opt}`);
|
||||
return;
|
||||
@ -182,11 +190,23 @@ export async function updateScriptEditorContent() {
|
||||
}
|
||||
|
||||
var codeCopy = code.repeat(1);
|
||||
var ramUsage = await calculateRamUsage(codeCopy);
|
||||
if (ramUsage !== -1) {
|
||||
var ramUsage = await calculateRamUsage(codeCopy, Player.getCurrentServer().scripts);
|
||||
if (ramUsage > 0) {
|
||||
scriptEditorRamText.innerText = "RAM: " + numeralWrapper.format(ramUsage, '0.00') + " GB";
|
||||
} else {
|
||||
switch (ramUsage) {
|
||||
case RamCalculationErrorCode.ImportError:
|
||||
scriptEditorRamText.innerText = "RAM: Import Error";
|
||||
break;
|
||||
case RamCalculationErrorCode.URLImportError:
|
||||
scriptEditorRamText.innerText = "RAM: HTTP Import Error";
|
||||
break;
|
||||
case RamCalculationErrorCode.SyntaxError:
|
||||
default:
|
||||
scriptEditorRamText.innerText = "RAM: Syntax Error";
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -229,7 +249,7 @@ function saveAndCloseScriptEditor() {
|
||||
let s = Player.getCurrentServer();
|
||||
for (var i = 0; i < s.scripts.length; i++) {
|
||||
if (filename == s.scripts[i].filename) {
|
||||
s.scripts[i].saveScript(getCurrentEditor().getCode(), Player);
|
||||
s.scripts[i].saveScript(getCurrentEditor().getCode(), Player.currentServer, Player.getCurrentServer().scripts);
|
||||
Engine.loadTerminalContent();
|
||||
return iTutorialNextStep();
|
||||
}
|
||||
@ -237,7 +257,7 @@ function saveAndCloseScriptEditor() {
|
||||
|
||||
// If the current script does NOT exist, create a new one
|
||||
let script = new Script();
|
||||
script.saveScript(getCurrentEditor().getCode(), Player);
|
||||
script.saveScript(getCurrentEditor().getCode(), Player.currentServer, Player.getCurrentServer().scripts);
|
||||
s.scripts.push(script);
|
||||
|
||||
return iTutorialNextStep();
|
||||
@ -265,7 +285,7 @@ function saveAndCloseScriptEditor() {
|
||||
//If the current script already exists on the server, overwrite it
|
||||
for (var i = 0; i < s.scripts.length; i++) {
|
||||
if (filename == s.scripts[i].filename) {
|
||||
s.scripts[i].saveScript(getCurrentEditor().getCode(), Player);
|
||||
s.scripts[i].saveScript(getCurrentEditor().getCode(), Player.currentServer, Player.getCurrentServer().scripts);
|
||||
Engine.loadTerminalContent();
|
||||
return;
|
||||
}
|
||||
@ -273,7 +293,7 @@ function saveAndCloseScriptEditor() {
|
||||
|
||||
//If the current script does NOT exist, create a new one
|
||||
const script = new Script();
|
||||
script.saveScript(getCurrentEditor().getCode(), Player);
|
||||
script.saveScript(getCurrentEditor().getCode(), Player.currentServer, Player.getCurrentServer().scripts);
|
||||
s.scripts.push(script);
|
||||
} else if (filename.endsWith(".txt")) {
|
||||
for (var i = 0; i < s.textFiles.length; ++i) {
|
||||
@ -293,42 +313,7 @@ function saveAndCloseScriptEditor() {
|
||||
Engine.loadTerminalContent();
|
||||
}
|
||||
|
||||
//Called when the game is loaded. Loads all running scripts (from all servers)
|
||||
//into worker scripts so that they will start running
|
||||
export function loadAllRunningScripts() {
|
||||
var total = 0;
|
||||
let skipScriptLoad = (window.location.href.toLowerCase().indexOf("?noscripts") !== -1);
|
||||
if (skipScriptLoad) { console.info("Skipping the load of any scripts during startup"); }
|
||||
for (var property in AllServers) {
|
||||
if (AllServers.hasOwnProperty(property)) {
|
||||
var server = AllServers[property];
|
||||
|
||||
//Reset each server's RAM usage to 0
|
||||
server.ramUsed = 0;
|
||||
|
||||
//Reset modules on all scripts
|
||||
for (var i = 0; i < server.scripts.length; ++i) {
|
||||
server.scripts[i].module = "";
|
||||
}
|
||||
|
||||
if (skipScriptLoad) {
|
||||
//Start game with no scripts
|
||||
server.runningScripts.length = 0;
|
||||
} else {
|
||||
for (var j = 0; j < server.runningScripts.length; ++j) {
|
||||
addWorkerScript(server.runningScripts[j], server);
|
||||
|
||||
//Offline production
|
||||
total += scriptCalculateOfflineProduction(server.runningScripts[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
function scriptCalculateOfflineProduction(runningScriptObj) {
|
||||
export function scriptCalculateOfflineProduction(runningScriptObj) {
|
||||
//The Player object stores the last update time from when we were online
|
||||
var thisUpdate = new Date().getTime();
|
||||
var lastUpdate = Player.lastUpdate;
|
||||
@ -424,7 +409,7 @@ function scriptCalculateOfflineProduction(runningScriptObj) {
|
||||
//designated server, and false otherwise
|
||||
export function findRunningScript(filename, args, server) {
|
||||
for (var i = 0; i < server.runningScripts.length; ++i) {
|
||||
if (server.runningScripts[i].filename == filename &&
|
||||
if (server.runningScripts[i].filename === filename &&
|
||||
compareArrays(server.runningScripts[i].args, args)) {
|
||||
return server.runningScripts[i];
|
||||
}
|
||||
|
@ -95,6 +95,7 @@ let NetscriptFunctions =
|
||||
|
||||
// TIX API
|
||||
"getStockPrice|getStockPosition|getStockSymbols|getStockMaxShares|" +
|
||||
"getStockAskPrice|getStockBidPrice|getStockPurchaseCost|getStockSaleGain|" +
|
||||
"buyStock|sellStock|shortStock|sellShort|" +
|
||||
"placeOrder|cancelOrder|getOrders|getStockVolatility|getStockForecast|" +
|
||||
"purchase4SMarketData|purchase4SMarketDataTixApi|" +
|
||||
|
@ -60,8 +60,6 @@ import 'codemirror/theme/xq-light.css';
|
||||
import 'codemirror/theme/yeti.css';
|
||||
import 'codemirror/theme/zenburn.css';
|
||||
|
||||
import "../../css/codemirror-overrides.scss";
|
||||
|
||||
import CodeMirror from "codemirror/lib/codemirror.js";
|
||||
import "codemirror/mode/javascript/javascript.js";
|
||||
import "./CodeMirrorNetscriptMode";
|
||||
|
@ -153,9 +153,13 @@ CodeMirror.defineMode("netscript", function(config, parserConfig) {
|
||||
|
||||
// Netscript TIX API
|
||||
"getStockPrice": atom,
|
||||
"getStockAskPrice": atom,
|
||||
"getStockBidPrice": atom,
|
||||
"getStockPosition": atom,
|
||||
"getStockSymbols": atom,
|
||||
"getStockMaxShares": atom,
|
||||
"getStockPurchaseCost": atom,
|
||||
"getStockSaleGain": atom,
|
||||
"buyStock": atom,
|
||||
"sellStock": atom,
|
||||
"shortStock": atom,
|
||||
|
@ -5,25 +5,42 @@ import { serverMetadata } from "./data/servers";
|
||||
import { HacknetServer } from "../Hacknet/HacknetServer";
|
||||
|
||||
import { IMap } from "../types";
|
||||
import { createRandomIp,
|
||||
ipExists } from "../../utils/IPAddress";
|
||||
import { createRandomIp } from "../../utils/IPAddress";
|
||||
import { getRandomInt } from "../../utils/helpers/getRandomInt";
|
||||
import { Reviver } from "../../utils/JSONReviver";
|
||||
|
||||
// Map of all Servers that exist in the game
|
||||
// Key (string) = IP
|
||||
// Value = Server object
|
||||
/**
|
||||
* Map of all Servers that exist in the game
|
||||
* Key (string) = IP
|
||||
* Value = Server object
|
||||
*/
|
||||
export let AllServers: IMap<Server | HacknetServer> = {};
|
||||
|
||||
export function ipExists(ip: string) {
|
||||
return (AllServers[ip] != null);
|
||||
}
|
||||
|
||||
export function createUniqueRandomIp(): string {
|
||||
const ip = createRandomIp();
|
||||
|
||||
// If the Ip already exists, recurse to create a new one
|
||||
if (ipExists(ip)) {
|
||||
return createRandomIp();
|
||||
}
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
// Saftely add a Server to the AllServers map
|
||||
export function AddToAllServers(server: Server | HacknetServer): void {
|
||||
var serverIp = server.ip;
|
||||
if (ipExists(serverIp)) {
|
||||
console.log("IP of server that's being added: " + serverIp);
|
||||
console.log("Hostname of the server thats being added: " + server.hostname);
|
||||
console.log("The server that already has this IP is: " + AllServers[serverIp].hostname);
|
||||
console.warn(`IP of server that's being added: ${serverIp}`);
|
||||
console.warn(`Hostname of the server thats being added: ${server.hostname}`);
|
||||
console.warn(`The server that already has this IP is: ${AllServers[serverIp].hostname}`);
|
||||
throw new Error("Error: Trying to add a server with an existing IP");
|
||||
}
|
||||
|
||||
AllServers[serverIp] = server;
|
||||
}
|
||||
|
||||
@ -71,7 +88,7 @@ export function initForeignServers(homeComputer: Server) {
|
||||
for (const metadata of serverMetadata) {
|
||||
const serverParams: IServerParams = {
|
||||
hostname: metadata.hostname,
|
||||
ip: createRandomIp(),
|
||||
ip: createUniqueRandomIp(),
|
||||
numOpenPortsRequired: metadata.numOpenPortsRequired,
|
||||
organizationName: metadata.organizationName
|
||||
};
|
||||
|
@ -5,6 +5,7 @@ import { CodingContract } from "../CodingContracts";
|
||||
import { Message } from "../Message/Message";
|
||||
import { RunningScript } from "../Script/RunningScript";
|
||||
import { Script } from "../Script/Script";
|
||||
import { isValidFilePath } from "../Terminal/DirectoryHelpers";
|
||||
import { TextFile } from "../TextFile";
|
||||
import { IReturnStatus } from "../types";
|
||||
|
||||
@ -224,14 +225,14 @@ export class BaseServer {
|
||||
*/
|
||||
writeToScriptFile(fn: string, code: string) {
|
||||
var ret = { success: false, overwritten: false };
|
||||
if (!isScriptFilename(fn)) { return ret; }
|
||||
if (!isValidFilePath(fn) || !isScriptFilename(fn)) { return ret; }
|
||||
|
||||
// Check if the script already exists, and overwrite it if it does
|
||||
for (let i = 0; i < this.scripts.length; ++i) {
|
||||
if (fn === this.scripts[i].filename) {
|
||||
let script = this.scripts[i];
|
||||
script.code = code;
|
||||
script.updateRamUsage();
|
||||
script.updateRamUsage(this.scripts);
|
||||
script.module = "";
|
||||
ret.overwritten = true;
|
||||
ret.success = true;
|
||||
@ -240,11 +241,7 @@ export class BaseServer {
|
||||
}
|
||||
|
||||
// Otherwise, create a new script
|
||||
const newScript = new Script();
|
||||
newScript.filename = fn;
|
||||
newScript.code = code;
|
||||
newScript.updateRamUsage();
|
||||
newScript.server = this.ip;
|
||||
const newScript = new Script(fn, code, this.ip, this.scripts);
|
||||
this.scripts.push(newScript);
|
||||
ret.success = true;
|
||||
return ret;
|
||||
@ -254,7 +251,7 @@ export class BaseServer {
|
||||
// Overwrites existing files. Creates new files if the text file does not exist
|
||||
writeToTextFile(fn: string, txt: string) {
|
||||
var ret = { success: false, overwritten: false };
|
||||
if (!fn.endsWith("txt")) { return ret; }
|
||||
if (!isValidFilePath(fn) || !fn.endsWith("txt")) { return ret; }
|
||||
|
||||
// Check if the text file already exists, and overwrite if it does
|
||||
for (let i = 0; i < this.textFiles.length; ++i) {
|
||||
|
@ -1,9 +1,6 @@
|
||||
// Class representing a single hackable Server
|
||||
import { BaseServer } from "./BaseServer";
|
||||
|
||||
// TODO This import is a circular import. Try to fix it in the future
|
||||
import { GetServerByHostname } from "./ServerHelpers";
|
||||
|
||||
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
|
||||
|
||||
import { createRandomString } from "../utils/helpers/createRandomString";
|
||||
@ -12,7 +9,7 @@ import { Generic_fromJSON,
|
||||
Generic_toJSON,
|
||||
Reviver } from "../../utils/JSONReviver";
|
||||
|
||||
interface IConstructorParams {
|
||||
export interface IConstructorParams {
|
||||
adminRights?: boolean;
|
||||
hackDifficulty?: number;
|
||||
hostname: string;
|
||||
@ -77,17 +74,6 @@ export class Server extends BaseServer {
|
||||
this.hostname = createRandomString(10);
|
||||
}
|
||||
|
||||
// Validate hostname by ensuring there are no repeats
|
||||
if (GetServerByHostname(this.hostname) != null) {
|
||||
// Use a for loop to ensure that we don't get suck in an infinite loop somehow
|
||||
let hostname: string = this.hostname;
|
||||
for (let i = 0; i < 200; ++i) {
|
||||
hostname = `${this.hostname}-${i}`;
|
||||
if (GetServerByHostname(hostname) == null) { break; }
|
||||
}
|
||||
this.hostname = hostname;
|
||||
}
|
||||
|
||||
this.purchasedByPlayer = params.purchasedByPlayer != null ? params.purchasedByPlayer : false;
|
||||
|
||||
//RAM, CPU speed and Scripts
|
||||
|
@ -1,5 +1,9 @@
|
||||
import { AllServers } from "./AllServers";
|
||||
import { Server } from "./Server";
|
||||
import {
|
||||
AllServers,
|
||||
createUniqueRandomIp,
|
||||
ipExists,
|
||||
} from "./AllServers";
|
||||
import { Server, IConstructorParams } from "./Server";
|
||||
|
||||
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
|
||||
import { CONSTANTS } from "../Constants";
|
||||
@ -9,6 +13,28 @@ import { Programs } from "../Programs/Programs";
|
||||
|
||||
import { isValidIPAddress } from "../../utils/helpers/isValidIPAddress";
|
||||
|
||||
/**
|
||||
* Constructs a new server, while also ensuring that the new server
|
||||
* does not have a duplicate hostname/ip.
|
||||
*/
|
||||
export function safetlyCreateUniqueServer(params: IConstructorParams): Server {
|
||||
if (params.ip != null && ipExists(params.ip)) {
|
||||
params.ip = createUniqueRandomIp();
|
||||
}
|
||||
|
||||
if (GetServerByHostname(params.hostname) != null) {
|
||||
// Use a for loop to ensure that we don't get suck in an infinite loop somehow
|
||||
let hostname: string = params.hostname;
|
||||
for (let i = 0; i < 200; ++i) {
|
||||
hostname = `${params.hostname}-${i}`;
|
||||
if (GetServerByHostname(hostname) == null) { break; }
|
||||
}
|
||||
params.hostname = hostname;
|
||||
}
|
||||
|
||||
return new Server(params);
|
||||
}
|
||||
|
||||
// Returns the number of cycles needed to grow the specified server by the
|
||||
// specified amount. 'growth' parameter is in decimal form, not percentage
|
||||
export function numCycleForGrowth(server: Server, growth: number, p: IPlayer) {
|
||||
@ -81,7 +107,7 @@ export function prestigeHomeComputer(homeComp: Server) {
|
||||
|
||||
//Update RAM usage on all scripts
|
||||
homeComp.scripts.forEach(function(script) {
|
||||
script.updateRamUsage();
|
||||
script.updateRamUsage(homeComp.scripts);
|
||||
});
|
||||
|
||||
homeComp.messages.length = 0; //Remove .lit and .msg files
|
||||
|
@ -2,13 +2,17 @@
|
||||
* Implements functions for purchasing servers or purchasing more RAM for
|
||||
* the home computer
|
||||
*/
|
||||
import {
|
||||
AddToAllServers,
|
||||
createUniqueRandomIp,
|
||||
} from "./AllServers";
|
||||
import { safetlyCreateUniqueServer } from "./ServerHelpers";
|
||||
|
||||
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
|
||||
import { CONSTANTS } from "../Constants";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import { AddToAllServers } from "../Server/AllServers";
|
||||
import { Server } from "../Server/Server";
|
||||
|
||||
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||
import { createRandomIp } from "../../utils/IPAddress";
|
||||
import { yesNoTxtInpBoxGetInput } from "../../utils/YesNoBox";
|
||||
import { isPowerOfTwo } from "../../utils/helpers/isPowerOfTwo";
|
||||
|
||||
@ -67,9 +71,14 @@ export function purchaseServer(ram: number, p: IPlayer) {
|
||||
}
|
||||
|
||||
// Create server
|
||||
var newServ = new Server({
|
||||
ip:createRandomIp(), hostname:hostname, organizationName:"",
|
||||
isConnectedTo:false, adminRights:true, purchasedByPlayer:true, maxRam:ram
|
||||
const newServ = safetlyCreateUniqueServer({
|
||||
adminRights: true,
|
||||
hostname: hostname,
|
||||
ip: createUniqueRandomIp(),
|
||||
isConnectedTo: false,
|
||||
maxRam:ram,
|
||||
organizationName: "",
|
||||
purchasedByPlayer: true,
|
||||
});
|
||||
AddToAllServers(newServ);
|
||||
|
||||
|
@ -1,23 +1,7 @@
|
||||
// tslint:disable:max-file-line-count
|
||||
|
||||
// This could actually be a JSON file as it should be constant metadata to be imported...
|
||||
|
||||
/**
|
||||
* Defines the minimum and maximum values for a range.
|
||||
* It is up to the consumer if these values are inclusive or exclusive.
|
||||
* It is up to the implementor to ensure max > min.
|
||||
*/
|
||||
interface IMinMaxRange {
|
||||
/**
|
||||
* The maximum bound of the range.
|
||||
*/
|
||||
max: number;
|
||||
|
||||
/**
|
||||
* The minimum bound of the range.
|
||||
*/
|
||||
min: number;
|
||||
}
|
||||
import { IMinMaxRange } from "../../types";
|
||||
|
||||
/**
|
||||
* The metadata describing the base state of servers on the network.
|
||||
|
@ -4,7 +4,7 @@
|
||||
import { CONSTANTS } from "../Constants";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
|
||||
export const SourceFileFlags: number[] = Array(CONSTANTS.TotalNumBitNodes + 1); // Skip 0
|
||||
export const SourceFileFlags: number[] = Array(CONSTANTS.TotalNumBitNodes + 1); // Skip index 0
|
||||
|
||||
export function updateSourceFileFlags(p: IPlayer) {
|
||||
for (let i = 0; i < SourceFileFlags.length; ++i) {
|
||||
|
296
src/StockMarket/BuyingAndSelling.ts
Normal file
296
src/StockMarket/BuyingAndSelling.ts
Normal file
@ -0,0 +1,296 @@
|
||||
/**
|
||||
* Functions for buying/selling stocks. There are four functions total, two for
|
||||
* long positions and two for short positions.
|
||||
*/
|
||||
import { Stock } from "./Stock";
|
||||
import {
|
||||
getBuyTransactionCost,
|
||||
getSellTransactionGain,
|
||||
processBuyTransactionPriceMovement,
|
||||
processSellTransactionPriceMovement
|
||||
} from "./StockMarketHelpers";
|
||||
|
||||
import { PositionTypes } from "./data/PositionTypes";
|
||||
|
||||
import { CONSTANTS } from "../Constants";
|
||||
import { WorkerScript } from "../Netscript/WorkerScript";
|
||||
import { Player } from "../Player";
|
||||
|
||||
import { numeralWrapper } from "../ui/numeralFormat";
|
||||
|
||||
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||
|
||||
/**
|
||||
* Each function takes an optional config object as its last argument
|
||||
*/
|
||||
interface IOptions {
|
||||
rerenderFn?: () => void;
|
||||
suppressDialog?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to buy a stock in the long position
|
||||
* @param {Stock} stock - Stock to buy
|
||||
* @param {number} shares - Number of shares to buy
|
||||
* @param {WorkerScript} workerScript - If this is being called through Netscript
|
||||
* @param opts - Optional configuration for this function's behavior. See top of file
|
||||
* @returns {boolean} - true if successful, false otherwise
|
||||
*/
|
||||
export function buyStock(stock: Stock, shares: number, workerScript: WorkerScript | null=null, opts: IOptions={}): boolean {
|
||||
const tixApi = (workerScript instanceof WorkerScript);
|
||||
|
||||
// Validate arguments
|
||||
shares = Math.round(shares);
|
||||
if (shares <= 0) { return false; }
|
||||
if (stock == null || isNaN(shares)) {
|
||||
if (tixApi) {
|
||||
workerScript!.log(`ERROR: buyStock() failed due to invalid arguments`);
|
||||
} else if (opts.suppressDialog !== true) {
|
||||
dialogBoxCreate("Failed to buy stock. This may be a bug, contact developer");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Does player have enough money?
|
||||
const totalPrice = getBuyTransactionCost(stock, shares, PositionTypes.Long);
|
||||
if (totalPrice == null) { return false; }
|
||||
if (Player.money.lt(totalPrice)) {
|
||||
if (tixApi) {
|
||||
workerScript!.log(`ERROR: buyStock() failed because you do not have enough money to purchase this potiion. You need ${numeralWrapper.formatMoney(totalPrice)}`);
|
||||
} else if (opts.suppressDialog !== true) {
|
||||
dialogBoxCreate(`You do not have enough money to purchase this. You need ${numeralWrapper.formatMoney(totalPrice)}`);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Would this purchase exceed the maximum number of shares?
|
||||
if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) {
|
||||
if (tixApi) {
|
||||
workerScript!.log(`ERROR: buyStock() failed because purchasing this many shares would exceed ${stock.symbol}'s maximum number of shares`);
|
||||
} else if (opts.suppressDialog !== true) {
|
||||
dialogBoxCreate(`You cannot purchase this many shares. ${stock.symbol} has a maximum of ${numeralWrapper.formatBigNumber(stock.maxShares)} shares.`);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const origTotal = stock.playerShares * stock.playerAvgPx;
|
||||
Player.loseMoney(totalPrice);
|
||||
const newTotal = origTotal + totalPrice - CONSTANTS.StockMarketCommission;
|
||||
stock.playerShares = Math.round(stock.playerShares + shares);
|
||||
stock.playerAvgPx = newTotal / stock.playerShares;
|
||||
processBuyTransactionPriceMovement(stock, shares, PositionTypes.Long);
|
||||
if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") {
|
||||
opts.rerenderFn();
|
||||
}
|
||||
|
||||
const resultTxt = `Bought ${numeralWrapper.format(shares, '0,0')} shares of ${stock.symbol} for ${numeralWrapper.formatMoney(totalPrice)}. ` +
|
||||
`Paid ${numeralWrapper.formatMoney(CONSTANTS.StockMarketCommission)} in commission fees.`
|
||||
if (tixApi) {
|
||||
if (workerScript!.shouldLog("buyStock")) { workerScript!.log(resultTxt); }
|
||||
} else if (opts.suppressDialog !== true) {
|
||||
dialogBoxCreate(resultTxt);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to sell a stock in the long position
|
||||
* @param {Stock} stock - Stock to sell
|
||||
* @param {number} shares - Number of shares to sell
|
||||
* @param {WorkerScript} workerScript - If this is being called through Netscript
|
||||
* @param opts - Optional configuration for this function's behavior. See top of file
|
||||
* returns {boolean} - true if successfully sells given number of shares OR MAX owned, false otherwise
|
||||
*/
|
||||
export function sellStock(stock: Stock, shares: number, workerScript: WorkerScript | null=null, opts: IOptions={}): boolean {
|
||||
const tixApi = (workerScript instanceof WorkerScript);
|
||||
|
||||
// Sanitize/Validate arguments
|
||||
if (stock == null || shares < 0 || isNaN(shares)) {
|
||||
if (tixApi) {
|
||||
workerScript!.log(`ERROR: sellStock() failed due to invalid arguments`);
|
||||
} else if (opts.suppressDialog !== true) {
|
||||
dialogBoxCreate("Failed to sell stock. This is probably due to an invalid quantity. Otherwise, this may be a bug, contact developer");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
shares = Math.round(shares);
|
||||
if (shares > stock.playerShares) { shares = stock.playerShares; }
|
||||
if (shares === 0) { return false; }
|
||||
|
||||
const gains = getSellTransactionGain(stock, shares, PositionTypes.Long);
|
||||
if (gains == null) { return false; }
|
||||
let netProfit = gains - (stock.playerAvgPx * shares);
|
||||
if (isNaN(netProfit)) { netProfit = 0; }
|
||||
Player.gainMoney(gains);
|
||||
Player.recordMoneySource(netProfit, "stock");
|
||||
if (tixApi) {
|
||||
workerScript!.scriptRef.onlineMoneyMade += netProfit;
|
||||
Player.scriptProdSinceLastAug += netProfit;
|
||||
}
|
||||
|
||||
stock.playerShares = Math.round(stock.playerShares - shares);
|
||||
if (stock.playerShares === 0) {
|
||||
stock.playerAvgPx = 0;
|
||||
}
|
||||
|
||||
processSellTransactionPriceMovement(stock, shares, PositionTypes.Long);
|
||||
|
||||
if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") {
|
||||
opts.rerenderFn();
|
||||
}
|
||||
|
||||
const resultTxt = `Sold ${numeralWrapper.format(shares, '0,0')} shares of ${stock.symbol}. ` +
|
||||
`After commissions, you gained a total of ${numeralWrapper.formatMoney(gains)}.`;
|
||||
if (tixApi) {
|
||||
if (workerScript!.shouldLog("sellStock")) { workerScript!.log(resultTxt); }
|
||||
} else if (opts.suppressDialog !== true) {
|
||||
dialogBoxCreate(resultTxt);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to buy a stock in the short position
|
||||
* @param {Stock} stock - Stock to sell
|
||||
* @param {number} shares - Number of shares to short
|
||||
* @param {WorkerScript} workerScript - If this is being called through Netscript
|
||||
* @param opts - Optional configuration for this function's behavior. See top of file
|
||||
* @returns {boolean} - true if successful, false otherwise
|
||||
*/
|
||||
export function shortStock(stock: Stock, shares: number, workerScript: WorkerScript | null=null, opts: IOptions={}): boolean {
|
||||
const tixApi = (workerScript instanceof WorkerScript);
|
||||
|
||||
// Validate arguments
|
||||
shares = Math.round(shares);
|
||||
if (shares <= 0) { return false; }
|
||||
if (stock == null || isNaN(shares)) {
|
||||
if (tixApi) {
|
||||
workerScript!.log("ERROR: shortStock() failed because of invalid arguments.");
|
||||
} else if (opts.suppressDialog !== true) {
|
||||
dialogBoxCreate("Failed to initiate a short position in a stock. This is probably " +
|
||||
"due to an invalid quantity. Otherwise, this may be a bug, so contact developer");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Does the player have enough money?
|
||||
const totalPrice = getBuyTransactionCost(stock, shares, PositionTypes.Short);
|
||||
if (totalPrice == null) { return false; }
|
||||
if (Player.money.lt(totalPrice)) {
|
||||
if (tixApi) {
|
||||
workerScript!.log("ERROR: shortStock() failed because you do not have enough " +
|
||||
"money to purchase this short position. You need " +
|
||||
numeralWrapper.formatMoney(totalPrice));
|
||||
} else if (opts.suppressDialog !== true) {
|
||||
dialogBoxCreate("You do not have enough money to purchase this short position. You need " +
|
||||
numeralWrapper.formatMoney(totalPrice));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Would this purchase exceed the maximum number of shares?
|
||||
if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) {
|
||||
if (tixApi) {
|
||||
workerScript!.log(`ERROR: shortStock() failed because purchasing this many short shares would exceed ${stock.symbol}'s maximum number of shares.`);
|
||||
} else if (opts.suppressDialog !== true) {
|
||||
dialogBoxCreate(`You cannot purchase this many shares. ${stock.symbol} has a maximum of ${stock.maxShares} shares.`);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const origTotal = stock.playerShortShares * stock.playerAvgShortPx;
|
||||
Player.loseMoney(totalPrice);
|
||||
const newTotal = origTotal + totalPrice - CONSTANTS.StockMarketCommission;
|
||||
stock.playerShortShares = Math.round(stock.playerShortShares + shares);
|
||||
stock.playerAvgShortPx = newTotal / stock.playerShortShares;
|
||||
processBuyTransactionPriceMovement(stock, shares, PositionTypes.Short);
|
||||
|
||||
if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") {
|
||||
opts.rerenderFn();
|
||||
}
|
||||
|
||||
const resultTxt = `Bought a short position of ${numeralWrapper.format(shares, '0,0')} shares of ${stock.symbol} ` +
|
||||
`for ${numeralWrapper.formatMoney(totalPrice)}. Paid ${numeralWrapper.formatMoney(CONSTANTS.StockMarketCommission)} ` +
|
||||
`in commission fees.`;
|
||||
if (tixApi) {
|
||||
if (workerScript!.shouldLog("shortStock")) { workerScript!.log(resultTxt); }
|
||||
} else if (!opts.suppressDialog) {
|
||||
dialogBoxCreate(resultTxt);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to sell a stock in the short position
|
||||
* @param {Stock} stock - Stock to sell
|
||||
* @param {number} shares - Number of shares to sell
|
||||
* @param {WorkerScript} workerScript - If this is being called through Netscript
|
||||
* @param opts - Optional configuration for this function's behavior. See top of file
|
||||
* @returns {boolean} true if successfully sells given amount OR max owned, false otherwise
|
||||
*/
|
||||
export function sellShort(stock: Stock, shares: number, workerScript: WorkerScript | null=null, opts: IOptions={}): boolean {
|
||||
const tixApi = (workerScript instanceof WorkerScript);
|
||||
|
||||
if (stock == null || isNaN(shares) || shares < 0) {
|
||||
if (tixApi) {
|
||||
workerScript!.log("ERROR: sellShort() failed because of invalid arguments.");
|
||||
} else if (!opts.suppressDialog) {
|
||||
dialogBoxCreate("Failed to sell a short position in a stock. This is probably " +
|
||||
"due to an invalid quantity. Otherwise, this may be a bug, so contact developer");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
shares = Math.round(shares);
|
||||
if (shares > stock.playerShortShares) {shares = stock.playerShortShares;}
|
||||
if (shares === 0) {return false;}
|
||||
|
||||
const origCost = shares * stock.playerAvgShortPx;
|
||||
const totalGain = getSellTransactionGain(stock, shares, PositionTypes.Short);
|
||||
if (totalGain == null || isNaN(totalGain) || origCost == null) {
|
||||
if (tixApi) {
|
||||
workerScript!.log(`Failed to sell short position in a stock. This is probably either due to invalid arguments, or a bug`);
|
||||
} else if (!opts.suppressDialog) {
|
||||
dialogBoxCreate(`Failed to sell short position in a stock. This is probably either due to invalid arguments, or a bug`);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
let profit = totalGain - origCost;
|
||||
if (isNaN(profit)) { profit = 0; }
|
||||
Player.gainMoney(totalGain);
|
||||
Player.recordMoneySource(profit, "stock");
|
||||
if (tixApi) {
|
||||
workerScript!.scriptRef.onlineMoneyMade += profit;
|
||||
Player.scriptProdSinceLastAug += profit;
|
||||
}
|
||||
|
||||
stock.playerShortShares = Math.round(stock.playerShortShares - shares);
|
||||
if (stock.playerShortShares === 0) {
|
||||
stock.playerAvgShortPx = 0;
|
||||
}
|
||||
processSellTransactionPriceMovement(stock, shares, PositionTypes.Short);
|
||||
|
||||
if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") {
|
||||
opts.rerenderFn();
|
||||
}
|
||||
|
||||
const resultTxt = `Sold your short position of ${numeralWrapper.format(shares, '0,0')} shares of ${stock.symbol}. ` +
|
||||
`After commissions, you gained a total of ${numeralWrapper.formatMoney(totalGain)}`;
|
||||
if (tixApi) {
|
||||
if (workerScript!.shouldLog("sellShort")) { workerScript!.log(resultTxt); }
|
||||
} else if (!opts.suppressDialog) {
|
||||
dialogBoxCreate(resultTxt);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
5
src/StockMarket/IOrderBook.ts
Normal file
5
src/StockMarket/IOrderBook.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { Order } from "./Order";
|
||||
|
||||
export interface IOrderBook {
|
||||
[key: string]: Order[];
|
||||
}
|
10
src/StockMarket/IStockMarket.ts
Normal file
10
src/StockMarket/IStockMarket.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { IOrderBook } from "./IOrderBook";
|
||||
import { Stock } from "./Stock";
|
||||
|
||||
export type IStockMarket = {
|
||||
[key: string]: Stock;
|
||||
} & {
|
||||
lastUpdate: number;
|
||||
storedCycles: number;
|
||||
Orders: IOrderBook;
|
||||
}
|
59
src/StockMarket/Order.ts
Normal file
59
src/StockMarket/Order.ts
Normal file
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Represents a Limit or Buy Order on the stock market. Does not represent
|
||||
* a Market Order since those are just executed immediately
|
||||
*/
|
||||
import { OrderTypes } from "./data/OrderTypes";
|
||||
import { PositionTypes } from "./data/PositionTypes";
|
||||
|
||||
import {
|
||||
Generic_fromJSON,
|
||||
Generic_toJSON,
|
||||
Reviver,
|
||||
} from "../../utils/JSONReviver";
|
||||
|
||||
export class Order {
|
||||
/**
|
||||
* Initializes a Order from a JSON save state
|
||||
*/
|
||||
static fromJSON(value: any): Order {
|
||||
return Generic_fromJSON(Order, value.data);
|
||||
}
|
||||
|
||||
readonly pos: PositionTypes;
|
||||
readonly price: number;
|
||||
shares: number;
|
||||
readonly stockSymbol: string;
|
||||
readonly type: OrderTypes;
|
||||
|
||||
constructor(stockSymbol: string="", shares: number=0, price: number=0, typ: OrderTypes=OrderTypes.LimitBuy, pos: PositionTypes=PositionTypes.Long) {
|
||||
// Validate arguments
|
||||
let invalidArgs: boolean = false;
|
||||
if (typeof shares !== "number" || typeof price !== "number") {
|
||||
invalidArgs = true;
|
||||
}
|
||||
if (isNaN(shares) || isNaN(price)) {
|
||||
invalidArgs = true;
|
||||
}
|
||||
if (typeof stockSymbol !== "string") {
|
||||
invalidArgs = true;
|
||||
}
|
||||
if (invalidArgs) {
|
||||
throw new Error(`Invalid constructor paramters for Order`);
|
||||
}
|
||||
|
||||
this.stockSymbol = stockSymbol;
|
||||
this.shares = shares;
|
||||
this.price = price;
|
||||
this.type = typ;
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the Order to a JSON save state.
|
||||
*/
|
||||
toJSON(): any {
|
||||
return Generic_toJSON("Order", this);
|
||||
}
|
||||
}
|
||||
|
||||
Reviver.constructors.Order = Order;
|
257
src/StockMarket/OrderProcessing.ts
Normal file
257
src/StockMarket/OrderProcessing.ts
Normal file
@ -0,0 +1,257 @@
|
||||
/**
|
||||
* Helper functions for determine whether Limit and Stop orders should
|
||||
* be executed (and executing them)
|
||||
*/
|
||||
import {
|
||||
buyStock,
|
||||
sellStock,
|
||||
shortStock,
|
||||
sellShort,
|
||||
} from "./BuyingAndSelling";
|
||||
import { IOrderBook } from "./IOrderBook";
|
||||
import { IStockMarket } from "./IStockMarket";
|
||||
import { Order } from "./Order";
|
||||
import { Stock } from "./Stock";
|
||||
|
||||
import { OrderTypes } from "./data/OrderTypes";
|
||||
import { PositionTypes } from "./data/PositionTypes";
|
||||
|
||||
import { IMap } from "../types";
|
||||
|
||||
import { numeralWrapper } from "../ui/numeralFormat";
|
||||
|
||||
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||
|
||||
interface IProcessOrderRefs {
|
||||
rerenderFn: () => void;
|
||||
stockMarket: IStockMarket;
|
||||
symbolToStockMap: IMap<Stock>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for all orders of a specific type and execute them if appropriate
|
||||
* @param {Stock} stock - Stock for which orders should be processed
|
||||
* @param {OrderTypes} orderType - Type of order to check (Limit/Stop buy/sell)
|
||||
* @param {PositionTypes} posType - Long or short
|
||||
* @param {IProcessOrderRefs} refs - References to objects/functions that are required for this function
|
||||
*/
|
||||
export function processOrders(stock: Stock, orderType: OrderTypes, posType: PositionTypes, refs: IProcessOrderRefs): void {
|
||||
let orderBook = refs.stockMarket["Orders"];
|
||||
if (orderBook == null) {
|
||||
const orders: IOrderBook = {};
|
||||
for (const name in refs.stockMarket) {
|
||||
const stock = refs.stockMarket[name];
|
||||
if (!(stock instanceof Stock)) { continue; }
|
||||
orders[stock.symbol] = [];
|
||||
}
|
||||
refs.stockMarket["Orders"] = orders;
|
||||
return; // Newly created, so no orders to process
|
||||
}
|
||||
let stockOrders = orderBook[stock.symbol];
|
||||
if (stockOrders == null || !(stockOrders.constructor === Array)) {
|
||||
console.error(`Invalid Order book for ${stock.symbol} in processOrders()`);
|
||||
stockOrders = [];
|
||||
return;
|
||||
}
|
||||
|
||||
for (const order of stockOrders) {
|
||||
if (order.type === orderType && order.pos === posType) {
|
||||
switch (order.type) {
|
||||
case OrderTypes.LimitBuy:
|
||||
if (order.pos === PositionTypes.Long && stock.price <= order.price) {
|
||||
executeOrder/*66*/(order, refs);
|
||||
} else if (order.pos === PositionTypes.Short && stock.price >= order.price) {
|
||||
executeOrder/*66*/(order, refs);
|
||||
}
|
||||
break;
|
||||
case OrderTypes.LimitSell:
|
||||
if (order.pos === PositionTypes.Long && stock.price >= order.price) {
|
||||
executeOrder/*66*/(order, refs);
|
||||
} else if (order.pos === PositionTypes.Short && stock.price <= order.price) {
|
||||
executeOrder/*66*/(order, refs);
|
||||
}
|
||||
break;
|
||||
case OrderTypes.StopBuy:
|
||||
if (order.pos === PositionTypes.Long && stock.price >= order.price) {
|
||||
executeOrder/*66*/(order, refs);
|
||||
} else if (order.pos === PositionTypes.Short && stock.price <= order.price) {
|
||||
executeOrder/*66*/(order, refs);
|
||||
}
|
||||
break;
|
||||
case OrderTypes.StopSell:
|
||||
if (order.pos === PositionTypes.Long && stock.price <= order.price) {
|
||||
executeOrder/*66*/(order, refs);
|
||||
} else if (order.pos === PositionTypes.Short && stock.price >= order.price) {
|
||||
executeOrder/*66*/(order, refs);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.warn(`Invalid order type: ${order.type}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a Stop or Limit Order.
|
||||
* @param {Order} order - Order being executed
|
||||
* @param {IStockMarket} stockMarket - Reference to StockMarket object
|
||||
*/
|
||||
function executeOrder(order: Order, refs: IProcessOrderRefs) {
|
||||
const stock = refs.symbolToStockMap[order.stockSymbol];
|
||||
if (!(stock instanceof Stock)) {
|
||||
console.error(`Could not find stock for this order: ${order.stockSymbol}`);
|
||||
return;
|
||||
}
|
||||
const stockMarket = refs.stockMarket;
|
||||
const orderBook = stockMarket["Orders"];
|
||||
const stockOrders = orderBook[stock.symbol];
|
||||
const isLimit = (order.type === OrderTypes.LimitBuy || order.type === OrderTypes.LimitSell);
|
||||
let sharesTransacted = 0;
|
||||
|
||||
// When orders are executed, the buying and selling functions shouldn't
|
||||
// emit popup dialog boxes. This options object configures the functions for that
|
||||
const opts = {
|
||||
rerenderFn: refs.rerenderFn,
|
||||
suppressDialog: true
|
||||
}
|
||||
|
||||
let res = true;
|
||||
let isBuy = false;
|
||||
switch (order.type) {
|
||||
case OrderTypes.LimitBuy: {
|
||||
isBuy = true;
|
||||
|
||||
// We only execute limit orders until the price fails to match the order condition
|
||||
const isLong = (order.pos === PositionTypes.Long);
|
||||
const firstShares = Math.min(order.shares, isLong ? stock.shareTxUntilMovementUp : stock.shareTxUntilMovementDown);
|
||||
|
||||
// First transaction to trigger movement
|
||||
let res = (isLong ? buyStock(stock, firstShares, null, opts) : shortStock(stock, firstShares, null, opts));
|
||||
if (res) {
|
||||
sharesTransacted = firstShares;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
let remainingShares = order.shares - firstShares;
|
||||
let remainingIterations = Math.ceil(remainingShares / stock.shareTxForMovement);
|
||||
for (let i = 0; i < remainingIterations; ++i) {
|
||||
if (isLong && stock.price > order.price) {
|
||||
break;
|
||||
} else if (!isLong && stock.price < order.price) {
|
||||
break;
|
||||
}
|
||||
|
||||
const shares = Math.min(remainingShares, stock.shareTxForMovement);
|
||||
let res = (isLong ? buyStock(stock, shares, null, opts) : shortStock(stock, shares, null, opts));
|
||||
if (res) {
|
||||
sharesTransacted += shares;
|
||||
remainingShares -= shares;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OrderTypes.StopBuy: {
|
||||
isBuy = true;
|
||||
sharesTransacted = order.shares;
|
||||
if (order.pos === PositionTypes.Long) {
|
||||
res = buyStock(stock, order.shares, null, opts) && res;
|
||||
} else if (order.pos === PositionTypes.Short) {
|
||||
res = shortStock(stock, order.shares, null, opts) && res;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OrderTypes.LimitSell: {
|
||||
// TODO need to account for player's shares here
|
||||
// We only execute limit orders until the price fails to match the order condition
|
||||
const isLong = (order.pos === PositionTypes.Long);
|
||||
const totalShares = Math.min((isLong ? stock.playerShares : stock.playerShortShares), order.shares);
|
||||
if (totalShares === 0) {
|
||||
return; // Player has no shares
|
||||
}
|
||||
const firstShares = Math.min(totalShares, isLong ? stock.shareTxUntilMovementDown : stock.shareTxUntilMovementUp);
|
||||
|
||||
// First transaction to trigger movement
|
||||
if (isLong ? sellStock(stock, firstShares, null, opts) : sellShort(stock, firstShares, null, opts)) {
|
||||
sharesTransacted = firstShares;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
let remainingShares = totalShares - firstShares;
|
||||
let remainingIterations = Math.ceil(remainingShares / stock.shareTxForMovement);
|
||||
for (let i = 0; i < remainingIterations; ++i) {
|
||||
if (isLong && stock.price < order.price) {
|
||||
break;
|
||||
} else if (!isLong && stock.price > order.price) {
|
||||
break;
|
||||
}
|
||||
|
||||
const shares = Math.min(remainingShares, stock.shareTxForMovement);
|
||||
if (isLong ? sellStock(stock, shares, null, opts) : sellShort(stock, shares, null, opts)) {
|
||||
sharesTransacted += shares;
|
||||
remainingShares -= shares;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OrderTypes.StopSell: {
|
||||
if (order.pos === PositionTypes.Long) {
|
||||
sharesTransacted = Math.min(stock.playerShares, order.shares);
|
||||
if (sharesTransacted <= 0) { return; }
|
||||
res = sellStock(stock, sharesTransacted, null, opts) && res;
|
||||
} else if (order.pos === PositionTypes.Short) {
|
||||
sharesTransacted = Math.min(stock.playerShortShares, order.shares);
|
||||
if (sharesTransacted <= 0) { return; }
|
||||
res = sellShort(stock, sharesTransacted, null, opts) && res;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.warn(`Invalid order type: ${order.type}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLimit) {
|
||||
res = (sharesTransacted > 0);
|
||||
}
|
||||
|
||||
// Position type, for logging/message purposes
|
||||
const pos = order.pos === PositionTypes.Long ? "Long" : "Short";
|
||||
|
||||
if (res) {
|
||||
for (let i = 0; i < stockOrders.length; ++i) {
|
||||
if (order == stockOrders[i]) {
|
||||
// Limit orders might only transact a certain # of shares, so we have the adjust the order qty.
|
||||
stockOrders[i].shares -= sharesTransacted;
|
||||
if (stockOrders[i].shares <= 0) {
|
||||
stockOrders.splice(i, 1);
|
||||
dialogBoxCreate(`${order.type} for ${stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}) was filled ` +
|
||||
`(${numeralWrapper.formatBigNumber(Math.round(sharesTransacted))} share)`);
|
||||
} else {
|
||||
dialogBoxCreate(`${order.type} for ${stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}) was partially filled ` +
|
||||
`(${numeralWrapper.formatBigNumber(Math.round(sharesTransacted))} shares transacted, ${stockOrders[i].shares} shares remaining`);
|
||||
}
|
||||
refs.rerenderFn();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.error("Could not find the following Order in Order Book: ");
|
||||
console.error(order);
|
||||
} else {
|
||||
if (isBuy) {
|
||||
dialogBoxCreate(`Failed to execute ${order.type} for ${stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}). ` +
|
||||
`This is most likely because you do not have enough money or the order would exceed the stock's maximum number of shares`);
|
||||
} else {
|
||||
dialogBoxCreate(`Failed to execute ${order.type} for ${stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}). ` +
|
||||
`This may be a bug, please report to game developer with details.`);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,58 @@
|
||||
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
|
||||
import { IMinMaxRange } from "../types";
|
||||
import {
|
||||
Generic_fromJSON,
|
||||
Generic_toJSON,
|
||||
Reviver
|
||||
} from "../../utils/JSONReviver";
|
||||
import { getRandomInt } from "../../utils/helpers/getRandomInt";
|
||||
|
||||
export interface IConstructorParams {
|
||||
b: boolean;
|
||||
initPrice: number | IMinMaxRange;
|
||||
marketCap: number;
|
||||
mv: number | IMinMaxRange;
|
||||
name: string;
|
||||
otlkMag: number;
|
||||
spreadPerc: number | IMinMaxRange;
|
||||
shareTxForMovement: number | IMinMaxRange;
|
||||
symbol: string;
|
||||
}
|
||||
|
||||
const defaultConstructorParams: IConstructorParams = {
|
||||
b: true,
|
||||
initPrice: 10e3,
|
||||
marketCap: 1e12,
|
||||
mv: 1,
|
||||
name: "",
|
||||
otlkMag: 0,
|
||||
spreadPerc: 0,
|
||||
shareTxForMovement: 1e6,
|
||||
symbol: "",
|
||||
}
|
||||
|
||||
// Helper function that convert a IMinMaxRange to a number
|
||||
function toNumber(n: number | IMinMaxRange): number {
|
||||
let value: number;
|
||||
switch (typeof n) {
|
||||
case "number": {
|
||||
return <number>n;
|
||||
}
|
||||
case "object": {
|
||||
const range = <IMinMaxRange>n;
|
||||
value = getRandomInt(range.min, range.max);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw Error(`Do not know how to convert the type '${typeof n}' to a number`);
|
||||
}
|
||||
|
||||
if (typeof n === "object" && typeof n.divisor === "number") {
|
||||
return value / n.divisor;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the valuation of a company in the World Stock Exchange.
|
||||
*/
|
||||
@ -22,6 +74,11 @@ export class Stock {
|
||||
*/
|
||||
readonly cap: number;
|
||||
|
||||
/**
|
||||
* Stocks previous share price
|
||||
*/
|
||||
lastPrice: number;
|
||||
|
||||
/**
|
||||
* Maximum number of shares that player can own (both long and short combined)
|
||||
*/
|
||||
@ -63,16 +120,35 @@ export class Stock {
|
||||
*/
|
||||
playerShortShares: number;
|
||||
|
||||
/**
|
||||
* The HTML element that displays the stock's info in the UI
|
||||
*/
|
||||
posTxtEl: HTMLElement | null;
|
||||
|
||||
/**
|
||||
* Stock's share price
|
||||
*/
|
||||
price: number;
|
||||
|
||||
/**
|
||||
* Percentage by which the stock's price changes for a transaction-induced
|
||||
* price movement.
|
||||
*/
|
||||
readonly priceMovementPerc: number;
|
||||
|
||||
/**
|
||||
* How many shares need to be transacted in order to trigger a price movement
|
||||
*/
|
||||
readonly shareTxForMovement: number;
|
||||
|
||||
/**
|
||||
* How many share transactions remaining until a price movement occurs
|
||||
* (separately tracked for upward and downward movements)
|
||||
*/
|
||||
shareTxUntilMovementDown: number;
|
||||
shareTxUntilMovementUp: number;
|
||||
|
||||
/**
|
||||
* Spread percentage. The bid/ask prices for this stock are N% above or below
|
||||
* the "real price" to emulate spread.
|
||||
*/
|
||||
readonly spreadPerc: number;
|
||||
|
||||
/**
|
||||
* The stock's ticker symbol
|
||||
*/
|
||||
@ -85,34 +161,51 @@ export class Stock {
|
||||
*/
|
||||
readonly totalShares: number;
|
||||
|
||||
constructor(name: string = "",
|
||||
symbol: string = "",
|
||||
mv: number = 1,
|
||||
b: boolean = true,
|
||||
otlkMag: number = 0,
|
||||
initPrice: number = 10e3,
|
||||
marketCap: number = 1e12) {
|
||||
this.name = name;
|
||||
this.symbol = symbol;
|
||||
this.price = initPrice;
|
||||
constructor(p: IConstructorParams = defaultConstructorParams) {
|
||||
this.name = p.name;
|
||||
this.symbol = p.symbol;
|
||||
this.price = toNumber(p.initPrice);
|
||||
this.lastPrice = this.price;
|
||||
this.playerShares = 0;
|
||||
this.playerAvgPx = 0;
|
||||
this.playerShortShares = 0;
|
||||
this.playerAvgShortPx = 0;
|
||||
this.mv = mv;
|
||||
this.b = b;
|
||||
this.otlkMag = otlkMag;
|
||||
this.cap = getRandomInt(initPrice * 1e3, initPrice * 25e3);
|
||||
this.mv = toNumber(p.mv);
|
||||
this.b = p.b;
|
||||
this.otlkMag = p.otlkMag;
|
||||
this.cap = getRandomInt(this.price * 1e3, this.price * 25e3);
|
||||
this.spreadPerc = toNumber(p.spreadPerc);
|
||||
this.priceMovementPerc = this.spreadPerc / (getRandomInt(10, 30) / 10);
|
||||
this.shareTxForMovement = toNumber(p.shareTxForMovement);
|
||||
this.shareTxUntilMovementDown = this.shareTxForMovement;
|
||||
this.shareTxUntilMovementUp = this.shareTxForMovement;
|
||||
|
||||
// Total shares is determined by market cap, and is rounded to nearest 100k
|
||||
let totalSharesUnrounded: number = (marketCap / initPrice);
|
||||
let totalSharesUnrounded: number = (p.marketCap / this.price);
|
||||
this.totalShares = Math.round(totalSharesUnrounded / 1e5) * 1e5;
|
||||
|
||||
// Max Shares (Outstanding shares) is a percentage of total shares
|
||||
const outstandingSharePercentage: number = 0.2;
|
||||
this.maxShares = Math.round((this.totalShares * outstandingSharePercentage) / 1e5) * 1e5;
|
||||
}
|
||||
|
||||
this.posTxtEl = null;
|
||||
changePrice(newPrice: number): void {
|
||||
this.lastPrice = this.price;
|
||||
this.price = newPrice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the price at which YOUR stock is bought (market ask price). Accounts for spread
|
||||
*/
|
||||
getAskPrice(): number {
|
||||
return this.price * (1 + (this.spreadPerc / 100));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the price at which YOUR stock is sold (market bid price). Accounts for spread
|
||||
*/
|
||||
getBidPrice(): number {
|
||||
return this.price * (1 - (this.spreadPerc / 100));
|
||||
}
|
||||
|
||||
/**
|
||||
|
File diff suppressed because it is too large
Load Diff
323
src/StockMarket/StockMarket.jsx
Normal file
323
src/StockMarket/StockMarket.jsx
Normal file
@ -0,0 +1,323 @@
|
||||
import {
|
||||
buyStock,
|
||||
sellStock,
|
||||
shortStock,
|
||||
sellShort,
|
||||
} from "./BuyingAndSelling";
|
||||
import { Order } from "./Order";
|
||||
import { processOrders } from "./OrderProcessing";
|
||||
import { Stock } from "./Stock";
|
||||
import {
|
||||
getBuyTransactionCost,
|
||||
getSellTransactionGain,
|
||||
processBuyTransactionPriceMovement,
|
||||
processSellTransactionPriceMovement
|
||||
} from "./StockMarketHelpers";
|
||||
import {
|
||||
getStockMarket4SDataCost,
|
||||
getStockMarket4STixApiCost
|
||||
} from "./StockMarketCosts";
|
||||
import { InitStockMetadata } from "./data/InitStockMetadata";
|
||||
import { OrderTypes } from "./data/OrderTypes";
|
||||
import { PositionTypes } from "./data/PositionTypes";
|
||||
import { StockSymbols } from "./data/StockSymbols";
|
||||
import { StockMarketRoot } from "./ui/Root";
|
||||
|
||||
import { CONSTANTS } from "../Constants";
|
||||
import { WorkerScript } from "../Netscript/WorkerScript";
|
||||
import { Player } from "../Player";
|
||||
|
||||
import { Page, routing } from ".././ui/navigationTracking";
|
||||
import { numeralWrapper } from ".././ui/numeralFormat";
|
||||
|
||||
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||
import { Reviver } from "../../utils/JSONReviver";
|
||||
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
export let StockMarket = {}; // Maps full stock name -> Stock object
|
||||
export let SymbolToStockMap = {}; // Maps symbol -> Stock object
|
||||
|
||||
export function placeOrder(stock, shares, price, type, position, workerScript=null) {
|
||||
const tixApi = (workerScript instanceof WorkerScript);
|
||||
if (!(stock instanceof Stock)) {
|
||||
if (tixApi) {
|
||||
workerScript.log(`ERROR: Invalid stock passed to placeOrder() function`);
|
||||
} else {
|
||||
dialogBoxCreate(`ERROR: Invalid stock passed to placeOrder() function`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (typeof shares !== "number" || typeof price !== "number") {
|
||||
if (tixApi) {
|
||||
workerScript.log("ERROR: Invalid numeric value provided for either 'shares' or 'price' argument");
|
||||
} else {
|
||||
dialogBoxCreate("ERROR: Invalid numeric value provided for either 'shares' or 'price' argument");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const order = new Order(stock.symbol, shares, price, type, position);
|
||||
if (StockMarket["Orders"] == null) {
|
||||
const orders = {};
|
||||
for (const name in StockMarket) {
|
||||
const stk = StockMarket[name];
|
||||
if (!(stk instanceof Stock)) { continue; }
|
||||
orders[stk.symbol] = [];
|
||||
}
|
||||
StockMarket["Orders"] = orders;
|
||||
}
|
||||
StockMarket["Orders"][stock.symbol].push(order);
|
||||
|
||||
// Process to see if it should be executed immediately
|
||||
const processOrderRefs = {
|
||||
rerenderFn: displayStockMarketContent,
|
||||
stockMarket: StockMarket,
|
||||
symbolToStockMap: SymbolToStockMap,
|
||||
}
|
||||
processOrders(stock, order.type, order.pos, processOrderRefs);
|
||||
displayStockMarketContent();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns true if successfully cancels an order, false otherwise
|
||||
export function cancelOrder(params, workerScript=null) {
|
||||
var tixApi = (workerScript instanceof WorkerScript);
|
||||
if (StockMarket["Orders"] == null) {return false;}
|
||||
if (params.order && params.order instanceof Order) {
|
||||
const order = params.order;
|
||||
// An 'Order' object is passed in
|
||||
var stockOrders = StockMarket["Orders"][order.stockSymbol];
|
||||
for (var i = 0; i < stockOrders.length; ++i) {
|
||||
if (order == stockOrders[i]) {
|
||||
stockOrders.splice(i, 1);
|
||||
displayStockMarketContent();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (params.stock && params.shares && params.price && params.type &&
|
||||
params.pos && params.stock instanceof Stock) {
|
||||
// Order properties are passed in. Need to look for the order
|
||||
var stockOrders = StockMarket["Orders"][params.stock.symbol];
|
||||
var orderTxt = params.stock.symbol + " - " + params.shares + " @ " +
|
||||
numeralWrapper.formatMoney(params.price);
|
||||
for (var i = 0; i < stockOrders.length; ++i) {
|
||||
var order = stockOrders[i];
|
||||
if (params.shares === order.shares &&
|
||||
params.price === order.price &&
|
||||
params.type === order.type &&
|
||||
params.pos === order.pos) {
|
||||
stockOrders.splice(i, 1);
|
||||
displayStockMarketContent();
|
||||
if (tixApi) {
|
||||
workerScript.scriptRef.log("Successfully cancelled order: " + orderTxt);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (tixApi) {
|
||||
workerScript.scriptRef.log("Failed to cancel order: " + orderTxt);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function loadStockMarket(saveString) {
|
||||
if (saveString === "") {
|
||||
StockMarket = {};
|
||||
} else {
|
||||
StockMarket = JSON.parse(saveString, Reviver);
|
||||
|
||||
// Backwards compatibility for v0.47.0
|
||||
const orderBook = StockMarket["Orders"];
|
||||
if (orderBook != null) {
|
||||
// For each order, set its 'stockSymbol' property equal to the
|
||||
// symbol of its 'stock' property
|
||||
for (const stockSymbol in orderBook) {
|
||||
const ordersForStock = orderBook[stockSymbol];
|
||||
if (Array.isArray(ordersForStock)) {
|
||||
for (const order of ordersForStock) {
|
||||
if (order instanceof Order && order.stock instanceof Stock) {
|
||||
order.stockSymbol = order.stock.symbol;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(`Converted Stock Market order book to v0.47.0 format`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function deleteStockMarket() {
|
||||
StockMarket = {};
|
||||
}
|
||||
|
||||
export function initStockMarket() {
|
||||
for (const stk in StockMarket) {
|
||||
if (StockMarket.hasOwnProperty(stk)) {
|
||||
delete StockMarket[stk];
|
||||
}
|
||||
}
|
||||
|
||||
for (const metadata of InitStockMetadata) {
|
||||
const name = metadata.name;
|
||||
StockMarket[name] = new Stock(metadata);
|
||||
}
|
||||
|
||||
const orders = {};
|
||||
for (const name in StockMarket) {
|
||||
const stock = StockMarket[name];
|
||||
if (!(stock instanceof Stock)) { continue; }
|
||||
orders[stock.symbol] = [];
|
||||
}
|
||||
StockMarket["Orders"] = orders;
|
||||
|
||||
StockMarket.storedCycles = 0;
|
||||
StockMarket.lastUpdate = 0;
|
||||
}
|
||||
|
||||
export function initSymbolToStockMap() {
|
||||
for (const name in StockSymbols) {
|
||||
if (StockSymbols.hasOwnProperty(name)) {
|
||||
const stock = StockMarket[name];
|
||||
if (stock == null) {
|
||||
console.error(`Could not find Stock for ${name}`);
|
||||
continue;
|
||||
}
|
||||
const symbol = StockSymbols[name];
|
||||
SymbolToStockMap[symbol] = stock;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function stockMarketCycle() {
|
||||
for (const name in StockMarket) {
|
||||
const stock = StockMarket[name];
|
||||
if (!(stock instanceof Stock)) { continue; }
|
||||
let thresh = 0.6;
|
||||
if (stock.b) { thresh = 0.4; }
|
||||
if (Math.random() < thresh) {
|
||||
stock.b = !stock.b;
|
||||
if (stock.otlkMag < 8) { stock.otlkMag += 0.1; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stock prices updated every 6 seconds
|
||||
const msPerStockUpdate = 6e3;
|
||||
const cyclesPerStockUpdate = msPerStockUpdate / CONSTANTS.MilliPerCycle;
|
||||
export function processStockPrices(numCycles=1) {
|
||||
if (StockMarket.storedCycles == null || isNaN(StockMarket.storedCycles)) { StockMarket.storedCycles = 0; }
|
||||
StockMarket.storedCycles += numCycles;
|
||||
|
||||
if (StockMarket.storedCycles < cyclesPerStockUpdate) { return; }
|
||||
|
||||
// We can process the update every 4 seconds as long as there are enough
|
||||
// stored cycles. This lets us account for offline time
|
||||
const timeNow = new Date().getTime();
|
||||
if (timeNow - StockMarket.lastUpdate < 4e3) { return; }
|
||||
|
||||
StockMarket.lastUpdate = timeNow;
|
||||
StockMarket.storedCycles -= cyclesPerStockUpdate;
|
||||
|
||||
var v = Math.random();
|
||||
for (const name in StockMarket) {
|
||||
const stock = StockMarket[name];
|
||||
if (!(stock instanceof Stock)) { continue; }
|
||||
let av = (v * stock.mv) / 100;
|
||||
if (isNaN(av)) { av = .02; }
|
||||
|
||||
let chc = 50;
|
||||
if (stock.b) {
|
||||
chc = (chc + stock.otlkMag) / 100;
|
||||
} else {
|
||||
chc = (chc - stock.otlkMag) / 100;
|
||||
}
|
||||
if (stock.price >= stock.cap) {
|
||||
chc = 0.1; // "Soft Limit" on stock price. It could still go up but its unlikely
|
||||
stock.b = false;
|
||||
}
|
||||
if (isNaN(chc)) { chc = 0.5; }
|
||||
|
||||
const c = Math.random();
|
||||
const processOrderRefs = {
|
||||
rerenderFn: displayStockMarketContent,
|
||||
stockMarket: StockMarket,
|
||||
symbolToStockMap: SymbolToStockMap,
|
||||
}
|
||||
if (c < chc) {
|
||||
stock.changePrice(stock.price * (1 + av));
|
||||
processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Short, processOrderRefs);
|
||||
processOrders(stock, OrderTypes.LimitSell, PositionTypes.Long, processOrderRefs);
|
||||
processOrders(stock, OrderTypes.StopBuy, PositionTypes.Long, processOrderRefs);
|
||||
processOrders(stock, OrderTypes.StopSell, PositionTypes.Short, processOrderRefs);
|
||||
} else {
|
||||
stock.changePrice(stock.price / (1 + av));
|
||||
processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Long, processOrderRefs);
|
||||
processOrders(stock, OrderTypes.LimitSell, PositionTypes.Short, processOrderRefs);
|
||||
processOrders(stock, OrderTypes.StopBuy, PositionTypes.Short, processOrderRefs);
|
||||
processOrders(stock, OrderTypes.StopSell, PositionTypes.Long, processOrderRefs);
|
||||
}
|
||||
|
||||
let otlkMagChange = stock.otlkMag * av;
|
||||
if (stock.otlkMag <= 0.1) {
|
||||
otlkMagChange = 1;
|
||||
}
|
||||
if (c < 0.5) {
|
||||
stock.otlkMag += otlkMagChange;
|
||||
} else {
|
||||
stock.otlkMag -= otlkMagChange;
|
||||
}
|
||||
if (stock.otlkMag > 50) { stock.otlkMag = 50; } // Cap so the "forecast" is between 0 and 100
|
||||
if (stock.otlkMag < 0) {
|
||||
stock.otlkMag *= -1;
|
||||
stock.b = !stock.b;
|
||||
}
|
||||
|
||||
// Shares required for price movement gradually approaches max over time
|
||||
stock.shareTxUntilMovement = Math.min(stock.shareTxUntilMovementUp + 5, stock.shareTxForMovement);
|
||||
stock.shareTxUntilMovement = Math.min(stock.shareTxUntilMovementDown + 5, stock.shareTxForMovement);
|
||||
}
|
||||
|
||||
displayStockMarketContent();
|
||||
}
|
||||
|
||||
let stockMarketContainer = null;
|
||||
function setStockMarketContainer() {
|
||||
stockMarketContainer = document.getElementById("stock-market-container");
|
||||
document.removeEventListener("DOMContentLoaded", setStockMarketContainer);
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", setStockMarketContainer);
|
||||
|
||||
function initStockMarketFnForReact() {
|
||||
initStockMarket();
|
||||
initSymbolToStockMap();
|
||||
}
|
||||
|
||||
export function displayStockMarketContent() {
|
||||
if (!routing.isOn(Page.StockMarket)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stockMarketContainer instanceof HTMLElement) {
|
||||
ReactDOM.render(
|
||||
<StockMarketRoot
|
||||
buyStockLong={buyStock}
|
||||
buyStockShort={shortStock}
|
||||
cancelOrder={cancelOrder}
|
||||
initStockMarket={initStockMarketFnForReact}
|
||||
p={Player}
|
||||
placeOrder={placeOrder}
|
||||
sellStockLong={sellStock}
|
||||
sellStockShort={sellShort}
|
||||
stockMarket={StockMarket}
|
||||
/>,
|
||||
stockMarketContainer
|
||||
)
|
||||
}
|
||||
}
|
386
src/StockMarket/StockMarketHelpers.ts
Normal file
386
src/StockMarket/StockMarketHelpers.ts
Normal file
@ -0,0 +1,386 @@
|
||||
import { Stock } from "./Stock";
|
||||
import { PositionTypes } from "./data/PositionTypes";
|
||||
import { CONSTANTS } from "../Constants";
|
||||
|
||||
// Amount by which a stock's forecast changes during each price movement
|
||||
export const forecastChangePerPriceMovement = 0.1;
|
||||
|
||||
/**
|
||||
* Given a stock, calculates the amount by which the stock price is multiplied
|
||||
* for an 'upward' price movement. This does not actually increase the stock's price,
|
||||
* just calculates the multiplier
|
||||
* @param {Stock} stock - Stock for price movement
|
||||
* @returns {number | null} Number by which stock's price should be multiplied. Null for invalid args
|
||||
*/
|
||||
export function calculateIncreasingPriceMovement(stock: Stock): number | null {
|
||||
if (!(stock instanceof Stock)) { return null; }
|
||||
|
||||
return (1 + (stock.priceMovementPerc / 100));
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a stock, calculates the amount by which the stock price is multiplied
|
||||
* for a "downward" price movement. This does not actually increase the stock's price,
|
||||
* just calculates the multiplier
|
||||
* @param {Stock} stock - Stock for price movement
|
||||
* @returns {number | null} Number by which stock's price should be multiplied. Null for invalid args
|
||||
*/
|
||||
export function calculateDecreasingPriceMovement(stock: Stock): number | null {
|
||||
if (!(stock instanceof Stock)) { return null; }
|
||||
|
||||
return (1 - (stock.priceMovementPerc / 100));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the total cost of a "buy" transaction. This accounts for spread,
|
||||
* price movements, and commission.
|
||||
* @param {Stock} stock - Stock being purchased
|
||||
* @param {number} shares - Number of shares being transacted
|
||||
* @param {PositionTypes} posType - Long or short position
|
||||
* @returns {number | null} Total transaction cost. Returns null for an invalid transaction
|
||||
*/
|
||||
export function getBuyTransactionCost(stock: Stock, shares: number, posType: PositionTypes): number | null {
|
||||
if (isNaN(shares) || shares <= 0 || !(stock instanceof Stock)) { return null; }
|
||||
|
||||
// Cap the 'shares' arg at the stock's maximum shares. This'll prevent
|
||||
// hanging in the case when a really big number is passed in
|
||||
shares = Math.min(shares, stock.maxShares);
|
||||
|
||||
const isLong = (posType === PositionTypes.Long);
|
||||
|
||||
// If the number of shares doesn't trigger a price movement, its a simple calculation
|
||||
if (isLong) {
|
||||
if (shares <= stock.shareTxUntilMovementUp) {
|
||||
return (shares * stock.getAskPrice()) + CONSTANTS.StockMarketCommission;
|
||||
}
|
||||
} else {
|
||||
if (shares <= stock.shareTxUntilMovementDown) {
|
||||
return (shares * stock.getBidPrice()) + CONSTANTS.StockMarketCommission;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate how many iterations of price changes we need to account for
|
||||
const firstShares = isLong ? stock.shareTxUntilMovementUp : stock.shareTxUntilMovementDown;
|
||||
let remainingShares = shares - firstShares;
|
||||
let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement);
|
||||
|
||||
// The initial cost calculation takes care of the first "iteration"
|
||||
let currPrice = isLong ? stock.getAskPrice() : stock.getBidPrice();
|
||||
let totalCost = (firstShares * currPrice);
|
||||
|
||||
const increasingMvmt = calculateIncreasingPriceMovement(stock)!;
|
||||
const decreasingMvmt = calculateDecreasingPriceMovement(stock)!;
|
||||
|
||||
function processPriceMovement() {
|
||||
if (isLong) {
|
||||
currPrice *= increasingMvmt;
|
||||
} else {
|
||||
currPrice *= decreasingMvmt;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 1; i < numIterations; ++i) {
|
||||
processPriceMovement();
|
||||
|
||||
const amt = Math.min(stock.shareTxForMovement, remainingShares);
|
||||
totalCost += (amt * currPrice);
|
||||
remainingShares -= amt;
|
||||
}
|
||||
|
||||
return totalCost + CONSTANTS.StockMarketCommission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a buy transaction's resulting price AND forecast movement.
|
||||
* @param {Stock} stock - Stock being purchased
|
||||
* @param {number} shares - Number of shares being transacted
|
||||
* @param {PositionTypes} posType - Long or short position
|
||||
*/
|
||||
export function processBuyTransactionPriceMovement(stock: Stock, shares: number, posType: PositionTypes): void {
|
||||
if (isNaN(shares) || shares <= 0 || !(stock instanceof Stock)) { return; }
|
||||
|
||||
// Cap the 'shares' arg at the stock's maximum shares. This'll prevent
|
||||
// hanging in the case when a really big number is passed in
|
||||
shares = Math.min(shares, stock.maxShares);
|
||||
|
||||
const isLong = (posType === PositionTypes.Long);
|
||||
|
||||
let currPrice = stock.price;
|
||||
function processPriceMovement() {
|
||||
if (isLong) {
|
||||
currPrice *= calculateIncreasingPriceMovement(stock)!;
|
||||
} else {
|
||||
currPrice *= calculateDecreasingPriceMovement(stock)!;
|
||||
}
|
||||
}
|
||||
|
||||
// If there's only going to be one iteration
|
||||
const firstShares = isLong ? stock.shareTxUntilMovementUp : stock.shareTxUntilMovementDown;
|
||||
if (shares <= firstShares) {
|
||||
function triggerMovement() {
|
||||
processPriceMovement();
|
||||
stock.changePrice(currPrice);
|
||||
stock.otlkMag -= (forecastChangePerPriceMovement);
|
||||
}
|
||||
|
||||
if (isLong) {
|
||||
stock.shareTxUntilMovementUp -= shares;
|
||||
if (stock.shareTxUntilMovementUp <= 0) {
|
||||
stock.shareTxUntilMovementUp = stock.shareTxForMovement;
|
||||
triggerMovement();
|
||||
}
|
||||
} else {
|
||||
stock.shareTxUntilMovementDown -= shares;
|
||||
if (stock.shareTxUntilMovementDown <= 0) {
|
||||
stock.shareTxUntilMovementDown = stock.shareTxForMovement;
|
||||
triggerMovement();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate how many iterations of price changes we need to account for
|
||||
let remainingShares = shares - firstShares;
|
||||
let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement);
|
||||
|
||||
for (let i = 1; i < numIterations; ++i) {
|
||||
processPriceMovement();
|
||||
}
|
||||
|
||||
// If on the offchance we end up perfectly at the next price movement
|
||||
if (isLong) {
|
||||
stock.shareTxUntilMovementUp = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovementUp) % stock.shareTxForMovement);
|
||||
if (stock.shareTxUntilMovementUp === stock.shareTxForMovement || stock.shareTxUntilMovementUp <= 0) {
|
||||
// The shareTxUntilMovementUp ended up at 0 at the end of the "processing"
|
||||
++numIterations;
|
||||
stock.shareTxUntilMovementUp = stock.shareTxForMovement;
|
||||
processPriceMovement();
|
||||
}
|
||||
} else {
|
||||
stock.shareTxUntilMovementDown = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovementDown) % stock.shareTxForMovement);
|
||||
if (stock.shareTxUntilMovementDown === stock.shareTxForMovement || stock.shareTxUntilMovementDown <= 0) {
|
||||
// The shareTxUntilMovementDown ended up at 0 at the end of the "processing"
|
||||
++numIterations;
|
||||
stock.shareTxUntilMovementDown = stock.shareTxForMovement;
|
||||
processPriceMovement();
|
||||
}
|
||||
}
|
||||
|
||||
stock.changePrice(currPrice);
|
||||
|
||||
// Forecast always decreases in magnitude
|
||||
const forecastChange = Math.min(5, forecastChangePerPriceMovement * (numIterations - 1));
|
||||
stock.otlkMag -= forecastChange;
|
||||
if (stock.otlkMag < 0.1) {
|
||||
stock.otlkMag = 0.1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the TOTAL amount of money gained from a sale (NOT net profit). This accounts
|
||||
* for spread, price movements, and commission.
|
||||
* @param {Stock} stock - Stock being sold
|
||||
* @param {number} shares - Number of sharse being transacted
|
||||
* @param {PositionTypes} posType - Long or short position
|
||||
* @returns {number | null} Amount of money gained from transaction. Returns null for an invalid transaction
|
||||
*/
|
||||
export function getSellTransactionGain(stock: Stock, shares: number, posType: PositionTypes): number | null {
|
||||
if (isNaN(shares) || shares <= 0 || !(stock instanceof Stock)) { return null; }
|
||||
|
||||
// Cap the 'shares' arg at the stock's maximum shares. This'll prevent
|
||||
// hanging in the case when a really big number is passed in
|
||||
shares = Math.min(shares, stock.maxShares);
|
||||
|
||||
const isLong = (posType === PositionTypes.Long);
|
||||
const firstShares = isLong ? stock.shareTxUntilMovementDown : stock.shareTxUntilMovementUp;
|
||||
|
||||
// If the number of shares doesn't trigger a price mvoement, its a simple calculation
|
||||
if (shares <= firstShares) {
|
||||
if (isLong) {
|
||||
return (shares * stock.getBidPrice()) - CONSTANTS.StockMarketCommission;
|
||||
} else {
|
||||
// Calculating gains for a short position requires calculating the profit made
|
||||
const origCost = shares * stock.playerAvgShortPx;
|
||||
const profit = ((stock.playerAvgShortPx - stock.getAskPrice()) * shares) - CONSTANTS.StockMarketCommission;
|
||||
|
||||
return origCost + profit;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate how many iterations of price changes we need to account for
|
||||
let remainingShares = shares - firstShares;
|
||||
let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement);
|
||||
|
||||
// Helper function to calculate gain for a single iteration
|
||||
function calculateGain(thisPrice: number, thisShares: number) {
|
||||
if (isLong) {
|
||||
return thisShares * thisPrice;
|
||||
} else {
|
||||
const origCost = thisShares * stock.playerAvgShortPx;
|
||||
const profit = ((stock.playerAvgShortPx - thisPrice) * thisShares);
|
||||
|
||||
return origCost + profit;
|
||||
}
|
||||
}
|
||||
|
||||
// The initial cost calculation takes care of the first "iteration"
|
||||
let currPrice = isLong ? stock.getBidPrice() : stock.getAskPrice();
|
||||
let totalGain = calculateGain(currPrice, firstShares);
|
||||
for (let i = 1; i < numIterations; ++i) {
|
||||
// Price movement
|
||||
if (isLong) {
|
||||
currPrice *= calculateDecreasingPriceMovement(stock)!;
|
||||
} else {
|
||||
currPrice *= calculateIncreasingPriceMovement(stock)!;
|
||||
}
|
||||
|
||||
const amt = Math.min(stock.shareTxForMovement, remainingShares);
|
||||
totalGain += calculateGain(currPrice, amt);
|
||||
remainingShares -= amt;
|
||||
}
|
||||
|
||||
return totalGain - CONSTANTS.StockMarketCommission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a sell transaction's resulting price movement
|
||||
* @param {Stock} stock - Stock being sold
|
||||
* @param {number} shares - Number of sharse being transacted
|
||||
* @param {PositionTypes} posType - Long or short position
|
||||
*/
|
||||
export function processSellTransactionPriceMovement(stock: Stock, shares: number, posType: PositionTypes): void {
|
||||
if (isNaN(shares) || shares <= 0 || !(stock instanceof Stock)) { return; }
|
||||
|
||||
// Cap the 'shares' arg at the stock's maximum shares. This'll prevent
|
||||
// hanging in the case when a really big number is passed in
|
||||
shares = Math.min(shares, stock.maxShares);
|
||||
|
||||
const isLong = (posType === PositionTypes.Long);
|
||||
const firstShares = isLong ? stock.shareTxUntilMovementDown : stock.shareTxUntilMovementUp;
|
||||
|
||||
let currPrice = stock.price;
|
||||
function processPriceMovement() {
|
||||
if (isLong) {
|
||||
currPrice *= calculateDecreasingPriceMovement(stock)!;
|
||||
} else {
|
||||
currPrice *= calculateIncreasingPriceMovement(stock)!;
|
||||
}
|
||||
}
|
||||
|
||||
// If there's only going to be one iteration at most
|
||||
if (shares <= firstShares) {
|
||||
function triggerPriceMovement() {
|
||||
processPriceMovement();
|
||||
stock.changePrice(currPrice);
|
||||
stock.otlkMag -= (forecastChangePerPriceMovement);
|
||||
}
|
||||
|
||||
if (isLong) {
|
||||
stock.shareTxUntilMovementDown -= shares;
|
||||
if (stock.shareTxUntilMovementDown <= 0) {
|
||||
stock.shareTxUntilMovementDown = stock.shareTxForMovement;
|
||||
triggerPriceMovement();
|
||||
}
|
||||
} else {
|
||||
stock.shareTxUntilMovementUp -= shares;
|
||||
if (stock.shareTxUntilMovementUp <= 0) {
|
||||
stock.shareTxUntilMovementUp = stock.shareTxForMovement;
|
||||
triggerPriceMovement();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate how many iterations of price changes we need to account for
|
||||
let remainingShares = shares - firstShares;
|
||||
let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement);
|
||||
|
||||
for (let i = 1; i < numIterations; ++i) {
|
||||
processPriceMovement();
|
||||
}
|
||||
|
||||
// If on the offchance we end up perfectly at the next price movement
|
||||
if (isLong) {
|
||||
stock.shareTxUntilMovementDown = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovementDown) % stock.shareTxForMovement);
|
||||
if (stock.shareTxUntilMovementDown === stock.shareTxForMovement || stock.shareTxUntilMovementDown <= 0) {
|
||||
++numIterations;
|
||||
stock.shareTxUntilMovementDown = stock.shareTxForMovement;
|
||||
processPriceMovement();
|
||||
}
|
||||
} else {
|
||||
stock.shareTxUntilMovementUp = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovementUp) % stock.shareTxForMovement);
|
||||
if (stock.shareTxUntilMovementUp === stock.shareTxForMovement || stock.shareTxUntilMovementUp <= 0) {
|
||||
++numIterations;
|
||||
stock.shareTxUntilMovementUp = stock.shareTxForMovement;
|
||||
processPriceMovement();
|
||||
}
|
||||
}
|
||||
|
||||
stock.changePrice(currPrice);
|
||||
|
||||
// Forecast always decreases in magnitude
|
||||
const forecastChange = Math.min(5, forecastChangePerPriceMovement * (numIterations - 1));
|
||||
stock.otlkMag -= forecastChange;
|
||||
if (stock.otlkMag < 0.1) {
|
||||
stock.otlkMag = 0.1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the maximum number of shares of a stock that can be purchased.
|
||||
* Handles mid-transaction price movements, both L and S positions, etc.
|
||||
* Used for the "Buy Max" button in the UI
|
||||
* @param {Stock} stock - Stock being purchased
|
||||
* @param {PositionTypes} posType - Long or short position
|
||||
* @param {number} money - Amount of money player has
|
||||
* @returns maximum number of shares that the player can purchase
|
||||
*/
|
||||
export function calculateBuyMaxAmount(stock: Stock, posType: PositionTypes, money: number): number {
|
||||
if (!(stock instanceof Stock)) { return 0; }
|
||||
|
||||
const isLong = (posType === PositionTypes.Long);
|
||||
|
||||
const increasingMvmt = calculateIncreasingPriceMovement(stock);
|
||||
const decreasingMvmt = calculateDecreasingPriceMovement(stock);
|
||||
if (increasingMvmt == null || decreasingMvmt == null) { return 0; }
|
||||
|
||||
let remainingMoney = money - CONSTANTS.StockMarketCommission;
|
||||
let currPrice = isLong ? stock.getAskPrice() : stock.getBidPrice();
|
||||
|
||||
// No price movement
|
||||
const firstShares = isLong ? stock.shareTxUntilMovementUp : stock.shareTxUntilMovementDown;
|
||||
const firstIterationCost = firstShares * currPrice;
|
||||
if (remainingMoney < firstIterationCost) {
|
||||
return Math.floor(remainingMoney / currPrice);
|
||||
}
|
||||
|
||||
// We'll avoid any accidental infinite loops by having a hardcoded maximum number of
|
||||
// iterations
|
||||
let numShares = firstShares;
|
||||
remainingMoney -= firstIterationCost;
|
||||
for (let i = 0; i < 10e3; ++i) {
|
||||
if (isLong) {
|
||||
currPrice *= increasingMvmt;
|
||||
} else {
|
||||
currPrice *= decreasingMvmt;
|
||||
}
|
||||
|
||||
const affordableShares = Math.floor(remainingMoney / currPrice);
|
||||
const actualShares = Math.min(stock.shareTxForMovement, affordableShares);
|
||||
|
||||
// Can't afford any more, so we're done
|
||||
if (actualShares <= 0) { break; }
|
||||
|
||||
numShares += actualShares;
|
||||
|
||||
let cost = actualShares * currPrice;
|
||||
remainingMoney -= cost;
|
||||
|
||||
if (remainingMoney <= 0) { break; }
|
||||
}
|
||||
|
||||
return Math.floor(numShares);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user