mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-26 17:43:48 +01:00
commit
20ca7533b0
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 {
|
||||
button {
|
||||
font-size: $defaultFontSize;
|
||||
#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: inline-block;
|
||||
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
|
134
dist/engine.css → dist/engineStyle.css
vendored
134
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-list li button {
|
||||
font-size: 16px; }
|
||||
.stock-market-info-and-purchases > h2 {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
margin-left: 10px; }
|
||||
|
||||
#stock-market-container p {
|
||||
padding: 6px;
|
||||
margin: 6px;
|
||||
.stock-market-info-and-purchases > p {
|
||||
display: block;
|
||||
margin-left: 10px;
|
||||
width: 70%; }
|
||||
|
||||
#stock-market-container a {
|
||||
.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-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,16 +25,74 @@ 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
|
||||
game.
|
||||
.. 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
|
||||
later in the game.
|
||||
.. 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
|
||||
whatever the current price of the stock is. For example if you choose to short a stock
|
||||
@ -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**
|
||||
|
||||
|
@ -15,11 +15,15 @@ access even after you 'reset' by installing Augmentations
|
||||
|
||||
.. toctree::
|
||||
:caption: API Functions:
|
||||
|
||||
|
||||
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>
|
||||
|
1240
package-lock.json
generated
1240
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,
|
||||
|
@ -4173,7 +4173,7 @@ function initBladeburner() {
|
||||
"results would be catastrophic.<br><br>" +
|
||||
"We do not have the power or jurisdiction to shutdown this down " +
|
||||
"through legal or political means, so we must resort to a covert " +
|
||||
"operation. Your goal is to destroy this technology and eliminate" +
|
||||
"operation. Your goal is to destroy this technology and eliminate " +
|
||||
"anyone who was involved in its creation.",
|
||||
baseDifficulty:15e3, reqdRank:30e3,
|
||||
rankGain:750, rankLoss:60, hpLoss:1000,
|
||||
|
@ -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);
|
||||
this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]);
|
||||
this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]);
|
||||
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);
|
||||
this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]);
|
||||
this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]);
|
||||
|
||||
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;
|
||||
}
|
||||
@ -899,9 +902,11 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
|
||||
break;
|
||||
}
|
||||
|
||||
//Make sure theres enough space in warehouse
|
||||
// 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}
|
||||
/>
|
||||
|
22
src/Gang.js
22
src/Gang.js
@ -682,21 +682,25 @@ 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 +
|
||||
(task.strWeight/100) * this.str +
|
||||
(task.defWeight/100) * this.def +
|
||||
(task.dexWeight/100) * this.dex +
|
||||
(task.agiWeight/100) * this.agi +
|
||||
(task.chaWeight/100) * this.cha;
|
||||
if (task == null || !(task instanceof GangMemberTask) || task.baseWanted === 0) { return 0; }
|
||||
let statWeight = (task.hackWeight / 100) * this.hack +
|
||||
(task.strWeight / 100) * this.str +
|
||||
(task.defWeight / 100) * this.def +
|
||||
(task.dexWeight / 100) * this.dex +
|
||||
(task.agiWeight / 100) * this.agi +
|
||||
(task.chaWeight / 100) * this.cha;
|
||||
statWeight -= (3.5 * task.difficulty);
|
||||
if (statWeight <= 0) { return 0; }
|
||||
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,35 +1,41 @@
|
||||
import { HacknetNode,
|
||||
BaseCostForHacknetNode,
|
||||
HacknetNodePurchaseNextMult,
|
||||
HacknetNodeMaxLevel,
|
||||
HacknetNodeMaxRam,
|
||||
HacknetNodeMaxCores } from "./HacknetNode";
|
||||
import { HacknetServer,
|
||||
BaseCostForHacknetServer,
|
||||
HacknetServerPurchaseMult,
|
||||
HacknetServerMaxLevel,
|
||||
HacknetServerMaxRam,
|
||||
HacknetServerMaxCores,
|
||||
HacknetServerMaxCache,
|
||||
MaxNumberHacknetServers } from "./HacknetServer";
|
||||
import { HashManager } from "./HashManager";
|
||||
import { HashUpgrades } from "./HashUpgrades";
|
||||
import {
|
||||
HacknetNode,
|
||||
BaseCostForHacknetNode,
|
||||
HacknetNodePurchaseNextMult,
|
||||
HacknetNodeMaxLevel,
|
||||
HacknetNodeMaxRam,
|
||||
HacknetNodeMaxCores
|
||||
} from "./HacknetNode";
|
||||
import {
|
||||
HacknetServer,
|
||||
BaseCostForHacknetServer,
|
||||
HacknetServerPurchaseMult,
|
||||
HacknetServerMaxLevel,
|
||||
HacknetServerMaxRam,
|
||||
HacknetServerMaxCores,
|
||||
HacknetServerMaxCache,
|
||||
MaxNumberHacknetServers
|
||||
} from "./HacknetServer";
|
||||
import { HashManager } from "./HashManager";
|
||||
import { HashUpgrades } from "./HashUpgrades";
|
||||
|
||||
import { generateRandomContract } from "../CodingContractGenerator";
|
||||
import { iTutorialSteps, iTutorialNextStep,
|
||||
ITutorial} from "../InteractiveTutorial";
|
||||
import { Player } from "../Player";
|
||||
import { AddToAllServers,
|
||||
AllServers } from "../Server/AllServers";
|
||||
import { GetServerByHostname } from "../Server/ServerHelpers";
|
||||
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
|
||||
import { Page, routing } from "../ui/navigationTracking";
|
||||
import { generateRandomContract } from "../CodingContractGenerator";
|
||||
import {
|
||||
iTutorialSteps,
|
||||
iTutorialNextStep,
|
||||
ITutorial
|
||||
} from "../InteractiveTutorial";
|
||||
import { Player } from "../Player";
|
||||
import { AddToAllServers, AllServers } from "../Server/AllServers";
|
||||
import { GetServerByHostname } from "../Server/ServerHelpers";
|
||||
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
|
||||
import { Page, routing } from "../ui/navigationTracking";
|
||||
|
||||
import {getElementById} from "../../utils/uiHelpers/getElementById";
|
||||
import { getElementById } from "../../utils/uiHelpers/getElementById";
|
||||
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { HacknetRoot } from "./ui/Root";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { HacknetRoot } from "./ui/Root";
|
||||
|
||||
let hacknetNodesDiv;
|
||||
function hacknetNodesInit() {
|
||||
@ -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,
|
||||
Generic_toJSON,
|
||||
Reviver } from "../../utils/JSONReviver";
|
||||
import {
|
||||
Generic_fromJSON,
|
||||
Generic_toJSON,
|
||||
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,
|
||||
HacknetNodeMaxRam,
|
||||
HacknetNodeMaxCores } from "../HacknetNode";
|
||||
import { getMaxNumberLevelUpgrades,
|
||||
getMaxNumberRamUpgrades,
|
||||
getMaxNumberCoreUpgrades } from "../HacknetHelpers";
|
||||
import {
|
||||
HacknetNodeMaxLevel,
|
||||
HacknetNodeMaxRam,
|
||||
HacknetNodeMaxCores
|
||||
} from "../HacknetNode";
|
||||
import {
|
||||
getMaxNumberLevelUpgrades,
|
||||
getMaxNumberRamUpgrades,
|
||||
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,
|
||||
HacknetServerMaxRam,
|
||||
HacknetServerMaxCores,
|
||||
HacknetServerMaxCache } from "../HacknetServer";
|
||||
import { getMaxNumberLevelUpgrades,
|
||||
getMaxNumberRamUpgrades,
|
||||
getMaxNumberCoreUpgrades,
|
||||
getMaxNumberCacheUpgrades } from "../HacknetHelpers";
|
||||
import {
|
||||
HacknetServerMaxLevel,
|
||||
HacknetServerMaxRam,
|
||||
HacknetServerMaxCores,
|
||||
HacknetServerMaxCache
|
||||
} from "../HacknetServer";
|
||||
import {
|
||||
getMaxNumberLevelUpgrades,
|
||||
getMaxNumberRamUpgrades,
|
||||
getMaxNumberCoreUpgrades,
|
||||
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"}>
|
||||
|
@ -321,7 +321,7 @@ function iTutorialEvaluateStep() {
|
||||
Engine.loadActiveScriptsContent();
|
||||
iTutorialSetText("This page displays stats/information about all of your scripts that are " +
|
||||
"running across every existing server. You can use this to gauge how well " +
|
||||
"your scripts are doing. Let's go back to the Terminal now using the 'Terminal'" +
|
||||
"your scripts are doing. Let's go back to the Terminal now using the 'Terminal' " +
|
||||
"link.");
|
||||
nextBtn.style.display = "none";
|
||||
|
||||
|
@ -2,37 +2,42 @@
|
||||
* Location and traveling-related helper functions.
|
||||
* Mostly used for UI
|
||||
*/
|
||||
import { CONSTANTS } from "../Constants";
|
||||
import { CONSTANTS } from "../Constants";
|
||||
|
||||
import { CityName } from "./data/CityNames";
|
||||
import { CityName } from "./data/CityNames";
|
||||
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import { AllServers,
|
||||
AddToAllServers } from "../Server/AllServers";
|
||||
import { Server } from "../Server/Server";
|
||||
import { getPurchaseServerCost,
|
||||
purchaseRamForHomeComputer,
|
||||
purchaseServer } from "../Server/ServerPurchases";
|
||||
import { SpecialServerIps } from "../Server/SpecialServerIps";
|
||||
import { Settings } from "../Settings/Settings";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import {
|
||||
AddToAllServers,
|
||||
createUniqueRandomIp,
|
||||
} from "../Server/AllServers";
|
||||
import { safetlyCreateUniqueServer } from "../Server/ServerHelpers";
|
||||
import {
|
||||
getPurchaseServerCost,
|
||||
purchaseRamForHomeComputer,
|
||||
purchaseServer
|
||||
} from "../Server/ServerPurchases";
|
||||
import { SpecialServerIps } from "../Server/SpecialServerIps";
|
||||
import { Settings } from "../Settings/Settings";
|
||||
|
||||
import { numeralWrapper } from "../ui/numeralFormat";
|
||||
import { numeralWrapper } from "../ui/numeralFormat";
|
||||
|
||||
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||
import { createRandomIp } from "../../utils/IPAddress";
|
||||
import { yesNoBoxGetYesButton,
|
||||
yesNoBoxGetNoButton,
|
||||
yesNoBoxClose,
|
||||
yesNoBoxCreate,
|
||||
yesNoTxtInpBoxGetYesButton,
|
||||
yesNoTxtInpBoxGetNoButton,
|
||||
yesNoTxtInpBoxClose,
|
||||
yesNoTxtInpBoxCreate } from "../../utils/YesNoBox";
|
||||
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||
import {
|
||||
yesNoBoxGetYesButton,
|
||||
yesNoBoxGetNoButton,
|
||||
yesNoBoxClose,
|
||||
yesNoBoxCreate,
|
||||
yesNoTxtInpBoxGetYesButton,
|
||||
yesNoTxtInpBoxGetNoButton,
|
||||
yesNoTxtInpBoxClose,
|
||||
yesNoTxtInpBoxCreate
|
||||
} from "../../utils/YesNoBox";
|
||||
|
||||
import { createElement } from "../../utils/uiHelpers/createElement";
|
||||
import { createPopup } from "../../utils/uiHelpers/createPopup";
|
||||
import { createPopupCloseButton } from "../../utils/uiHelpers/createPopupCloseButton";
|
||||
import { removeElementById } from "../../utils/uiHelpers/removeElementById";
|
||||
import { createElement } from "../../utils/uiHelpers/createElement";
|
||||
import { createPopup } from "../../utils/uiHelpers/createPopup";
|
||||
import { createPopupCloseButton } from "../../utils/uiHelpers/createPopupCloseButton";
|
||||
import { removeElementById } from "../../utils/uiHelpers/removeElementById";
|
||||
|
||||
/**
|
||||
* Create a pop-up box that lets the player confirm traveling to a different city
|
||||
@ -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);
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { Message } from "./Message";
|
||||
import { Augmentatation } from "../Augmentation/Augmentation";
|
||||
import { Augmentations } from "../Augmentation/Augmentations";
|
||||
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
|
||||
import { Programs } from "../Programs/Programs";
|
||||
import { inMission } from "../Missions";
|
||||
import { Player } from "../Player";
|
||||
import { redPillFlag } from "../RedPill";
|
||||
import { GetServerByHostname } from "../Server/ServerHelpers";
|
||||
import { Settings } from "../Settings/Settings";
|
||||
import { dialogBoxCreate,
|
||||
dialogBoxOpened} from "../../utils/DialogBox";
|
||||
import { Message } from "./Message";
|
||||
import { Augmentatation } from "../Augmentation/Augmentation";
|
||||
import { Augmentations } from "../Augmentation/Augmentations";
|
||||
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
|
||||
import { Programs } from "../Programs/Programs";
|
||||
import { inMission } from "../Missions";
|
||||
import { Player } from "../Player";
|
||||
import { redPillFlag } from "../RedPill";
|
||||
import { GetServerByHostname } from "../Server/ServerHelpers";
|
||||
import { Settings } from "../Settings/Settings";
|
||||
import { dialogBoxCreate, dialogBoxOpened} from "../../utils/DialogBox";
|
||||
import { Reviver } 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,60 +35,22 @@ 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;
|
||||
}
|
||||
|
||||
//'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);
|
||||
}
|
||||
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.`);
|
||||
}
|
||||
|
||||
//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);
|
||||
}
|
||||
}
|
||||
if (requestedThreads > threads) {
|
||||
throw makeRuntimeRejectMsg(workerScript, `Too many threads requested by ${functionName}. Requested: ${requestedThreads}. Has: ${threads}.`);
|
||||
}
|
||||
workerScript.scriptRef.log("Could not find script " + scriptname + " on " + server.hostname);
|
||||
return Promise.resolve(false);
|
||||
return requestedThreadsAsInt;
|
||||
}
|
||||
|
||||
|
||||
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,8 +168,15 @@ 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) {
|
||||
fnArgs.push(arguments[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);
|
||||
@ -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,10 +304,10 @@ 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 over the tree and process ImportDeclaration nodes
|
||||
walk.simple(ast, {
|
||||
ImportDeclaration: (node) => {
|
||||
hasImports = true;
|
||||
@ -366,7 +322,7 @@ function processNetscript1Imports(code, workerScript) {
|
||||
let scriptAst = parse(script.code, {ecmaVersion:5, allowReserved:true, sourceType:"module"});
|
||||
|
||||
if (node.specifiers.length === 1 && node.specifiers[0].type === "ImportNamespaceSpecifier") {
|
||||
//import * as namespace from script
|
||||
// import * as namespace from script
|
||||
let namespace = node.specifiers[0].local.name;
|
||||
let fnNames = []; //Names only
|
||||
let fnDeclarations = []; //FunctionDeclaration Node objects
|
||||
@ -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
|
||||
@ -456,34 +413,34 @@ function processNetscript1Imports(code, workerScript) {
|
||||
return res;
|
||||
}
|
||||
|
||||
//Loop through workerScripts and run every script that is not currently running
|
||||
function runScriptsLoop() {
|
||||
var scriptDeleted = false;
|
||||
// Loop through workerScripts and run every script that is not currently running
|
||||
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--) {
|
||||
// Delete any scripts that finished or have been killed. Loop backwards bc removing items screws up indexing
|
||||
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;
|
||||
// Delete script from the runningScripts array on its host serverIp
|
||||
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
|
||||
for (let j = 0; j < workerScripts.length; j++) {
|
||||
if (workerScripts[j].serverIp !== ip) {
|
||||
continue;
|
||||
}
|
||||
if(j === i) { // not this one
|
||||
continue
|
||||
if (j === i) { // not this one
|
||||
continue;
|
||||
}
|
||||
AllServers[ip].ramUsed += workerScripts[j].ramUsage;
|
||||
}
|
||||
|
||||
//Delete script from Active Scripts
|
||||
// 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);
|
||||
@ -491,16 +448,16 @@ function runScriptsLoop() {
|
||||
}
|
||||
}
|
||||
|
||||
//Delete script from workerScripts
|
||||
// Delete script from workerScripts
|
||||
workerScripts.splice(i, 1);
|
||||
}
|
||||
}
|
||||
if (scriptDeleted) {updateActiveScriptsItems();} //Force Update
|
||||
if (scriptDeleted) { updateActiveScriptsItems(); } // Force Update
|
||||
|
||||
|
||||
//Run any scripts that haven't been started
|
||||
for (var i = 0; i < workerScripts.length; i++) {
|
||||
//If it isn't running, start the script
|
||||
// Run any scripts that haven't been started
|
||||
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.
|
||||
if (workerScripts[i].name.endsWith(".js") || workerScripts[i].name.endsWith(".ns")) {
|
||||
@ -510,8 +467,8 @@ function runScriptsLoop() {
|
||||
if (!(p instanceof Promise)) { continue; }
|
||||
}
|
||||
|
||||
//Once the code finishes (either resolved or rejected, doesnt matter), set its
|
||||
//running status to false
|
||||
// Once the code finishes (either resolved or rejected, doesnt matter), set its
|
||||
// running status to false
|
||||
p.then(function(w) {
|
||||
console.log("Stopping script " + w.name + " because it finished running naturally");
|
||||
w.running = false;
|
||||
@ -523,9 +480,9 @@ function runScriptsLoop() {
|
||||
console.error("Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + w.toString());
|
||||
return;
|
||||
} else if (w.constructor === Array && w.length === 2 && w[0] === "RETURNSTATEMENT") {
|
||||
//Script ends with a return statement
|
||||
// Script ends with a return statement
|
||||
console.log("Script returning with value: " + w[1]);
|
||||
//TODO maybe do something with this in the future
|
||||
// TODO maybe do something with this in the future
|
||||
return;
|
||||
} else if (w instanceof WorkerScript) {
|
||||
if (isScriptErrorMessage(w.errorMessage)) {
|
||||
@ -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() {
|
||||
|
@ -1,60 +1,69 @@
|
||||
import { Augmentations } from "../../Augmentation/Augmentations";
|
||||
import { applyAugmentation } from "../../Augmentation/AugmentationHelpers";
|
||||
import { PlayerOwnedAugmentation } from "../../Augmentation/PlayerOwnedAugmentation";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
||||
import { Bladeburner } from "../../Bladeburner";
|
||||
import { CodingContractRewardType } from "../../CodingContracts";
|
||||
import { Company } from "../../Company/Company";
|
||||
import { Companies } from "../../Company/Companies";
|
||||
import { getNextCompanyPositionHelper } from "../../Company/GetNextCompanyPosition";
|
||||
import { getJobRequirementText } from "../../Company/GetJobRequirementText";
|
||||
import { CompanyPositions } from "../../Company/CompanyPositions";
|
||||
import * as posNames from "../../Company/data/companypositionnames";
|
||||
import {CONSTANTS} from "../../Constants";
|
||||
import { Corporation } from "../../Corporation/Corporation";
|
||||
import { Programs } from "../../Programs/Programs";
|
||||
import { determineCrimeSuccess } from "../../Crime/CrimeHelpers";
|
||||
import { Crimes } from "../../Crime/Crimes";
|
||||
import {Engine} from "../../engine";
|
||||
import { Faction } from "../../Faction/Faction";
|
||||
import { Factions } from "../../Faction/Factions";
|
||||
import { displayFactionContent } from "../../Faction/FactionHelpers";
|
||||
import { resetGangs } from "../../Gang";
|
||||
import { hasHacknetServers } from "../../Hacknet/HacknetHelpers";
|
||||
import { HashManager } from "../../Hacknet/HashManager";
|
||||
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 {Settings} from "../../Settings/Settings";
|
||||
import {SpecialServerIps, SpecialServerNames} from "../../Server/SpecialServerIps";
|
||||
import {SourceFiles, applySourceFile} from "../../SourceFile";
|
||||
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
|
||||
import { Augmentations } from "../../Augmentation/Augmentations";
|
||||
import { applyAugmentation } from "../../Augmentation/AugmentationHelpers";
|
||||
import { PlayerOwnedAugmentation } from "../../Augmentation/PlayerOwnedAugmentation";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
||||
import { Bladeburner } from "../../Bladeburner";
|
||||
import { CodingContractRewardType } from "../../CodingContracts";
|
||||
import { Company } from "../../Company/Company";
|
||||
import { Companies } from "../../Company/Companies";
|
||||
import { getNextCompanyPositionHelper } from "../../Company/GetNextCompanyPosition";
|
||||
import { getJobRequirementText } from "../../Company/GetJobRequirementText";
|
||||
import { CompanyPositions } from "../../Company/CompanyPositions";
|
||||
import * as posNames from "../../Company/data/companypositionnames";
|
||||
import {CONSTANTS} from "../../Constants";
|
||||
import { Corporation } from "../../Corporation/Corporation";
|
||||
import { Programs } from "../../Programs/Programs";
|
||||
import { determineCrimeSuccess } from "../../Crime/CrimeHelpers";
|
||||
import { Crimes } from "../../Crime/Crimes";
|
||||
import { Engine } from "../../engine";
|
||||
import { Faction } from "../../Faction/Faction";
|
||||
import { Factions } from "../../Faction/Factions";
|
||||
import { displayFactionContent } from "../../Faction/FactionHelpers";
|
||||
import { resetGangs } from "../../Gang";
|
||||
import { hasHacknetServers } from "../../Hacknet/HacknetHelpers";
|
||||
import { HashManager } from "../../Hacknet/HashManager";
|
||||
import { Cities } from "../../Locations/Cities";
|
||||
import { Locations } from "../../Locations/Locations";
|
||||
import { CityName } from "../../Locations/data/CityNames";
|
||||
import { LocationName } from "../../Locations/data/LocationNames";
|
||||
import { Sleeve } from "../../PersonObjects/Sleeve/Sleeve";
|
||||
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";
|
||||
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
|
||||
|
||||
import Decimal from "decimal.js";
|
||||
import Decimal from "decimal.js";
|
||||
|
||||
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 {convertTimeMsToTimeElapsedString} from "../../../utils/StringHelperFunctions";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { MoneySourceTracker } from "../../utils/MoneySourceTracker";
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
import { clearEventListeners } from "../../../utils/uiHelpers/clearEventListeners";
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1,15 +1,18 @@
|
||||
/**
|
||||
* Server and HacknetServer-related methods for the Player class (PlayerObject)
|
||||
*/
|
||||
import { IPlayer } from "../IPlayer";
|
||||
import { IPlayer } from "../IPlayer";
|
||||
|
||||
import { CONSTANTS } from "../../Constants";
|
||||
import { CONSTANTS } from "../../Constants";
|
||||
|
||||
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
||||
import { HacknetServer } from "../../Hacknet/HacknetServer";
|
||||
import { AddToAllServers,
|
||||
AllServers } from "../../Server/AllServers";
|
||||
import { SpecialServerIps } from "../../Server/SpecialServerIps";
|
||||
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
||||
import { HacknetServer } from "../../Hacknet/HacknetServer";
|
||||
import {
|
||||
AddToAllServers,
|
||||
AllServers,
|
||||
createUniqueRandomIp,
|
||||
} from "../../Server/AllServers";
|
||||
import { SpecialServerIps } from "../../Server/SpecialServerIps";
|
||||
|
||||
export function hasTorRouter(this: IPlayer) {
|
||||
return SpecialServerIps.hasOwnProperty("Darkweb Server");
|
||||
@ -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,11 +1,10 @@
|
||||
// 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 {parse, Node} from "../../utils/acorn";
|
||||
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
|
||||
// construct within a user script.
|
||||
@ -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;
|
||||
}
|
||||
|
@ -1,16 +1,17 @@
|
||||
// Class representing a Script instance that is actively running.
|
||||
// A Script can have multiple active instances
|
||||
import { Script } from "./Script";
|
||||
import { FconfSettings } from "../Fconf/FconfSettings";
|
||||
import { AllServers } from "../Server/AllServers";
|
||||
import { Settings } from "../Settings/Settings";
|
||||
import { IMap } from "../types";
|
||||
import { post } from "../ui/postToTerminal";
|
||||
import { Script } from "./Script";
|
||||
import { FconfSettings } from "../Fconf/FconfSettings";
|
||||
import { Settings } from "../Settings/Settings";
|
||||
import { IMap } from "../types";
|
||||
import { post } from "../ui/postToTerminal";
|
||||
|
||||
import { Generic_fromJSON,
|
||||
Generic_toJSON,
|
||||
Reviver } from "../../utils/JSONReviver";
|
||||
import { getTimestamp } from "../../utils/helpers/getTimestamp";
|
||||
import {
|
||||
Generic_fromJSON,
|
||||
Generic_toJSON,
|
||||
Reviver
|
||||
} from "../../utils/JSONReviver";
|
||||
import { getTimestamp } from "../../utils/helpers/getTimestamp";
|
||||
|
||||
export class RunningScript {
|
||||
// Initializes a RunningScript Object from a JSON save state
|
||||
@ -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,
|
||||
Generic_toJSON,
|
||||
Reviver } from "../../utils/JSONReviver";
|
||||
import {
|
||||
Generic_fromJSON,
|
||||
Generic_toJSON,
|
||||
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,33 +1,39 @@
|
||||
import { Script } from "./Script";
|
||||
import { Script } from "./Script";
|
||||
|
||||
import { calculateRamUsage } from "./RamCalculations";
|
||||
import { isScriptFilename } from "./ScriptHelpersTS";
|
||||
import { RamCalculationErrorCode } from "./RamCalculationErrorCodes";
|
||||
import { calculateRamUsage } from "./RamCalculations";
|
||||
import { isScriptFilename } from "./ScriptHelpersTS";
|
||||
|
||||
import {CONSTANTS} from "../Constants";
|
||||
import {Engine} from "../engine";
|
||||
import { parseFconfSettings } from "../Fconf/Fconf";
|
||||
import { FconfSettings } from "../Fconf/FconfSettings";
|
||||
import {iTutorialSteps, iTutorialNextStep,
|
||||
ITutorial} from "../InteractiveTutorial";
|
||||
import { addWorkerScript } from "../NetscriptWorker";
|
||||
import { Player } from "../Player";
|
||||
import { AceEditor } from "../ScriptEditor/Ace";
|
||||
import { CodeMirrorEditor } from "../ScriptEditor/CodeMirror";
|
||||
import { AllServers } from "../Server/AllServers";
|
||||
import { processSingleServerGrowth } from "../Server/ServerHelpers";
|
||||
import { Settings } from "../Settings/Settings";
|
||||
import { EditorSetting } from "../Settings/SettingEnums";
|
||||
import { isValidFilePath } from "../Terminal/DirectoryHelpers";
|
||||
import {TextFile} from "../TextFile";
|
||||
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 { Player } from "../Player";
|
||||
import { AceEditor } from "../ScriptEditor/Ace";
|
||||
import { CodeMirrorEditor } from "../ScriptEditor/CodeMirror";
|
||||
import { AllServers } from "../Server/AllServers";
|
||||
import { processSingleServerGrowth } from "../Server/ServerHelpers";
|
||||
import { Settings } from "../Settings/Settings";
|
||||
import { EditorSetting } from "../Settings/SettingEnums";
|
||||
import { isValidFilePath } from "../Terminal/DirectoryHelpers";
|
||||
import { TextFile } from "../TextFile";
|
||||
|
||||
import {Page, routing} from "../ui/navigationTracking";
|
||||
import {numeralWrapper} from "../ui/numeralFormat";
|
||||
import { Page, routing } from "../ui/navigationTracking";
|
||||
import { numeralWrapper } from "../ui/numeralFormat";
|
||||
|
||||
import {dialogBoxCreate} from "../../utils/DialogBox";
|
||||
import {Reviver, Generic_toJSON,
|
||||
Generic_fromJSON} from "../../utils/JSONReviver";
|
||||
import {compareArrays} from "../../utils/helpers/compareArrays";
|
||||
import {createElement} from "../../utils/uiHelpers/createElement";
|
||||
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||
import {
|
||||
Reviver,
|
||||
Generic_toJSON,
|
||||
Generic_fromJSON
|
||||
} from "../../utils/JSONReviver";
|
||||
import { compareArrays } from "../../utils/helpers/compareArrays";
|
||||
import { createElement } from "../../utils/uiHelpers/createElement";
|
||||
|
||||
var scriptEditorRamCheck = null, scriptEditorRamText = null;
|
||||
export function scriptEditorInit() {
|
||||
@ -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 {
|
||||
scriptEditorRamText.innerText = "RAM: Syntax Error";
|
||||
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,15 +249,15 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
//If the current script does NOT exist, create a new one
|
||||
// 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";
|
||||
|
||||
@ -223,15 +224,15 @@ export class BaseServer {
|
||||
* Overwrites existing files. Creates new files if the script does not eixst
|
||||
*/
|
||||
writeToScriptFile(fn: string, code: string) {
|
||||
var ret = {success: false, overwritten: false};
|
||||
if (!isScriptFilename(fn)) { return ret; }
|
||||
var ret = { success: false, overwritten: false };
|
||||
if (!isValidFilePath(fn) || !isScriptFilename(fn)) { return ret; }
|
||||
|
||||
//Check if the script already exists, and overwrite it if it does
|
||||
// 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;
|
||||
@ -239,12 +240,8 @@ export class BaseServer {
|
||||
}
|
||||
}
|
||||
|
||||
//Otherwise, create a new script
|
||||
const newScript = new Script();
|
||||
newScript.filename = fn;
|
||||
newScript.code = code;
|
||||
newScript.updateRamUsage();
|
||||
newScript.server = this.ip;
|
||||
// Otherwise, create a new script
|
||||
const newScript = new Script(fn, code, this.ip, this.scripts);
|
||||
this.scripts.push(newScript);
|
||||
ret.success = true;
|
||||
return ret;
|
||||
@ -254,9 +251,9 @@ 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
|
||||
// Check if the text file already exists, and overwrite if it does
|
||||
for (let i = 0; i < this.textFiles.length; ++i) {
|
||||
if (this.textFiles[i].fn === fn) {
|
||||
ret.overwritten = true;
|
||||
@ -266,7 +263,7 @@ export class BaseServer {
|
||||
}
|
||||
}
|
||||
|
||||
//Otherwise create a new text file
|
||||
// Otherwise create a new text file
|
||||
var newFile = new TextFile(fn, txt);
|
||||
this.textFiles.push(newFile);
|
||||
ret.success = true;
|
||||
|
@ -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,13 +1,39 @@
|
||||
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";
|
||||
import { HacknetServer } from "../Hacknet/HacknetServer";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import { Programs } from "../Programs/Programs";
|
||||
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
|
||||
import { CONSTANTS } from "../Constants";
|
||||
import { HacknetServer } from "../Hacknet/HacknetServer";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import { Programs } from "../Programs/Programs";
|
||||
|
||||
import {isValidIPAddress} from "../../utils/helpers/isValidIPAddress";
|
||||
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
|
||||
@ -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,15 +2,19 @@
|
||||
* Implements functions for purchasing servers or purchasing more RAM for
|
||||
* the home computer
|
||||
*/
|
||||
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";
|
||||
import {
|
||||
AddToAllServers,
|
||||
createUniqueRandomIp,
|
||||
} from "./AllServers";
|
||||
import { safetlyCreateUniqueServer } from "./ServerHelpers";
|
||||
|
||||
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
|
||||
import { CONSTANTS } from "../Constants";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
|
||||
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||
import { yesNoTxtInpBoxGetInput } from "../../utils/YesNoBox";
|
||||
import { isPowerOfTwo } from "../../utils/helpers/isPowerOfTwo";
|
||||
|
||||
// Returns the cost of purchasing a server with the given RAM
|
||||
// Returns Infinity for invalid 'ram' arguments
|
||||
@ -66,17 +70,22 @@ export function purchaseServer(ram: number, p: IPlayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Create server
|
||||
var newServ = new Server({
|
||||
ip:createRandomIp(), hostname:hostname, organizationName:"",
|
||||
isConnectedTo:false, adminRights:true, purchasedByPlayer:true, maxRam:ram
|
||||
// Create server
|
||||
const newServ = safetlyCreateUniqueServer({
|
||||
adminRights: true,
|
||||
hostname: hostname,
|
||||
ip: createUniqueRandomIp(),
|
||||
isConnectedTo: false,
|
||||
maxRam:ram,
|
||||
organizationName: "",
|
||||
purchasedByPlayer: true,
|
||||
});
|
||||
AddToAllServers(newServ);
|
||||
|
||||
//Add to Player's purchasedServers array
|
||||
// Add to Player's purchasedServers array
|
||||
p.purchasedServers.push(newServ.ip);
|
||||
|
||||
//Connect new server to home computer
|
||||
// Connect new server to home computer
|
||||
var homeComputer = p.getHomeComputer();
|
||||
homeComputer.serversOnNetwork.push(newServ.ip);
|
||||
newServ.serversOnNetwork.push(homeComputer.ip);
|
||||
|
@ -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;
|
||||
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);
|
||||
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 = 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
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user