mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-12-30 01:47:33 +01:00
commit
4476d6b258
126
css/activescripts.scss
Normal file
126
css/activescripts.scss
Normal file
@ -0,0 +1,126 @@
|
||||
@import "theme";
|
||||
|
||||
.active-scripts-list {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
#active-scripts-container {
|
||||
position: fixed;
|
||||
padding-top: 10px;
|
||||
|
||||
> p {
|
||||
width: 70%;
|
||||
margin: 6px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.accordion-header {
|
||||
> pre {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.active-scripts-server-header {
|
||||
background-color: #444;
|
||||
font-size: $defaultFontSize * 1.25;
|
||||
color: #fff;
|
||||
margin: 6px 6px 0 6px;
|
||||
padding: 6px;
|
||||
cursor: pointer;
|
||||
width: 60%;
|
||||
text-align: left;
|
||||
border: none;
|
||||
outline: none;
|
||||
|
||||
&:after {
|
||||
content: '\02795'; /* "plus" sign (+) */
|
||||
font-size: $defaultFontSize * 0.8125;
|
||||
color: #fff;
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
&.active, &:hover {
|
||||
background-color: #555;
|
||||
}
|
||||
}
|
||||
|
||||
.active-scripts-server-header.active {
|
||||
&:after {
|
||||
content: "\2796"; /* "minus" sign (-) */
|
||||
font-size: $defaultFontSize * 0.8125;
|
||||
color: #fff;
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.active-scripts-server-panel {
|
||||
margin: 0 6px 6px 6px;
|
||||
padding: 0 6px 6px 6px;
|
||||
width: 55%;
|
||||
margin-left: 5%;
|
||||
display: none;
|
||||
|
||||
div, ul, ul > li {
|
||||
background-color: #555;
|
||||
}
|
||||
}
|
||||
|
||||
.active-scripts-script-header {
|
||||
background-color: #555;
|
||||
border: none;
|
||||
color: var(--my-font-color);
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
outline: none;
|
||||
padding: 4px 25px 4px 10px;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
width: auto;
|
||||
|
||||
&:after {
|
||||
content: '\02795'; /* "plus" sign (+) */
|
||||
font-size: $defaultFontSize * 0.8125;
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
color: transparent;
|
||||
text-shadow: 0 0 0 var(--my-font-color);
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
}
|
||||
|
||||
&.active:after {
|
||||
content: "\2796"; /* "minus" sign (-) */
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.active:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: #555;
|
||||
}
|
||||
}
|
||||
|
||||
.active-scripts-script-panel {
|
||||
background-color: #555;
|
||||
display: none;
|
||||
font-size: 14px;
|
||||
margin-bottom: 6px;
|
||||
padding: 0 18px;
|
||||
width: auto;
|
||||
|
||||
pre, h2, ul, li {
|
||||
background-color: #555;
|
||||
width: auto;
|
||||
color: #fff;
|
||||
margin-left: 5%;
|
||||
}
|
||||
}
|
31
css/augmentations.scss
Normal file
31
css/augmentations.scss
Normal file
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Styling for the Augmentations UI. This is the page that displays all of the
|
||||
* player's owned and purchased Augmentations and Source-Files. It also allows
|
||||
* the player to install Augmentations
|
||||
*/
|
||||
@import "theme";
|
||||
|
||||
#augmentations-container {
|
||||
position: fixed;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
#augmentations-content {
|
||||
> p {
|
||||
font-size: $defaultFontSize * 0.875;
|
||||
width: 70%;
|
||||
}
|
||||
}
|
||||
|
||||
.augmentations-list {
|
||||
button,
|
||||
div {
|
||||
color: var(--my-font-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
}
|
@ -18,126 +18,6 @@
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
/* Active scripts */
|
||||
.active-scripts-list {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
#active-scripts-container {
|
||||
position: fixed;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
#active-scripts-text,
|
||||
#active-scripts-total-prod {
|
||||
width: 70%;
|
||||
margin: 6px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.active-scripts-server-header {
|
||||
background-color: #444;
|
||||
font-size: $defaultFontSize * 1.25;
|
||||
color: #fff;
|
||||
margin: 6px 6px 0 6px;
|
||||
padding: 6px;
|
||||
cursor: pointer;
|
||||
width: 60%;
|
||||
text-align: left;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.active-scripts-server-header.active,
|
||||
.active-scripts-server-header:hover {
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
.active-scripts-server-header.active:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
|
||||
.active-scripts-server-header:after {
|
||||
content: '\02795'; /* "plus" sign (+) */
|
||||
font-size: $defaultFontSize * 0.8125;
|
||||
color: #fff;
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.active-scripts-server-header.active:after {
|
||||
content: "\2796"; /* "minus" sign (-) */
|
||||
font-size: $defaultFontSize * 0.8125;
|
||||
color: #fff;
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.active-scripts-server-panel {
|
||||
margin: 0 6px 6px 6px;
|
||||
padding: 0 6px 6px 6px;
|
||||
width: 55%;
|
||||
margin-left: 5%;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.active-scripts-server-panel div,
|
||||
.active-scripts-server-panel ul,
|
||||
.active-scripts-server-panel ul > li {
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
.active-scripts-script-header {
|
||||
background-color: #555;
|
||||
color: var(--my-font-color);
|
||||
padding: 4px 25px 4px 10px;
|
||||
cursor: pointer;
|
||||
width: auto;
|
||||
text-align: left;
|
||||
border: none;
|
||||
outline: none;
|
||||
position: relative;
|
||||
|
||||
&:after {
|
||||
content: '\02795'; /* "plus" sign (+) */
|
||||
font-size: $defaultFontSize * 0.8125;
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
color: transparent;
|
||||
text-shadow: 0 0 0 var(--my-font-color);
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
}
|
||||
|
||||
&.active:after {
|
||||
content: "\2796"; /* "minus" sign (-) */
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.active:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: #555;
|
||||
}
|
||||
}
|
||||
|
||||
.active-scripts-script-panel {
|
||||
padding: 0 18px;
|
||||
background-color: #555;
|
||||
width: auto;
|
||||
display: none;
|
||||
margin-bottom: 6px;
|
||||
|
||||
p, h2, ul, li {
|
||||
background-color: #555;
|
||||
width: auto;
|
||||
color: #fff;
|
||||
margin-left: 5%;
|
||||
}
|
||||
}
|
||||
|
||||
/* World */
|
||||
#world-container {
|
||||
position: fixed;
|
||||
@ -185,19 +65,6 @@
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
#faction-donate-amount-txt,
|
||||
#faction-donate-input {
|
||||
padding: 6px;
|
||||
margin: 6px;
|
||||
display: inline-block;
|
||||
color: var(--my-font-color);
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
#faction-donate-amount-txt {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
#faction-container p,
|
||||
#faction-container pre {
|
||||
padding: 4px 6px;
|
||||
@ -213,45 +80,12 @@
|
||||
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
||||
}
|
||||
|
||||
/* Faction Augmentations */
|
||||
#faction-augmentations-container {
|
||||
position: fixed;
|
||||
padding-top: 10px;
|
||||
|
||||
p, a, ul, h1 {
|
||||
margin: 8px;
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
/* World */
|
||||
#world-container li {
|
||||
margin: 0 0 15px 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
/* Augmentations */
|
||||
#augmentations-container {
|
||||
position: fixed;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.augmentations-list {
|
||||
button,
|
||||
div {
|
||||
color: var(--my-font-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 2px 5px;
|
||||
}
|
||||
|
||||
div {
|
||||
padding: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tutorial */
|
||||
#tutorial-container {
|
||||
position: fixed;
|
||||
|
@ -243,8 +243,8 @@ a:visited {
|
||||
/* Accordion menus (Header with collapsible panel) */
|
||||
.accordion-header {
|
||||
background-color: #444;
|
||||
font-size: $defaultFontSize * 1.25;
|
||||
color: #fff;
|
||||
font-size: $defaultFontSize * 1.25;
|
||||
margin: 6px 6px 0 6px;
|
||||
padding: 4px 6px;
|
||||
cursor: pointer;
|
||||
|
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
2
dist/engineStyle.bundle.js
vendored
@ -1,2 +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)}});
|
||||
!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],u[i]&&s.push(u[i][0]),u[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 r.push.apply(r,l||[]),o()}function o(){for(var n,t=0;t<r.length;t++){for(var o=r[t],e=!0,f=1;f<o.length;f++){var c=o[f];0!==u[c]&&(e=!1)}e&&(r.splice(t--,1),n=i(i.s=o[0]))}return n}var e={},u={1:0},r=[];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;r.push([361,0]),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){},352:function(n,t,o){},354:function(n,t,o){},356:function(n,t,o){},358:function(n,t,o){},361:function(n,t,o){"use strict";o.r(t);o(360),o(358),o(356),o(354),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)}});
|
||||
//# sourceMappingURL=engineStyle.bundle.js.map
|
259
dist/engineStyle.css
vendored
259
dist/engineStyle.css
vendored
@ -261,8 +261,8 @@ a:visited {
|
||||
/* Accordion menus (Header with collapsible panel) */
|
||||
.accordion-header {
|
||||
background-color: #444;
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
margin: 6px 6px 0 6px;
|
||||
padding: 4px 6px;
|
||||
cursor: pointer;
|
||||
@ -933,6 +933,104 @@ button {
|
||||
height: 30px;
|
||||
margin-left: 6px; }
|
||||
|
||||
/* COLORS */
|
||||
/* Attributes */
|
||||
.active-scripts-list {
|
||||
list-style-type: none; }
|
||||
|
||||
#active-scripts-container {
|
||||
position: fixed;
|
||||
padding-top: 10px; }
|
||||
#active-scripts-container > p {
|
||||
width: 70%;
|
||||
margin: 6px;
|
||||
padding: 4px; }
|
||||
#active-scripts-container .accordion-header > pre {
|
||||
color: white; }
|
||||
|
||||
.active-scripts-server-header {
|
||||
background-color: #444;
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
margin: 6px 6px 0 6px;
|
||||
padding: 6px;
|
||||
cursor: pointer;
|
||||
width: 60%;
|
||||
text-align: left;
|
||||
border: none;
|
||||
outline: none; }
|
||||
.active-scripts-server-header:after {
|
||||
content: '\2795';
|
||||
/* "plus" sign (+) */
|
||||
font-size: 13px;
|
||||
color: #fff;
|
||||
float: right;
|
||||
margin-left: 5px; }
|
||||
.active-scripts-server-header.active, .active-scripts-server-header:hover {
|
||||
background-color: #555; }
|
||||
|
||||
.active-scripts-server-header.active:after {
|
||||
content: "\2796";
|
||||
/* "minus" sign (-) */
|
||||
font-size: 13px;
|
||||
color: #fff;
|
||||
float: right;
|
||||
margin-left: 5px; }
|
||||
|
||||
.active-scripts-server-header.active:hover {
|
||||
background-color: #666; }
|
||||
|
||||
.active-scripts-server-panel {
|
||||
margin: 0 6px 6px 6px;
|
||||
padding: 0 6px 6px 6px;
|
||||
width: 55%;
|
||||
margin-left: 5%;
|
||||
display: none; }
|
||||
.active-scripts-server-panel div, .active-scripts-server-panel ul, .active-scripts-server-panel ul > li {
|
||||
background-color: #555; }
|
||||
|
||||
.active-scripts-script-header {
|
||||
background-color: #555;
|
||||
border: none;
|
||||
color: var(--my-font-color);
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
outline: none;
|
||||
padding: 4px 25px 4px 10px;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
width: auto; }
|
||||
.active-scripts-script-header:after {
|
||||
content: '\2795';
|
||||
/* "plus" sign (+) */
|
||||
font-size: 13px;
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
color: transparent;
|
||||
text-shadow: 0 0 0 var(--my-font-color);
|
||||
position: absolute;
|
||||
bottom: 4px; }
|
||||
.active-scripts-script-header.active:after {
|
||||
content: "\2796";
|
||||
/* "minus" sign (-) */ }
|
||||
.active-scripts-script-header:hover, .active-scripts-script-header.active:hover {
|
||||
background-color: #666; }
|
||||
.active-scripts-script-header.active {
|
||||
background-color: #555; }
|
||||
|
||||
.active-scripts-script-panel {
|
||||
background-color: #555;
|
||||
display: none;
|
||||
font-size: 14px;
|
||||
margin-bottom: 6px;
|
||||
padding: 0 18px;
|
||||
width: auto; }
|
||||
.active-scripts-script-panel pre, .active-scripts-script-panel h2, .active-scripts-script-panel ul, .active-scripts-script-panel li {
|
||||
background-color: #555;
|
||||
width: auto;
|
||||
color: #fff;
|
||||
margin-left: 5%; }
|
||||
|
||||
/* COLORS */
|
||||
/* Attributes */
|
||||
/**
|
||||
@ -1007,107 +1105,6 @@ button {
|
||||
padding-top: 10px;
|
||||
position: fixed; }
|
||||
|
||||
/* Active scripts */
|
||||
.active-scripts-list {
|
||||
list-style-type: none; }
|
||||
|
||||
#active-scripts-container {
|
||||
position: fixed;
|
||||
padding-top: 10px; }
|
||||
|
||||
#active-scripts-text,
|
||||
#active-scripts-total-prod {
|
||||
width: 70%;
|
||||
margin: 6px;
|
||||
padding: 4px; }
|
||||
|
||||
.active-scripts-server-header {
|
||||
background-color: #444;
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
margin: 6px 6px 0 6px;
|
||||
padding: 6px;
|
||||
cursor: pointer;
|
||||
width: 60%;
|
||||
text-align: left;
|
||||
border: none;
|
||||
outline: none; }
|
||||
|
||||
.active-scripts-server-header.active,
|
||||
.active-scripts-server-header:hover {
|
||||
background-color: #555; }
|
||||
|
||||
.active-scripts-server-header.active:hover {
|
||||
background-color: #666; }
|
||||
|
||||
.active-scripts-server-header:after {
|
||||
content: '\2795';
|
||||
/* "plus" sign (+) */
|
||||
font-size: 13px;
|
||||
color: #fff;
|
||||
float: right;
|
||||
margin-left: 5px; }
|
||||
|
||||
.active-scripts-server-header.active:after {
|
||||
content: "\2796";
|
||||
/* "minus" sign (-) */
|
||||
font-size: 13px;
|
||||
color: #fff;
|
||||
float: right;
|
||||
margin-left: 5px; }
|
||||
|
||||
.active-scripts-server-panel {
|
||||
margin: 0 6px 6px 6px;
|
||||
padding: 0 6px 6px 6px;
|
||||
width: 55%;
|
||||
margin-left: 5%;
|
||||
display: none; }
|
||||
|
||||
.active-scripts-server-panel div,
|
||||
.active-scripts-server-panel ul,
|
||||
.active-scripts-server-panel ul > li {
|
||||
background-color: #555; }
|
||||
|
||||
.active-scripts-script-header {
|
||||
background-color: #555;
|
||||
color: var(--my-font-color);
|
||||
padding: 4px 25px 4px 10px;
|
||||
cursor: pointer;
|
||||
width: auto;
|
||||
text-align: left;
|
||||
border: none;
|
||||
outline: none;
|
||||
position: relative; }
|
||||
.active-scripts-script-header:after {
|
||||
content: '\2795';
|
||||
/* "plus" sign (+) */
|
||||
font-size: 13px;
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
color: transparent;
|
||||
text-shadow: 0 0 0 var(--my-font-color);
|
||||
position: absolute;
|
||||
bottom: 4px; }
|
||||
.active-scripts-script-header.active:after {
|
||||
content: "\2796";
|
||||
/* "minus" sign (-) */ }
|
||||
.active-scripts-script-header:hover, .active-scripts-script-header.active:hover {
|
||||
background-color: #666; }
|
||||
.active-scripts-script-header.active {
|
||||
background-color: #555; }
|
||||
|
||||
.active-scripts-script-panel {
|
||||
padding: 0 18px;
|
||||
background-color: #555;
|
||||
width: auto;
|
||||
display: none;
|
||||
margin-bottom: 6px; }
|
||||
.active-scripts-script-panel p, .active-scripts-script-panel h2, .active-scripts-script-panel ul, .active-scripts-script-panel li {
|
||||
background-color: #555;
|
||||
width: auto;
|
||||
color: #fff;
|
||||
margin-left: 5%; }
|
||||
|
||||
/* World */
|
||||
#world-container {
|
||||
position: fixed;
|
||||
@ -1147,17 +1144,6 @@ button {
|
||||
margin: 6px;
|
||||
width: 70%; }
|
||||
|
||||
#faction-donate-amount-txt,
|
||||
#faction-donate-input {
|
||||
padding: 6px;
|
||||
margin: 6px;
|
||||
display: inline-block;
|
||||
color: var(--my-font-color);
|
||||
background-color: #000; }
|
||||
|
||||
#faction-donate-amount-txt {
|
||||
width: 50%; }
|
||||
|
||||
#faction-container p,
|
||||
#faction-container pre {
|
||||
padding: 4px 6px;
|
||||
@ -1176,35 +1162,11 @@ button {
|
||||
word-wrap: break-word;
|
||||
/* Internet Explorer 5.5+ */ }
|
||||
|
||||
/* Faction Augmentations */
|
||||
#faction-augmentations-container {
|
||||
position: fixed;
|
||||
padding-top: 10px; }
|
||||
#faction-augmentations-container p, #faction-augmentations-container a, #faction-augmentations-container ul, #faction-augmentations-container h1 {
|
||||
margin: 8px;
|
||||
padding: 4px; }
|
||||
|
||||
/* World */
|
||||
#world-container li {
|
||||
margin: 0 0 15px 0;
|
||||
list-style-type: none; }
|
||||
|
||||
/* Augmentations */
|
||||
#augmentations-container {
|
||||
position: fixed;
|
||||
padding-top: 10px; }
|
||||
|
||||
.augmentations-list button,
|
||||
.augmentations-list div {
|
||||
color: var(--my-font-color);
|
||||
text-decoration: none; }
|
||||
|
||||
.augmentations-list button {
|
||||
padding: 2px 5px; }
|
||||
|
||||
.augmentations-list div {
|
||||
padding: 6px; }
|
||||
|
||||
/* Tutorial */
|
||||
#tutorial-container {
|
||||
position: fixed;
|
||||
@ -1286,6 +1248,29 @@ button {
|
||||
display: inline;
|
||||
width: 25%; }
|
||||
|
||||
/**
|
||||
* Styling for the Augmentations UI. This is the page that displays all of the
|
||||
* player's owned and purchased Augmentations and Source-Files. It also allows
|
||||
* the player to install Augmentations
|
||||
*/
|
||||
/* COLORS */
|
||||
/* Attributes */
|
||||
#augmentations-container {
|
||||
position: fixed;
|
||||
padding-top: 10px; }
|
||||
|
||||
#augmentations-content > p {
|
||||
font-size: 14px;
|
||||
width: 70%; }
|
||||
|
||||
.augmentations-list button,
|
||||
.augmentations-list div {
|
||||
color: var(--my-font-color);
|
||||
text-decoration: none; }
|
||||
|
||||
.augmentations-list button {
|
||||
padding: 4px; }
|
||||
|
||||
/* COLORS */
|
||||
/* Attributes */
|
||||
/**
|
||||
|
26
dist/vendor.bundle.js
vendored
26
dist/vendor.bundle.js
vendored
File diff suppressed because one or more lines are too long
@ -28,6 +28,27 @@ a stock.
|
||||
.. note:: Shorting stocks is not available immediately, and must be unlocked later in the
|
||||
game.
|
||||
|
||||
Forecast & Second-Order Forecast
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
A stock's forecast is its likelihood of increasing or decreasing in value. The
|
||||
forecast is typically represented by its probability of increasing in either
|
||||
a decimal or percentage form. For example, a forecast of 70% means the stock
|
||||
has a 70% chance of increasing and a 30% chance of decreasing.
|
||||
|
||||
A stock's second-order forecast is the target value that its forecast trends towards.
|
||||
For example, if a stock has a forecast of 60% and a second-order forecast of 70%,
|
||||
then the stock's forecast should slowly trend towards 70% over time. However, this is
|
||||
determined by RNG so there is a chance that it may never reach 70%.
|
||||
|
||||
Both the forecast and the second-order forecast change over time.
|
||||
|
||||
A stock's forecast can be viewed after purchasing Four Sigma (4S) Market Data
|
||||
access. This lets you see the forecast info on the Stock Market UI. If you also
|
||||
purchase access to the 4S Market Data TIX API, then you can view a stock's forecast
|
||||
using the :js:func:`getStockForecast` function.
|
||||
|
||||
A stock's second-order forecast is always hidden.
|
||||
|
||||
.. _gameplay_stock_market_spread:
|
||||
|
||||
Spread (Bid Price & Ask Price)
|
||||
@ -51,38 +72,16 @@ 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.
|
||||
Buying or selling a large number of shares
|
||||
of a stock will influence that stock's forecast & second-order 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.
|
||||
|
||||
The effect that transactions have on a stock's second-order forecast is
|
||||
significantly smaller than the effect on its forecast.
|
||||
|
||||
.. _gameplay_stock_market_order_types:
|
||||
|
||||
@ -134,15 +133,49 @@ 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.
|
||||
.. _gameplay_stock_market_player_actions_influencing_stock:
|
||||
|
||||
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.
|
||||
Player Actions Influencing Stocks
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
It is possible for your actions elsewhere in the game to influence the stock market.
|
||||
|
||||
Hacking
|
||||
If a server has a corresponding stock (e.g. *foodnstuff* server -> FoodNStuff
|
||||
stock), then hacking that server can decrease the stock's second-order
|
||||
forecast. This causes the corresponding stock's forecast to trend downwards in value
|
||||
over time.
|
||||
|
||||
This effect only occurs if you set the *stock* option to
|
||||
true when calling the :js:func:`hack` function. The chance that hacking a
|
||||
server will cause this effect is based on what percentage of the
|
||||
server's total money you steal.
|
||||
|
||||
A single hack will have a minor
|
||||
effect, but continuously hacking a server for lots of money over time
|
||||
will have a noticeable effect in making the stock's forecast trend downwards.
|
||||
|
||||
Growing
|
||||
If a server has a corresponding stock (e.g. *foodnstuff* server -> FoodNStuff
|
||||
stock), then growing that server's money can increase the stock's
|
||||
second-order forecast. This causes the corresponding stock's
|
||||
forecast to trend upwards in value over time.
|
||||
|
||||
This effect only occurs if you set the *stock* option to true when calling the
|
||||
:js:func:`grow` function. The chance that growing a server will cause this
|
||||
effect is based on what percentage of the server's total money to add to it.
|
||||
|
||||
A single grow operation will have a minor effect, but continuously growing
|
||||
a server for lots of money over time will have a noticeable effect in making
|
||||
the stock's forecast trend upwards.
|
||||
|
||||
Working for a Company
|
||||
If a company has a corresponding stock, then working for that company will
|
||||
increase the corresponding stock's second-order forecast. This will
|
||||
cause the stock's forecast to (slowly) trend upwards in value
|
||||
over time.
|
||||
|
||||
The potency of this effect is based on how "effective" you are when you work
|
||||
(i.e. its based on your stats and multipliers).
|
||||
|
||||
Automating the Stock Market
|
||||
---------------------------
|
||||
@ -159,7 +192,8 @@ 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
|
||||
* Likelihood of increasing or decreasing (i.e. the stock's forecast)
|
||||
* Likelihood of forecast increasing or decreasing (i.e. the stock's second-order forecast)
|
||||
* How easily a stock's price/forecast is influenced by transactions
|
||||
* Spread percentage
|
||||
* Maximum price (not a real maximum, more of a "soft cap")
|
||||
|
@ -3,6 +3,21 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
v0.47.1 - 6/27/2019
|
||||
-------------------
|
||||
* Stock Market changes:
|
||||
* Transactions no longer influence stock prices (but they still influence forecast)
|
||||
* Changed the way stocks behave, particularly with regard to how the stock forecast occasionally "flips"
|
||||
* Hacking & growing a server can potentially affect the way the corresponding stock's forecast changes
|
||||
* Working for a company positively affects the way the corresponding stock's forecast changes
|
||||
|
||||
* Scripts now start/stop instantly
|
||||
* Improved performance when starting up many copies of a new NetscriptJS script (by Ornedan)
|
||||
* Improved performance when killing scripts
|
||||
* Dialog boxes can now be closed with the ESC key (by jaguilar)
|
||||
* NetscriptJS scripts should now be "re-compiled" if their dependencies change (by jaguilar)
|
||||
* write() function should now properly cause NetscriptJS scripts to "re-compile" (by jaguilar)
|
||||
|
||||
v0.47.0 - 5/17/2019
|
||||
-------------------
|
||||
* Stock Market changes:
|
||||
|
@ -8,6 +8,8 @@ grow() Netscript Function
|
||||
|
||||
* 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.
|
||||
* stock (*boolean*) - If true, the function can affect the stock market. See
|
||||
:ref:`gameplay_stock_market_player_actions_influencing_stock`
|
||||
|
||||
:returns: The number by which the money on the server was multiplied for the growth
|
||||
:RAM cost: 0.15 GB
|
||||
|
@ -8,6 +8,8 @@ hack() Netscript Function
|
||||
|
||||
* 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.
|
||||
* stock (*boolean*) - If true, the function can affect the stock market. See
|
||||
:ref:`gameplay_stock_market_player_actions_influencing_stock`
|
||||
|
||||
:returns: The amount of money stolen if the hack is successful, and zero otherwise
|
||||
:RAM cost: 0.1 GB
|
||||
|
@ -16,8 +16,6 @@ You can use the Singularity Functions in other BitNodes if and only if you have
|
||||
Source-File 4 will open up additional Singularity Functions that you can use in other BitNodes. If your Source-File 4 is upgraded all the way to
|
||||
level 3, then you will be able to access all of the Singularity Functions.
|
||||
|
||||
Note that Singularity Functions require twice as much RAM outside of BitNode-4
|
||||
|
||||
.. toctree::
|
||||
:caption: Functions:
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
getStockPurchaseCost() Netscript Function
|
||||
=========================================
|
||||
|
||||
.. js:function:: getStockPurchaseCost(sym, shares, posType)
|
||||
.. js:function:: getStockPurchaseCost(sym, shares, posType)
|
||||
|
||||
:param string sym: Stock symbol
|
||||
:param number shares: Number of shares to purchase
|
||||
@ -10,6 +10,5 @@ getStockPurchaseCost() Netscript Function
|
||||
: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>`
|
||||
shares of a stock. This takes into account :ref:`spread <gameplay_stock_market_spread>`
|
||||
and commission fees.
|
||||
|
@ -1,15 +1,14 @@
|
||||
getStockSaleGain() Netscript Function
|
||||
=====================================
|
||||
|
||||
.. js:function:: getStockSaleGain(sym, shares, posType)
|
||||
.. js:function:: getStockSaleGain(sym, shares, posType)
|
||||
|
||||
:param string sym: Stock symbol
|
||||
:param number shares: Number of shares to purchase
|
||||
:param number shares: Number of shares to sell
|
||||
: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>`
|
||||
shares of a stock. This takes into account :ref:`spread <gameplay_stock_market_spread>`
|
||||
and commission fees.
|
||||
|
14
package-lock.json
generated
14
package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bitburner",
|
||||
"version": "0.46.2",
|
||||
"version": "0.47.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@ -361,6 +361,18 @@
|
||||
"integrity": "sha512-LAQ1d4OPfSJ/BMbI2DuizmYrrkD9JMaTdi2hQTlI53lQ4kRQPyZQRS4CYQ7O66bnBBnP/oYdRxbk++X0xuFU6A==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/chai": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz",
|
||||
"integrity": "sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/mocha": {
|
||||
"version": "5.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz",
|
||||
"integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/numeral": {
|
||||
"version": "0.0.25",
|
||||
"resolved": "https://registry.npmjs.org/@types/numeral/-/numeral-0.0.25.tgz",
|
||||
|
@ -45,6 +45,8 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.3.4",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"babel-loader": "^8.0.5",
|
||||
"beautify-lint": "^1.0.3",
|
||||
"benchmark": "^2.1.1",
|
||||
|
@ -1,337 +0,0 @@
|
||||
// TODO - Convert this to React
|
||||
import { workerScripts, killWorkerScript } from "./NetscriptWorker";
|
||||
import { Player } from "./Player";
|
||||
import { getServer } from "./Server/ServerHelpers";
|
||||
|
||||
import { Page, routing } from "./ui/navigationTracking";
|
||||
import { numeralWrapper } from "./ui/numeralFormat";
|
||||
|
||||
import { dialogBoxCreate } from "../utils/DialogBox";
|
||||
import { logBoxCreate } from "../utils/LogBox";
|
||||
import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
|
||||
import { arrayToString } from "../utils/helpers/arrayToString";
|
||||
import { createProgressBarText } from "../utils/helpers/createProgressBarText";
|
||||
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
|
||||
import { roundToTwo } from "../utils/helpers/roundToTwo";
|
||||
import { createAccordionElement } from "../utils/uiHelpers/createAccordionElement";
|
||||
import { createElement } from "../utils/uiHelpers/createElement";
|
||||
import { getElementById } from "../utils/uiHelpers/getElementById";
|
||||
import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement";
|
||||
import { removeElement } from "../utils/uiHelpers/removeElement";
|
||||
|
||||
|
||||
/**
|
||||
* {
|
||||
* serverName: {
|
||||
* header: Server Header Element
|
||||
* panel: Server Panel List (ul) element
|
||||
* scripts: {
|
||||
* script id: Ref to Script information
|
||||
* }
|
||||
* }
|
||||
* ...
|
||||
*/
|
||||
const ActiveScriptsUI = {};
|
||||
const ActiveScriptsTasks = []; // Sequentially schedule the creation/deletion of UI elements
|
||||
|
||||
const getHeaderHtml = (server) => {
|
||||
// TODO: calculate the longest hostname length rather than hard coding it
|
||||
const longestHostnameLength = 18;
|
||||
const paddedName = `${server.hostname}${" ".repeat(longestHostnameLength)}`.slice(0, Math.max(server.hostname.length, longestHostnameLength));
|
||||
const barOptions = {
|
||||
progress: server.ramUsed / server.maxRam,
|
||||
totalTicks: 30
|
||||
};
|
||||
return `${paddedName} ${createProgressBarText(barOptions)}`.replace(/\s/g, ' ');
|
||||
};
|
||||
|
||||
const updateHeaderHtml = (server) => {
|
||||
const accordion = ActiveScriptsUI[server.hostname];
|
||||
if (accordion === null || accordion === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert it to a string, as that's how it's stored it will come out of the data attributes
|
||||
const ramPercentage = '' + roundToTwo(server.ramUsed / server.maxRam);
|
||||
if (accordion.header.dataset.ramPercentage !== ramPercentage) {
|
||||
accordion.header.dataset.ramPercentage = ramPercentage;
|
||||
accordion.header.innerHTML = getHeaderHtml(server);
|
||||
}
|
||||
}
|
||||
|
||||
function createActiveScriptsServerPanel(server) {
|
||||
let hostname = server.hostname;
|
||||
|
||||
var activeScriptsList = document.getElementById("active-scripts-list");
|
||||
|
||||
let res = createAccordionElement({
|
||||
hdrText: getHeaderHtml(server)
|
||||
});
|
||||
let li = res[0];
|
||||
var hdr = res[1];
|
||||
let panel = res[2];
|
||||
|
||||
if (ActiveScriptsUI[hostname] != null) {
|
||||
console.log("WARNING: Tried to create already-existing Active Scripts Server panel. This is most likely fine. It probably means many scripts just got started up on a new server. Aborting");
|
||||
return;
|
||||
}
|
||||
|
||||
var panelScriptList = createElement("ul");
|
||||
panel.appendChild(panelScriptList);
|
||||
activeScriptsList.appendChild(li);
|
||||
|
||||
ActiveScriptsUI[hostname] = {
|
||||
header: hdr,
|
||||
panel: panel,
|
||||
panelList: panelScriptList,
|
||||
scripts: {}, // Holds references to li elements for each active script
|
||||
scriptHdrs: {}, // Holds references to header elements for each active script
|
||||
scriptStats: {}, // Holds references to the p elements containing text for each active script
|
||||
};
|
||||
|
||||
return li;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the info for a particular server (Dropdown header + Panel with all info)
|
||||
* in the Active Scripts page if it exists
|
||||
*/
|
||||
function deleteActiveScriptsServerPanel(server) {
|
||||
let hostname = server.hostname;
|
||||
if (ActiveScriptsUI[hostname] == null) {
|
||||
console.log("WARNING: Tried to delete non-existent Active Scripts Server panel. Aborting");
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure it's empty
|
||||
if (Object.keys(ActiveScriptsUI[hostname].scripts).length > 0) {
|
||||
console.warn("Tried to delete Active Scripts Server panel that still has scripts. Aborting");
|
||||
return;
|
||||
}
|
||||
|
||||
removeElement(ActiveScriptsUI[hostname].panel);
|
||||
removeElement(ActiveScriptsUI[hostname].header);
|
||||
delete ActiveScriptsUI[hostname];
|
||||
}
|
||||
|
||||
function addActiveScriptsItem(workerscript) {
|
||||
var server = getServer(workerscript.serverIp);
|
||||
if (server == null) {
|
||||
console.warn("Invalid server IP for workerscript in addActiveScriptsItem()");
|
||||
return;
|
||||
}
|
||||
let hostname = server.hostname;
|
||||
|
||||
ActiveScriptsTasks.push(function(workerscript, hostname) {
|
||||
if (ActiveScriptsUI[hostname] == null) {
|
||||
createActiveScriptsServerPanel(server);
|
||||
}
|
||||
|
||||
// Create the unique identifier (key) for this script
|
||||
var itemNameArray = ["active", "scripts", hostname, workerscript.name];
|
||||
for (var i = 0; i < workerscript.args.length; ++i) {
|
||||
itemNameArray.push(String(workerscript.args[i]));
|
||||
}
|
||||
var itemName = itemNameArray.join("-");
|
||||
|
||||
let res = createAccordionElement({hdrText:workerscript.name});
|
||||
let li = res[0];
|
||||
let hdr = res[1];
|
||||
let panel = res[2];
|
||||
|
||||
hdr.classList.remove("accordion-header");
|
||||
hdr.classList.add("active-scripts-script-header");
|
||||
panel.classList.remove("accordion-panel");
|
||||
panel.classList.add("active-scripts-script-panel");
|
||||
|
||||
/**
|
||||
* Handle the constant elements on the panel that don't change after creation:
|
||||
* Threads, args, kill/log button
|
||||
*/
|
||||
panel.appendChild(createElement("p", {
|
||||
innerHTML: "Threads: " + workerscript.scriptRef.threads + "<br>" +
|
||||
"Args: " + arrayToString(workerscript.args)
|
||||
}));
|
||||
var panelText = createElement("p", {
|
||||
innerText: "Loading...",
|
||||
fontSize: "14px",
|
||||
});
|
||||
panel.appendChild(panelText);
|
||||
panel.appendChild(createElement("br"));
|
||||
panel.appendChild(createElement("span", {
|
||||
innerText: "Log",
|
||||
class: "accordion-button",
|
||||
margin: "4px",
|
||||
padding: "4px",
|
||||
clickListener: () => {
|
||||
logBoxCreate(workerscript.scriptRef);
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
panel.appendChild(createElement("span", {
|
||||
innerText: "Kill Script",
|
||||
class: "accordion-button",
|
||||
margin: "4px",
|
||||
padding: "4px",
|
||||
clickListener: () => {
|
||||
killWorkerScript(workerscript.scriptRef, workerscript.scriptRef.server);
|
||||
dialogBoxCreate("Killing script, may take a few minutes to complete...");
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
|
||||
// Append element to list
|
||||
ActiveScriptsUI[hostname]["panelList"].appendChild(li);
|
||||
ActiveScriptsUI[hostname].scripts[itemName] = li;
|
||||
ActiveScriptsUI[hostname].scriptHdrs[itemName] = hdr;
|
||||
ActiveScriptsUI[hostname].scriptStats[itemName] = panelText;
|
||||
}.bind(null, workerscript, hostname));
|
||||
}
|
||||
|
||||
function deleteActiveScriptsItem(workerscript) {
|
||||
ActiveScriptsTasks.push(function(workerscript) {
|
||||
var server = getServer(workerscript.serverIp);
|
||||
if (server == null) {
|
||||
throw new Error("ERROR: Invalid server IP for workerscript. This most likely occurred because " +
|
||||
"you tried to delete a large number of scripts and also deleted servers at the " +
|
||||
"same time. It's not a big deal, just save and refresh the game.");
|
||||
return;
|
||||
}
|
||||
let hostname = server.hostname;
|
||||
if (ActiveScriptsUI[hostname] == null) {
|
||||
console.log("ERROR: Trying to delete Active Script UI Element with a hostname that cant be found in ActiveScriptsUI: " + hostname);
|
||||
return;
|
||||
}
|
||||
|
||||
var itemNameArray = ["active", "scripts", server.hostname, workerscript.name];
|
||||
for (var i = 0; i < workerscript.args.length; ++i) {
|
||||
itemNameArray.push(String(workerscript.args[i]));
|
||||
}
|
||||
var itemName = itemNameArray.join("-");
|
||||
|
||||
let li = ActiveScriptsUI[hostname].scripts[itemName];
|
||||
if (li == null) {
|
||||
console.log("ERROR: Cannot find Active Script UI element for workerscript: ");
|
||||
console.log(workerscript);
|
||||
return;
|
||||
}
|
||||
removeElement(li);
|
||||
delete ActiveScriptsUI[hostname].scripts[itemName];
|
||||
delete ActiveScriptsUI[hostname].scriptHdrs[itemName];
|
||||
delete ActiveScriptsUI[hostname].scriptStats[itemName];
|
||||
if (Object.keys(ActiveScriptsUI[hostname].scripts).length === 0) {
|
||||
deleteActiveScriptsServerPanel(server);
|
||||
}
|
||||
}.bind(null, workerscript));
|
||||
}
|
||||
|
||||
function updateActiveScriptsItems(maxTasks=150) {
|
||||
/**
|
||||
* Run tasks that need to be done sequentially (adding items, creating/deleting server panels)
|
||||
* We'll limit this to 150 at a time for performance (in case someone decides to start a
|
||||
* bunch of scripts all at once...)
|
||||
*/
|
||||
const numTasks = Math.min(maxTasks, ActiveScriptsTasks.length);
|
||||
for (let i = 0; i < numTasks; ++i) {
|
||||
let task = ActiveScriptsTasks.shift();
|
||||
try {
|
||||
task();
|
||||
} catch(e) {
|
||||
exceptionAlert(e);
|
||||
console.log(task);
|
||||
}
|
||||
}
|
||||
|
||||
let total = 0;
|
||||
for (var i = 0; i < workerScripts.length; ++i) {
|
||||
try {
|
||||
total += updateActiveScriptsItemContent(workerScripts[i]);
|
||||
} catch(e) {
|
||||
exceptionAlert(e);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
function updateActiveScriptsItemContent(workerscript) {
|
||||
var server = getServer(workerscript.serverIp);
|
||||
if (server == null) {
|
||||
console.log("ERROR: Invalid server IP for workerscript in updateActiveScriptsItemContent().");
|
||||
return;
|
||||
}
|
||||
let hostname = server.hostname;
|
||||
if (ActiveScriptsUI[hostname] == null) {
|
||||
return; // Hasn't been created yet. We'll skip it
|
||||
}
|
||||
|
||||
updateHeaderHtml(server);
|
||||
|
||||
var itemNameArray = ["active", "scripts", server.hostname, workerscript.name];
|
||||
for (var i = 0; i < workerscript.args.length; ++i) {
|
||||
itemNameArray.push(String(workerscript.args[i]));
|
||||
}
|
||||
var itemName = itemNameArray.join("-");
|
||||
|
||||
if (ActiveScriptsUI[hostname].scriptStats[itemName] == null) {
|
||||
return; // Hasn't been fully added yet. We'll skip it
|
||||
}
|
||||
var item = ActiveScriptsUI[hostname].scriptStats[itemName];
|
||||
|
||||
// Update the text if necessary. This fn returns the online $/s production
|
||||
return updateActiveScriptsText(workerscript, item, itemName);
|
||||
}
|
||||
|
||||
function updateActiveScriptsText(workerscript, item, itemName) {
|
||||
var server = getServer(workerscript.serverIp);
|
||||
if (server == null) {
|
||||
console.log("ERROR: Invalid server IP for workerscript for updateActiveScriptsText()");
|
||||
return;
|
||||
}
|
||||
let hostname = server.hostname;
|
||||
if (ActiveScriptsUI[hostname] == null || ActiveScriptsUI[hostname].scriptHdrs[itemName] == null) {
|
||||
console.log("ERROR: Trying to update Active Script UI Element with a hostname that cant be found in ActiveScriptsUI: " + hostname);
|
||||
return;
|
||||
}
|
||||
|
||||
updateHeaderHtml(server);
|
||||
var onlineMps = workerscript.scriptRef.onlineMoneyMade / workerscript.scriptRef.onlineRunningTime;
|
||||
|
||||
// Only update if the item is visible
|
||||
if (ActiveScriptsUI[hostname].header.classList.contains("active") === false) {return onlineMps;}
|
||||
if (ActiveScriptsUI[hostname].scriptHdrs[itemName].classList.contains("active") === false) {return onlineMps;}
|
||||
|
||||
removeChildrenFromElement(item);
|
||||
|
||||
var onlineTime = "Online Time: " + convertTimeMsToTimeElapsedString(workerscript.scriptRef.onlineRunningTime * 1e3);
|
||||
var offlineTime = "Offline Time: " + convertTimeMsToTimeElapsedString(workerscript.scriptRef.offlineRunningTime * 1e3);
|
||||
|
||||
// Online
|
||||
var onlineTotalMoneyMade = "Total online production: " + numeralWrapper.formatMoney(workerscript.scriptRef.onlineMoneyMade);
|
||||
var onlineTotalExpEarned = (Array(26).join(" ") + numeralWrapper.formatBigNumber(workerscript.scriptRef.onlineExpGained) + " hacking exp").replace( / /g, " ");
|
||||
|
||||
var onlineMpsText = "Online production rate: " + numeralWrapper.formatMoney(onlineMps) + " / second";
|
||||
var onlineEps = workerscript.scriptRef.onlineExpGained / workerscript.scriptRef.onlineRunningTime;
|
||||
var onlineEpsText = (Array(25).join(" ") + numeralWrapper.formatBigNumber(onlineEps) + " hacking exp / second").replace( / /g, " ");
|
||||
|
||||
// Offline
|
||||
var offlineTotalMoneyMade = "Total offline production: " + numeralWrapper.formatMoney(workerscript.scriptRef.offlineMoneyMade);
|
||||
var offlineTotalExpEarned = (Array(27).join(" ") + numeralWrapper.formatBigNumber(workerscript.scriptRef.offlineExpGained) + " hacking exp").replace( / /g, " ");
|
||||
|
||||
var offlineMps = workerscript.scriptRef.offlineMoneyMade / workerscript.scriptRef.offlineRunningTime;
|
||||
var offlineMpsText = "Offline production rate: " + numeralWrapper.formatMoney(offlineMps) + " / second";
|
||||
var offlineEps = workerscript.scriptRef.offlineExpGained / workerscript.scriptRef.offlineRunningTime;
|
||||
var offlineEpsText = (Array(26).join(" ") + numeralWrapper.formatBigNumber(offlineEps) + " hacking exp / second").replace( / /g, " ");
|
||||
|
||||
item.innerHTML = onlineTime + "<br>" + offlineTime + "<br>" + onlineTotalMoneyMade + "<br>" + onlineTotalExpEarned + "<br>" +
|
||||
onlineMpsText + "<br>" + onlineEpsText + "<br>" + offlineTotalMoneyMade + "<br>" + offlineTotalExpEarned + "<br>" +
|
||||
offlineMpsText + "<br>" + offlineEpsText + "<br>";
|
||||
return onlineMps;
|
||||
}
|
||||
|
||||
export {addActiveScriptsItem, deleteActiveScriptsItem, updateActiveScriptsItems};
|
@ -3,10 +3,11 @@ import { Augmentations } from "./Augmentations";
|
||||
import { PlayerOwnedAugmentation } from "./PlayerOwnedAugmentation";
|
||||
import { AugmentationNames } from "./data/AugmentationNames";
|
||||
|
||||
import { AugmentationsRoot } from "./ui/Root";
|
||||
|
||||
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
|
||||
import { CONSTANTS } from "../Constants";
|
||||
import { Factions,
|
||||
factionExists } from "../Faction/Factions";
|
||||
import { Factions, factionExists } from "../Faction/Factions";
|
||||
import { addWorkerScript } from "../NetscriptWorker";
|
||||
import { Player } from "../Player";
|
||||
import { prestigeAugmentation } from "../Prestige";
|
||||
@ -16,18 +17,23 @@ import { Script } from "../Script/Script";
|
||||
import { Server } from "../Server/Server";
|
||||
import { OwnedAugmentationsOrderSetting } from "../Settings/SettingEnums";
|
||||
import { Settings } from "../Settings/Settings";
|
||||
import { Page, routing } from "../ui/navigationTracking";
|
||||
|
||||
import { SourceFiles } from "../SourceFile";
|
||||
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||
import { createAccordionElement } from "../../utils/uiHelpers/createAccordionElement";
|
||||
import { Reviver, Generic_toJSON,
|
||||
Generic_fromJSON } from "../../utils/JSONReviver";
|
||||
import {
|
||||
Reviver,
|
||||
Generic_toJSON,
|
||||
Generic_fromJSON
|
||||
} from "../../utils/JSONReviver";
|
||||
import { formatNumber } from "../../utils/StringHelperFunctions";
|
||||
import { clearObject } from "../../utils/helpers/clearObject";
|
||||
import { createElement } from "../../utils/uiHelpers/createElement";
|
||||
import { isString } from "../../utils/helpers/isString";
|
||||
import { removeChildrenFromElement } from "../../utils/uiHelpers/removeChildrenFromElement";
|
||||
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
function AddToAugmentations(aug) {
|
||||
var name = aug.name;
|
||||
@ -2041,17 +2047,6 @@ function applyAugmentation(aug, reapply=false) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if (aug.name === AugmentationNames.NeuroFluxGovernor) {
|
||||
for (var i = 0; i < Player.augmentations.length; ++i) {
|
||||
if (Player.augmentations[i].name == AugmentationNames.NeuroFluxGovernor) {
|
||||
//Already have this aug, just upgrade the level
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Push onto Player's Augmentation list
|
||||
if (!reapply) {
|
||||
var ownedAug = new PlayerOwnedAugmentation(aug.name);
|
||||
@ -2104,211 +2099,17 @@ function augmentationExists(name) {
|
||||
return Augmentations.hasOwnProperty(name);
|
||||
}
|
||||
|
||||
function displayAugmentationsContent(contentEl) {
|
||||
removeChildrenFromElement(contentEl);
|
||||
contentEl.appendChild(createElement("h1", {
|
||||
innerText:"Purchased Augmentations",
|
||||
}));
|
||||
export function displayAugmentationsContent(contentEl) {
|
||||
if (!routing.isOn(Page.Augmentations)) { return; }
|
||||
if (!(contentEl instanceof HTMLElement)) { return; }
|
||||
|
||||
contentEl.appendChild(createElement("pre", {
|
||||
width:"70%", whiteSpace:"pre-wrap", display:"block",
|
||||
innerText:"Below is a list of all Augmentations you have purchased but not yet installed. Click the button below to install them.\n" +
|
||||
"WARNING: Installing your Augmentations resets most of your progress, including:\n\n" +
|
||||
"Stats/Skill levels and Experience\n" +
|
||||
"Money\n" +
|
||||
"Scripts on every computer but your home computer\n" +
|
||||
"Purchased servers\n" +
|
||||
"Hacknet Nodes\n" +
|
||||
"Faction/Company reputation\n" +
|
||||
"Stocks\n" +
|
||||
"Installing Augmentations lets you start over with the perks and benefits granted by all " +
|
||||
"of the Augmentations you have ever installed. Also, you will keep any scripts and RAM/Core upgrades " +
|
||||
"on your home computer (but you will lose all programs besides NUKE.exe)."
|
||||
}));
|
||||
|
||||
//Install Augmentations button
|
||||
contentEl.appendChild(createElement("a", {
|
||||
class:"a-link-button", innerText:"Install Augmentations",
|
||||
tooltip:"'I never asked for this'",
|
||||
clickListener:()=>{
|
||||
installAugmentations();
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
|
||||
//Backup button
|
||||
contentEl.appendChild(createElement("a", {
|
||||
class:"a-link-button flashing-button", innerText:"Backup Save (Export)",
|
||||
tooltip:"It's always a good idea to backup/export your save!",
|
||||
clickListener:()=>{
|
||||
saveObject.exportGame();
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
|
||||
//Purchased/queued augmentations list
|
||||
var queuedAugmentationsList = createElement("ul", {class:"augmentations-list"});
|
||||
|
||||
for (var i = 0; i < Player.queuedAugmentations.length; ++i) {
|
||||
var augName = Player.queuedAugmentations[i].name;
|
||||
var aug = Augmentations[augName];
|
||||
|
||||
var displayName = augName;
|
||||
if (augName === AugmentationNames.NeuroFluxGovernor) {
|
||||
displayName += " - Level " + (Player.queuedAugmentations[i].level);
|
||||
}
|
||||
|
||||
var accordion = createAccordionElement({hdrText:displayName, panelText:aug.info});
|
||||
queuedAugmentationsList.appendChild(accordion[0]);
|
||||
}
|
||||
contentEl.appendChild(queuedAugmentationsList);
|
||||
|
||||
//Installed augmentations list
|
||||
contentEl.appendChild(createElement("h1", {
|
||||
innerText:"Installed Augmentations", marginTop:"8px",
|
||||
}));
|
||||
contentEl.appendChild(createElement("p", {
|
||||
width:"70%", whiteSpace:"pre-wrap",
|
||||
innerText:"List of all Augmentations (including Source Files) that have been " +
|
||||
"installed. You have gained the effects of these Augmentations."
|
||||
}));
|
||||
|
||||
var augmentationsList = createElement("ul", {class:"augmentations-list"});
|
||||
|
||||
//Expand/Collapse All buttons
|
||||
contentEl.appendChild(createElement("a", {
|
||||
class:"a-link-button", fontSize:"14px", innerText:"Expand All", display:"inline-block",
|
||||
clickListener:()=>{
|
||||
var allHeaders = augmentationsList.getElementsByClassName("accordion-header");
|
||||
for (var i = 0; i < allHeaders.length; ++i) {
|
||||
if (!allHeaders[i].classList.contains("active")) {allHeaders[i].click();}
|
||||
}
|
||||
}
|
||||
}));
|
||||
contentEl.appendChild(createElement("a", {
|
||||
class:"a-link-button", fontSize:"14px", innerText:"Collapse All", display:"inline-block",
|
||||
clickListener:()=>{
|
||||
var allHeaders = augmentationsList.getElementsByClassName("accordion-header");
|
||||
for (var i = 0; i < allHeaders.length; ++i) {
|
||||
if (allHeaders[i].classList.contains("active")) {allHeaders[i].click();}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
//Sort Buttons
|
||||
const sortInOrderButton = createElement("a", {
|
||||
class:"a-link-button", fontSize:"14px", innerText:"Sort in Order",
|
||||
tooltip:"Sorts the Augmentations alphabetically and Source Files in numerical order (1, 2, 3,...)",
|
||||
clickListener:()=>{
|
||||
removeChildrenFromElement(augmentationsList);
|
||||
|
||||
//Create a copy of Player's Source Files and augs array and sort them
|
||||
var sourceFiles = Player.sourceFiles.slice();
|
||||
var augs = Player.augmentations.slice();
|
||||
sourceFiles.sort((sf1, sf2)=>{
|
||||
return sf1.n - sf2.n;
|
||||
});
|
||||
augs.sort((aug1, aug2)=>{
|
||||
return aug1.name <= aug2.name ? -1 : 1;
|
||||
});
|
||||
displaySourceFiles(augmentationsList, sourceFiles);
|
||||
displayAugmentations(augmentationsList, augs);
|
||||
|
||||
Settings.OwnedAugmentationsOrder = OwnedAugmentationsOrderSetting.Alphabetically;
|
||||
}
|
||||
});
|
||||
contentEl.appendChild(sortInOrderButton);
|
||||
|
||||
const sortByAcquirementTimeButton = createElement("a", {
|
||||
class:"a-link-button", fontSize:"14px", innerText:"Sort by Acquirement Time",
|
||||
tooltip:"Sorts the Augmentations and Source Files based on when you acquired them (same as default)",
|
||||
clickListener:()=>{
|
||||
removeChildrenFromElement(augmentationsList);
|
||||
displaySourceFiles(augmentationsList, Player.sourceFiles);
|
||||
displayAugmentations(augmentationsList, Player.augmentations);
|
||||
|
||||
Settings.OwnedAugmentationsOrder = OwnedAugmentationsOrderSetting.AcquirementTime;
|
||||
}
|
||||
});
|
||||
contentEl.appendChild(sortByAcquirementTimeButton);
|
||||
|
||||
if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) {
|
||||
sortInOrderButton.click();
|
||||
} else {
|
||||
sortByAcquirementTimeButton.click();
|
||||
}
|
||||
contentEl.appendChild(augmentationsList);
|
||||
|
||||
// Display multiplier information at the bottom
|
||||
contentEl.appendChild(createElement("p", {
|
||||
display: "block",
|
||||
innerHTML:
|
||||
`<br><br><strong><u>Total Multipliers:</u></strong><br>` +
|
||||
'Hacking Chance multiplier: ' + formatNumber(Player.hacking_chance_mult * 100, 2) + '%<br>' +
|
||||
'Hacking Speed multiplier: ' + formatNumber(Player.hacking_speed_mult * 100, 2) + '%<br>' +
|
||||
'Hacking Money multiplier: ' + formatNumber(Player.hacking_money_mult * 100, 2) + '%<br>' +
|
||||
'Hacking Growth multiplier: ' + formatNumber(Player.hacking_grow_mult * 100, 2) + '%<br><br>' +
|
||||
'Hacking Level multiplier: ' + formatNumber(Player.hacking_mult * 100, 2) + '%<br>' +
|
||||
'Hacking Experience multiplier: ' + formatNumber(Player.hacking_exp_mult * 100, 2) + '%<br><br>' +
|
||||
'Strength Level multiplier: ' + formatNumber(Player.strength_mult * 100, 2) + '%<br>' +
|
||||
'Strength Experience multiplier: ' + formatNumber(Player.strength_exp_mult * 100, 2) + '%<br><br>' +
|
||||
'Defense Level multiplier: ' + formatNumber(Player.defense_mult * 100, 2) + '%<br>' +
|
||||
'Defense Experience multiplier: ' + formatNumber(Player.defense_exp_mult * 100, 2) + '%<br><br>' +
|
||||
'Dexterity Level multiplier: ' + formatNumber(Player.dexterity_mult * 100, 2) + '%<br>' +
|
||||
'Dexterity Experience multiplier: ' + formatNumber(Player.dexterity_exp_mult * 100, 2) + '%<br><br>' +
|
||||
'Agility Level multiplier: ' + formatNumber(Player.agility_mult * 100, 2) + '%<br>' +
|
||||
'Agility Experience multiplier: ' + formatNumber(Player.agility_exp_mult * 100, 2) + '%<br><br>' +
|
||||
'Charisma Level multiplier: ' + formatNumber(Player.charisma_mult * 100, 2) + '%<br>' +
|
||||
'Charisma Experience multiplier: ' + formatNumber(Player.charisma_exp_mult * 100, 2) + '%<br><br>' +
|
||||
'Hacknet Node production multiplier: ' + formatNumber(Player.hacknet_node_money_mult * 100, 2) + '%<br>' +
|
||||
'Hacknet Node purchase cost multiplier: ' + formatNumber(Player.hacknet_node_purchase_cost_mult * 100, 2) + '%<br>' +
|
||||
'Hacknet Node RAM upgrade cost multiplier: ' + formatNumber(Player.hacknet_node_ram_cost_mult * 100, 2) + '%<br>' +
|
||||
'Hacknet Node Core purchase cost multiplier: ' + formatNumber(Player.hacknet_node_core_cost_mult * 100, 2) + '%<br>' +
|
||||
'Hacknet Node level upgrade cost multiplier: ' + formatNumber(Player.hacknet_node_level_cost_mult * 100, 2) + '%<br><br>' +
|
||||
'Company reputation gain multiplier: ' + formatNumber(Player.company_rep_mult * 100, 2) + '%<br>' +
|
||||
'Faction reputation gain multiplier: ' + formatNumber(Player.faction_rep_mult * 100, 2) + '%<br>' +
|
||||
'Salary multiplier: ' + formatNumber(Player.work_money_mult * 100, 2) + '%<br>' +
|
||||
'Crime success multiplier: ' + formatNumber(Player.crime_success_mult * 100, 2) + '%<br>' +
|
||||
'Crime money multiplier: ' + formatNumber(Player.crime_money_mult * 100, 2) + '%<br><br><br>',
|
||||
}))
|
||||
}
|
||||
|
||||
//Creates the accordion elements to display Augmentations
|
||||
// @listElement - List DOM element to append accordion elements to
|
||||
// @augs - Array of Augmentation objects
|
||||
function displayAugmentations(listElement, augs) {
|
||||
for (var i = 0; i < augs.length; ++i) {
|
||||
var augName = augs[i].name;
|
||||
var aug = Augmentations[augName];
|
||||
|
||||
var displayName = augName;
|
||||
if (augName === AugmentationNames.NeuroFluxGovernor) {
|
||||
displayName += " - Level " + (augs[i].level);
|
||||
}
|
||||
var accordion = createAccordionElement({hdrText:displayName, panelText:aug.info});
|
||||
listElement.appendChild(accordion[0]);
|
||||
}
|
||||
}
|
||||
|
||||
//Creates the accordion elements to display Source Files
|
||||
// @listElement - List DOM element to append accordion elements to
|
||||
// @sourceFiles - Array of Source File objects
|
||||
function displaySourceFiles(listElement, sourceFiles) {
|
||||
for (var i = 0; i < sourceFiles.length; ++i) {
|
||||
var srcFileKey = "SourceFile" + sourceFiles[i].n;
|
||||
var sourceFileObject = SourceFiles[srcFileKey];
|
||||
if (sourceFileObject == null) {
|
||||
console.log("ERROR: Invalid source file number: " + sourceFiles[i].n);
|
||||
continue;
|
||||
}
|
||||
const maxLevel = sourceFiles[i].n == 12 ? "∞" : "3";
|
||||
var accordion = createAccordionElement({
|
||||
hdrText:sourceFileObject.name + "<br>" + "Level " + (sourceFiles[i].lvl) + " / "+maxLevel,
|
||||
panelText:sourceFileObject.info
|
||||
});
|
||||
|
||||
listElement.appendChild(accordion[0]);
|
||||
}
|
||||
ReactDOM.render(
|
||||
<AugmentationsRoot
|
||||
exportGameFn={saveObject.exportGame.bind(saveObject)}
|
||||
installAugmentationsFn={installAugmentations}
|
||||
/>,
|
||||
contentEl
|
||||
);
|
||||
}
|
||||
|
||||
export function isRepeatableAug(aug) {
|
||||
@ -2319,6 +2120,9 @@ export function isRepeatableAug(aug) {
|
||||
return false;
|
||||
}
|
||||
|
||||
export {installAugmentations,
|
||||
initAugmentations, applyAugmentation, augmentationExists,
|
||||
displayAugmentationsContent};
|
||||
export {
|
||||
installAugmentations,
|
||||
initAugmentations,
|
||||
applyAugmentation,
|
||||
augmentationExists,
|
||||
};
|
42
src/Augmentation/ui/InstalledAugmentations.tsx
Normal file
42
src/Augmentation/ui/InstalledAugmentations.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* React Component for displaying a list of the player's installed Augmentations
|
||||
* on the Augmentations UI
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
import { Player } from "../../Player";
|
||||
import { Augmentations } from "../../Augmentation/Augmentations";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums";
|
||||
|
||||
import { AugmentationAccordion } from "../../ui/React/AugmentationAccordion";
|
||||
|
||||
export function InstalledAugmentations(): React.ReactElement {
|
||||
const sourceAugs = Player.augmentations.slice();
|
||||
|
||||
if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) {
|
||||
sourceAugs.sort((aug1, aug2) => {
|
||||
return aug1.name <= aug2.name ? -1 : 1;
|
||||
});
|
||||
}
|
||||
|
||||
const augs = sourceAugs.map((e) => {
|
||||
const aug = Augmentations[e.name];
|
||||
|
||||
let level = null;
|
||||
if (e.name === AugmentationNames.NeuroFluxGovernor) {
|
||||
level = e.level;
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={e.name}>
|
||||
<AugmentationAccordion aug={aug} level={level} />
|
||||
</li>
|
||||
)
|
||||
});
|
||||
|
||||
return (
|
||||
<>{augs}</>
|
||||
)
|
||||
}
|
107
src/Augmentation/ui/InstalledAugmentationsAndSourceFiles.tsx
Normal file
107
src/Augmentation/ui/InstalledAugmentationsAndSourceFiles.tsx
Normal file
@ -0,0 +1,107 @@
|
||||
/**
|
||||
* React Component for displaying all of the player's installed Augmentations and
|
||||
* Source-Files.
|
||||
*
|
||||
* It also contains 'configuration' buttons that allow you to change how the
|
||||
* Augs/SF's are displayed
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
import { InstalledAugmentations } from "./InstalledAugmentations";
|
||||
import { ListConfiguration } from "./ListConfiguration";
|
||||
import { OwnedSourceFiles } from "./OwnedSourceFiles";
|
||||
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums";
|
||||
|
||||
type IProps = {}
|
||||
|
||||
type IState = {
|
||||
rerenderFlag: boolean;
|
||||
}
|
||||
|
||||
export class InstalledAugmentationsAndSourceFiles extends React.Component<IProps, IState> {
|
||||
listRef: React.RefObject<HTMLUListElement>;
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
rerenderFlag: false,
|
||||
}
|
||||
|
||||
this.collapseAllHeaders = this.collapseAllHeaders.bind(this);
|
||||
this.expandAllHeaders = this.expandAllHeaders.bind(this);
|
||||
this.sortByAcquirementTime = this.sortByAcquirementTime.bind(this);
|
||||
this.sortInOrder = this.sortInOrder.bind(this);
|
||||
|
||||
this.listRef = React.createRef();
|
||||
}
|
||||
|
||||
collapseAllHeaders() {
|
||||
const ul = this.listRef.current;
|
||||
if (ul == null) { return; }
|
||||
const tickers = ul.getElementsByClassName("accordion-header");
|
||||
for (let i = 0; i < tickers.length; ++i) {
|
||||
const ticker = tickers[i];
|
||||
if (!(ticker instanceof HTMLButtonElement)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ticker.classList.contains("active")) {
|
||||
ticker.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expandAllHeaders() {
|
||||
const ul = this.listRef.current;
|
||||
if (ul == null) { return; }
|
||||
const tickers = ul.getElementsByClassName("accordion-header");
|
||||
for (let i = 0; i < tickers.length; ++i) {
|
||||
const ticker = tickers[i];
|
||||
if (!(ticker instanceof HTMLButtonElement)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ticker.classList.contains("active")) {
|
||||
ticker.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rerender() {
|
||||
this.setState((prevState) => {
|
||||
return {
|
||||
rerenderFlag: !prevState.rerenderFlag,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sortByAcquirementTime() {
|
||||
Settings.OwnedAugmentationsOrder = OwnedAugmentationsOrderSetting.AcquirementTime;
|
||||
this.rerender();
|
||||
}
|
||||
|
||||
sortInOrder() {
|
||||
Settings.OwnedAugmentationsOrder = OwnedAugmentationsOrderSetting.Alphabetically
|
||||
this.rerender();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<ListConfiguration
|
||||
collapseAllButtonsFn={this.collapseAllHeaders}
|
||||
expandAllButtonsFn={this.expandAllHeaders}
|
||||
sortByAcquirementTimeFn={this.sortByAcquirementTime}
|
||||
sortInOrderFn={this.sortInOrder}
|
||||
/>
|
||||
<ul className="augmentations-list" ref={this.listRef}>
|
||||
<OwnedSourceFiles />
|
||||
<InstalledAugmentations />
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
39
src/Augmentation/ui/ListConfiguration.tsx
Normal file
39
src/Augmentation/ui/ListConfiguration.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* React Component for configuring the way installed augmentations and
|
||||
* Source-Files are displayed in the Augmentations UI
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
import { StdButton } from "../../ui/React/StdButton";
|
||||
|
||||
type IProps = {
|
||||
collapseAllButtonsFn: () => void;
|
||||
expandAllButtonsFn: () => void;
|
||||
sortByAcquirementTimeFn: () => void;
|
||||
sortInOrderFn: () => void;
|
||||
}
|
||||
|
||||
export function ListConfiguration(props: IProps): React.ReactElement {
|
||||
return (
|
||||
<>
|
||||
<StdButton
|
||||
onClick={props.expandAllButtonsFn}
|
||||
text="Expand All"
|
||||
/>
|
||||
<StdButton
|
||||
onClick={props.collapseAllButtonsFn}
|
||||
text="Collapse All"
|
||||
/>
|
||||
<StdButton
|
||||
onClick={props.sortInOrderFn}
|
||||
text="Sort in Order"
|
||||
tooltip="Sorts the Augmentations alphabetically and Source-Files in numeral order"
|
||||
/>
|
||||
<StdButton
|
||||
onClick={props.sortByAcquirementTimeFn}
|
||||
text="Sort by Acquirement Time"
|
||||
tooltip="Sorts the Augmentations and Source-Files based on when you acquired them (same as default)"
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
41
src/Augmentation/ui/OwnedSourceFiles.tsx
Normal file
41
src/Augmentation/ui/OwnedSourceFiles.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* React Component for displaying a list of the player's Source-Files
|
||||
* on the Augmentations UI
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
import { Player } from "../../Player";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums";
|
||||
import { SourceFiles } from "../../SourceFile/SourceFiles";
|
||||
|
||||
import { SourceFileAccordion } from "../../ui/React/SourceFileAccordion";
|
||||
|
||||
export function OwnedSourceFiles(): React.ReactElement {
|
||||
const sourceSfs = Player.sourceFiles.slice();
|
||||
|
||||
if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) {
|
||||
sourceSfs.sort((sf1, sf2) => {
|
||||
return sf1.n - sf2.n;
|
||||
});
|
||||
}
|
||||
|
||||
const sfs = sourceSfs.map((e) => {
|
||||
const srcFileKey = "SourceFile" + e.n;
|
||||
const sfObj = SourceFiles[srcFileKey];
|
||||
if (sfObj == null) {
|
||||
console.error(`Invalid source file number: ${e.n}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={e.n}>
|
||||
<SourceFileAccordion level={e.lvl} sf={sfObj} />
|
||||
</li>
|
||||
)
|
||||
});
|
||||
|
||||
return (
|
||||
<>{sfs}</>
|
||||
);
|
||||
}
|
96
src/Augmentation/ui/PlayerMultipliers.tsx
Normal file
96
src/Augmentation/ui/PlayerMultipliers.tsx
Normal file
@ -0,0 +1,96 @@
|
||||
/**
|
||||
* React component for displaying the player's multipliers on the Augmentation UI page
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
import { Player } from "../../Player";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
|
||||
export function PlayerMultipliers(): React.ReactElement {
|
||||
return (
|
||||
<>
|
||||
<p><strong><u>Total Multipliers:</u></strong></p>
|
||||
|
||||
<pre>
|
||||
{'Hacking Chance multiplier: ' + numeralWrapper.formatPercentage(Player.hacking_chance_mult)}
|
||||
</pre>
|
||||
<pre>
|
||||
{'Hacking Speed multiplier: ' + numeralWrapper.formatPercentage(Player.hacking_speed_mult)}
|
||||
</pre>
|
||||
<pre>
|
||||
{'Hacking Money multiplier: ' + numeralWrapper.formatPercentage(Player.hacking_money_mult)}
|
||||
</pre>
|
||||
<pre>
|
||||
{'Hacking Growth multiplier: ' + numeralWrapper.formatPercentage(Player.hacking_grow_mult)}
|
||||
</pre><br />
|
||||
<pre>
|
||||
{'Hacking Level multiplier: ' + numeralWrapper.formatPercentage(Player.hacking_mult)}
|
||||
</pre>
|
||||
<pre>
|
||||
{'Hacking Experience multiplier: ' + numeralWrapper.formatPercentage(Player.hacking_exp_mult)}
|
||||
</pre>
|
||||
<br />
|
||||
<pre>
|
||||
{'Strength Level multiplier: ' + numeralWrapper.formatPercentage(Player.strength_mult)}
|
||||
</pre>
|
||||
<pre>
|
||||
{'Strength Experience multiplier: ' + numeralWrapper.formatPercentage(Player.strength_exp_mult)}
|
||||
</pre>
|
||||
<br />
|
||||
<pre>
|
||||
{'Defense Level multiplier: ' + numeralWrapper.formatPercentage(Player.defense_mult)}
|
||||
</pre>
|
||||
<pre>
|
||||
{'Defense Experience multiplier: ' + numeralWrapper.formatPercentage(Player.defense_exp_mult)}
|
||||
</pre><br />
|
||||
<pre>
|
||||
{'Dexterity Level multiplier: ' + numeralWrapper.formatPercentage(Player.dexterity_mult)}
|
||||
</pre>
|
||||
<pre>
|
||||
{'Dexterity Experience multiplier: ' + numeralWrapper.formatPercentage(Player.dexterity_exp_mult)}
|
||||
</pre><br />
|
||||
<pre>
|
||||
{'Agility Level multiplier: ' + numeralWrapper.formatPercentage(Player.agility_mult)}
|
||||
</pre>
|
||||
<pre>
|
||||
{'Agility Experience multiplier: ' + numeralWrapper.formatPercentage(Player.agility_exp_mult)}
|
||||
</pre><br />
|
||||
<pre>
|
||||
{'Charisma Level multiplier: ' + numeralWrapper.formatPercentage(Player.charisma_mult)}
|
||||
</pre>
|
||||
<pre>
|
||||
{'Charisma Experience multiplier: ' + numeralWrapper.formatPercentage(Player.charisma_exp_mult)}
|
||||
</pre><br />
|
||||
<pre>
|
||||
{'Hacknet Node production multiplier: ' + numeralWrapper.formatPercentage(Player.hacknet_node_money_mult)}
|
||||
</pre>
|
||||
<pre>
|
||||
{'Hacknet Node purchase cost multiplier: ' + numeralWrapper.formatPercentage(Player.hacknet_node_purchase_cost_mult)}
|
||||
</pre>
|
||||
<pre>
|
||||
{'Hacknet Node RAM upgrade cost multiplier: ' + numeralWrapper.formatPercentage(Player.hacknet_node_ram_cost_mult)}
|
||||
</pre>
|
||||
<pre>
|
||||
{'Hacknet Node Core purchase cost multiplier: ' + numeralWrapper.formatPercentage(Player.hacknet_node_core_cost_mult)}
|
||||
</pre>
|
||||
<pre>
|
||||
{'Hacknet Node level upgrade cost multiplier: ' + numeralWrapper.formatPercentage(Player.hacknet_node_level_cost_mult)}
|
||||
</pre><br />
|
||||
<pre>
|
||||
{'Company reputation gain multiplier: ' + numeralWrapper.formatPercentage(Player.company_rep_mult)}
|
||||
</pre>
|
||||
<pre>
|
||||
{'Faction reputation gain multiplier: ' + numeralWrapper.formatPercentage(Player.faction_rep_mult)}
|
||||
</pre>
|
||||
<pre>
|
||||
{'Salary multiplier: ' + numeralWrapper.formatPercentage(Player.work_money_mult)}
|
||||
</pre><br />
|
||||
<pre>
|
||||
{'Crime success multiplier: ' + numeralWrapper.formatPercentage(Player.crime_success_mult)}
|
||||
</pre>
|
||||
<pre>
|
||||
{'Crime money multiplier: ' + numeralWrapper.formatPercentage(Player.crime_money_mult)}
|
||||
</pre>
|
||||
</>
|
||||
)
|
||||
}
|
32
src/Augmentation/ui/PurchasedAugmentations.tsx
Normal file
32
src/Augmentation/ui/PurchasedAugmentations.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* React component for displaying all of the player's purchased (but not installed)
|
||||
* Augmentations on the Augmentations UI.
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
import { Augmentations } from "../../Augmentation/Augmentations";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
import { Player } from "../../Player";
|
||||
|
||||
import { AugmentationAccordion } from "../../ui/React/AugmentationAccordion";
|
||||
|
||||
export function PurchasedAugmentations(): React.ReactElement {
|
||||
const augs: React.ReactElement[] = [];
|
||||
for (const ownedAug of Player.queuedAugmentations) {
|
||||
const aug = Augmentations[ownedAug.name];
|
||||
let level = null;
|
||||
if (ownedAug.name === AugmentationNames.NeuroFluxGovernor) {
|
||||
level = ownedAug.level;
|
||||
}
|
||||
|
||||
augs.push(
|
||||
<li key={`${ownedAug.name}${ownedAug.level}`}>
|
||||
<AugmentationAccordion aug={aug} level={level} />
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className="augmentations-list">{augs}</ul>
|
||||
)
|
||||
}
|
83
src/Augmentation/ui/Root.tsx
Normal file
83
src/Augmentation/ui/Root.tsx
Normal file
@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Root React component for the Augmentations UI page that display all of your
|
||||
* owned and purchased Augmentations and Source-Files.
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
import { InstalledAugmentationsAndSourceFiles } from "./InstalledAugmentationsAndSourceFiles";
|
||||
import { PlayerMultipliers } from "./PlayerMultipliers";
|
||||
import { PurchasedAugmentations } from "./PurchasedAugmentations";
|
||||
|
||||
import { Player } from "../../Player";
|
||||
import { StdButton } from "../../ui/React/StdButton";
|
||||
|
||||
type IProps = {
|
||||
exportGameFn: () => void;
|
||||
installAugmentationsFn: () => void;
|
||||
}
|
||||
|
||||
type IState = {
|
||||
|
||||
}
|
||||
|
||||
export class AugmentationsRoot extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div id="augmentations-content">
|
||||
<h1>Purchased Augmentations</h1>
|
||||
<p>
|
||||
Below is a list of all Augmentations you have purchased but not
|
||||
yet installed. Click the button below to install them.
|
||||
</p>
|
||||
<p>
|
||||
WARNING: Installing your Augmentations resets most of your progress,
|
||||
including:
|
||||
</p><br />
|
||||
<p>- Stats/Skill levels and Experience</p>
|
||||
<p>- Money</p>
|
||||
<p>- Scripts on every computer but your home computer</p>
|
||||
<p>- Purchased servers</p>
|
||||
<p>- Hacknet Nodes</p>
|
||||
<p>- Faction/Company reputation</p>
|
||||
<p>- Stocks</p><br />
|
||||
<p>
|
||||
Installing Augmentations lets you start over with the perks and
|
||||
benefits granted by all of the Augmentations you have ever
|
||||
installed. Also, you will keep any scripts and RAM/Core upgrades
|
||||
on your home computer (but you will lose all programs besides
|
||||
NUKE.exe)
|
||||
</p>
|
||||
|
||||
<StdButton
|
||||
onClick={this.props.installAugmentationsFn}
|
||||
text="Install Augmentations"
|
||||
tooltip="'I never asked for this'"
|
||||
/>
|
||||
|
||||
<StdButton
|
||||
addClasses="flashing-button"
|
||||
onClick={this.props.exportGameFn}
|
||||
text="Backup Save (Export)"
|
||||
tooltip="It's always a good idea to backup/export your save!"
|
||||
/>
|
||||
<PurchasedAugmentations />
|
||||
|
||||
<h1>Installed Augmentations</h1>
|
||||
<p>
|
||||
{
|
||||
`List of all Augmentations ${Player.sourceFiles.length > 0 ? "and Source Files " : ""} ` +
|
||||
`that have been installed. You have gained the effects of these.`
|
||||
}
|
||||
</p>
|
||||
<InstalledAugmentationsAndSourceFiles />
|
||||
|
||||
<br /> <br />
|
||||
<PlayerMultipliers />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
@ -25,11 +25,9 @@ class BitNode {
|
||||
}
|
||||
|
||||
|
||||
export let BitNodes: IMap<BitNode> = {};
|
||||
export const BitNodes: IMap<BitNode> = {};
|
||||
|
||||
export function initBitNodes() {
|
||||
BitNodes = {};
|
||||
BitNodes["BitNode1"] = new BitNode(1, "Source Genesis", "The original BitNode",
|
||||
BitNodes["BitNode1"] = new BitNode(1, "Source Genesis", "The original BitNode",
|
||||
"The first BitNode created by the Enders to imprison the minds of humans. It became " +
|
||||
"the prototype and testing-grounds for all of the BitNodes that followed.<br><br>" +
|
||||
"This is the first BitNode that you play through. It has no special " +
|
||||
@ -41,7 +39,7 @@ export function initBitNodes() {
|
||||
"Level 1: 16%<br>" +
|
||||
"Level 2: 24%<br>" +
|
||||
"Level 3: 28%");
|
||||
BitNodes["BitNode2"] = new BitNode(2, "Rise of the Underworld", "From the shadows, they rose", //Gangs
|
||||
BitNodes["BitNode2"] = new BitNode(2, "Rise of the Underworld", "From the shadows, they rose", //Gangs
|
||||
"From the shadows, they rose.<br><br>Organized crime groups quickly filled the void of power " +
|
||||
"left behind from the collapse of Western government in the 2050s. As society and civlization broke down, " +
|
||||
"people quickly succumbed to the innate human impulse of evil and savagery. The organized crime " +
|
||||
@ -63,7 +61,7 @@ export function initBitNodes() {
|
||||
"Level 1: 24%<br>" +
|
||||
"Level 2: 36%<br>" +
|
||||
"Level 3: 42%");
|
||||
BitNodes["BitNode3"] = new BitNode(3, "Corporatocracy", "The Price of Civilization",
|
||||
BitNodes["BitNode3"] = new BitNode(3, "Corporatocracy", "The Price of Civilization",
|
||||
"Our greatest illusion is that a healthy society can revolve around a " +
|
||||
"single-minded pursuit of wealth.<br><br>" +
|
||||
"Sometime in the early 21st century economic and political globalization turned " +
|
||||
@ -82,7 +80,7 @@ export function initBitNodes() {
|
||||
"Level 1: 8%<br>" +
|
||||
"Level 2: 12%<br>" +
|
||||
"Level 3: 14%");
|
||||
BitNodes["BitNode4"] = new BitNode(4, "The Singularity", "The Man and the Machine",
|
||||
BitNodes["BitNode4"] = new BitNode(4, "The Singularity", "The Man and the Machine",
|
||||
"The Singularity has arrived. The human race is gone, replaced " +
|
||||
"by artificially superintelligent beings that are more machine than man. <br><br>" +
|
||||
"In this BitNode, progressing is significantly harder. Experience gain rates " +
|
||||
@ -94,7 +92,7 @@ export function initBitNodes() {
|
||||
"upgrade its level up to a maximum of 3. This Source-File lets you access and use the Singularity " +
|
||||
"Functions in other BitNodes. Each level of this Source-File will open up more Singularity Functions " +
|
||||
"that you can use.");
|
||||
BitNodes["BitNode5"] = new BitNode(5, "Artificial Intelligence", "Posthuman",
|
||||
BitNodes["BitNode5"] = new BitNode(5, "Artificial Intelligence", "Posthuman",
|
||||
"They said it couldn't be done. They said the human brain, " +
|
||||
"along with its consciousness and intelligence, couldn't be replicated. They said the complexity " +
|
||||
"of the brain results from unpredictable, nonlinear interactions that couldn't be modeled " +
|
||||
@ -118,7 +116,7 @@ export function initBitNodes() {
|
||||
"Level 1: 8%<br>" +
|
||||
"Level 2: 12%<br>" +
|
||||
"Level 3: 14%");
|
||||
BitNodes["BitNode6"] = new BitNode(6, "Bladeburners", "Like Tears in Rain",
|
||||
BitNodes["BitNode6"] = new BitNode(6, "Bladeburners", "Like Tears in Rain",
|
||||
"In the middle of the 21st century, OmniTek Incorporated began designing and manufacturing advanced synthetic " +
|
||||
"androids, or Synthoids for short. They achieved a major technological breakthrough in the sixth generation " +
|
||||
"of their Synthoid design, called MK-VI, by developing a hyperintelligent AI. Many argue that this was " +
|
||||
@ -138,7 +136,7 @@ export function initBitNodes() {
|
||||
"Level 1: 8%<br>" +
|
||||
"Level 2: 12%<br>" +
|
||||
"Level 3: 14%");
|
||||
BitNodes["BitNode7"] = new BitNode(7, "Bladeburners 2079", "More human than humans",
|
||||
BitNodes["BitNode7"] = new BitNode(7, "Bladeburners 2079", "More human than humans",
|
||||
"In the middle of the 21st century, you were doing cutting-edge work at OmniTek Incorporated as part of the AI design team " +
|
||||
"for advanced synthetic androids, or Synthoids for short. You helped achieve a major technological " +
|
||||
"breakthrough in the sixth generation of the company's Synthoid design, called MK-VI, by developing a hyperintelligent AI. " +
|
||||
@ -161,7 +159,7 @@ export function initBitNodes() {
|
||||
"Level 1: 8%<br>" +
|
||||
"Level 2: 12%<br>" +
|
||||
"Level 3: 14%");
|
||||
BitNodes["BitNode8"] = new BitNode(8, "Ghost of Wall Street", "Money never sleeps",
|
||||
BitNodes["BitNode8"] = new BitNode(8, "Ghost of Wall Street", "Money never sleeps",
|
||||
"You are trying to make a name for yourself as an up-and-coming hedge fund manager on Wall Street.<br><br>" +
|
||||
"In this BitNode:<br><br>" +
|
||||
"You start with $250 million<br>" +
|
||||
@ -176,7 +174,7 @@ export function initBitNodes() {
|
||||
"Level 3: Ability to use limit/stop orders in other BitNodes<br><br>" +
|
||||
"This Source-File also increases your hacking growth multipliers by: " +
|
||||
"<br>Level 1: 12%<br>Level 2: 18%<br>Level 3: 21%");
|
||||
BitNodes["BitNode9"] = new BitNode(9, "Hacktocracy", "Hacknet Unleashed",
|
||||
BitNodes["BitNode9"] = new BitNode(9, "Hacktocracy", "Hacknet Unleashed",
|
||||
"When Fulcrum Technologies released their open-source Linux distro Chapeau, it quickly " +
|
||||
"became the OS of choice for the underground hacking community. Chapeau became especially notorious for " +
|
||||
"powering the Hacknet, a global, decentralized network used for nefarious purposes. Fulcrum quickly " +
|
||||
@ -194,7 +192,7 @@ export function initBitNodes() {
|
||||
"Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode<br><br>" +
|
||||
"(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT " +
|
||||
"when installing Augmentations)");
|
||||
BitNodes["BitNode10"] = new BitNode(10, "Digital Carbon", "Your body is not who you are",
|
||||
BitNodes["BitNode10"] = new BitNode(10, "Digital Carbon", "Your body is not who you are",
|
||||
"In 2084, VitaLife unveiled to the world the Persona Core, a technology that allowed people " +
|
||||
"to digitize their consciousness. Their consciousness could then be transferred into Synthoids " +
|
||||
"or other bodies by trasmitting the digitized data. Human bodies became nothing more than 'sleeves' for the " +
|
||||
@ -210,7 +208,7 @@ export function initBitNodes() {
|
||||
"Destroying this BitNode will give you Source-File 10, or if you already have this Source-File it will " +
|
||||
"upgrade its level up to a maximum of 3. This Source-File unlocks Sleeve technology in other BitNodes. " +
|
||||
"Each level of this Source-File also grants you a Duplicate Sleeve");
|
||||
BitNodes["BitNode11"] = new BitNode(11, "The Big Crash", "Okay. Sell it all.",
|
||||
BitNodes["BitNode11"] = new BitNode(11, "The Big Crash", "Okay. Sell it all.",
|
||||
"The 2050s was defined by the massive amounts of violent civil unrest and anarchic rebellion that rose all around the world. It was this period " +
|
||||
"of disorder that eventually lead to the governmental reformation of many global superpowers, most notably " +
|
||||
"the USA and China. But just as the world was slowly beginning to recover from these dark times, financial catastrophe hit.<br><br>" +
|
||||
@ -234,26 +232,25 @@ export function initBitNodes() {
|
||||
"Level 1: 32%<br>" +
|
||||
"Level 2: 48%<br>" +
|
||||
"Level 3: 56%");
|
||||
BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.",
|
||||
BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.",
|
||||
"To iterate is human, to recurse divine.<br><br>" +
|
||||
"Every time this BitNode is destroyed, it becomes slightly harder. Destroying this BitNode will give your Souce-File 12, or " +
|
||||
"if you already have this Source-File it will upgrade its level. There is no maximum level for Source-File 12. Each level " +
|
||||
"of Source-File 12 will increase all of your multipliers by 1%. This effect is multiplicative with itself. " +
|
||||
"In other words, level N of this Source-File will result in a multiplier of 1.01^N (or 0.99^N for multipliers that decrease)");
|
||||
//Books: Frontera, Shiner
|
||||
BitNodes["BitNode13"] = new BitNode(13, "fOS", "COMING SOON"); //Unlocks the new game mode and the rest of the BitNodes
|
||||
BitNodes["BitNode14"] = new BitNode(14, "", "COMING SOON");
|
||||
BitNodes["BitNode15"] = new BitNode(15, "", "COMING SOON");
|
||||
BitNodes["BitNode16"] = new BitNode(16, "", "COMING SOON");
|
||||
BitNodes["BitNode17"] = new BitNode(17, "", "COMING SOON");
|
||||
BitNodes["BitNode18"] = new BitNode(18, "", "COMING SOON");
|
||||
BitNodes["BitNode19"] = new BitNode(19, "", "COMING SOON");
|
||||
BitNodes["BitNode20"] = new BitNode(20, "", "COMING SOON");
|
||||
BitNodes["BitNode21"] = new BitNode(21, "", "COMING SOON");
|
||||
BitNodes["BitNode22"] = new BitNode(22, "", "COMING SOON");
|
||||
BitNodes["BitNode23"] = new BitNode(23, "", "COMING SOON");
|
||||
BitNodes["BitNode24"] = new BitNode(24, "", "COMING SOON");
|
||||
}
|
||||
// Books: Frontera, Shiner
|
||||
BitNodes["BitNode13"] = new BitNode(13, "fOS", "COMING SOON"); //Unlocks the new game mode and the rest of the BitNodes
|
||||
BitNodes["BitNode14"] = new BitNode(14, "", "COMING SOON");
|
||||
BitNodes["BitNode15"] = new BitNode(15, "", "COMING SOON");
|
||||
BitNodes["BitNode16"] = new BitNode(16, "", "COMING SOON");
|
||||
BitNodes["BitNode17"] = new BitNode(17, "", "COMING SOON");
|
||||
BitNodes["BitNode18"] = new BitNode(18, "", "COMING SOON");
|
||||
BitNodes["BitNode19"] = new BitNode(19, "", "COMING SOON");
|
||||
BitNodes["BitNode20"] = new BitNode(20, "", "COMING SOON");
|
||||
BitNodes["BitNode21"] = new BitNode(21, "", "COMING SOON");
|
||||
BitNodes["BitNode22"] = new BitNode(22, "", "COMING SOON");
|
||||
BitNodes["BitNode23"] = new BitNode(23, "", "COMING SOON");
|
||||
BitNodes["BitNode24"] = new BitNode(24, "", "COMING SOON");
|
||||
|
||||
export function initBitNodeMultipliers(p: IPlayer) {
|
||||
if (p.bitNodeN == null) {
|
||||
|
@ -6,7 +6,7 @@
|
||||
import { IMap } from "./types";
|
||||
|
||||
export let CONSTANTS: IMap<any> = {
|
||||
Version: "0.47.0",
|
||||
Version: "0.47.1",
|
||||
|
||||
/** Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience
|
||||
* and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then
|
||||
@ -221,47 +221,18 @@ export let CONSTANTS: IMap<any> = {
|
||||
|
||||
LatestUpdate:
|
||||
`
|
||||
v0.47.0
|
||||
v0.47.1
|
||||
* 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
|
||||
** Transactions no longer influence stock prices (but they still influence forecast)
|
||||
** Changed the way stocks behave, particularly with regard to how the stock forecast occasionally "flips"
|
||||
** Hacking & growing a server can potentially affect the way the corresponding stock's forecast changes
|
||||
** Working for a company positively affects the way the corresponding stock's forecast changes
|
||||
|
||||
* 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
|
||||
* Scripts now start/stop instantly
|
||||
* Improved performance when starting up many copies of a new NetscriptJS script (by Ornedan)
|
||||
* Improved performance when killing scripts
|
||||
* Dialog boxes can now be closed with the ESC key (by jaguilar)
|
||||
* NetscriptJS scripts should now be "re-compiled" if their dependencies change (by jaguilar)
|
||||
* write() function should now properly cause NetscriptJS scripts to "re-compile" (by jaguilar)
|
||||
`
|
||||
}
|
||||
|
4
src/Hacknet/GetMaxNumberUpgradeFns.js
Normal file
4
src/Hacknet/GetMaxNumberUpgradeFns.js
Normal file
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Utility functions for calculating the maximum number of Hacknet upgrades the player
|
||||
* can purchase for a Node with his/her current money
|
||||
*/
|
@ -1,3 +1,13 @@
|
||||
/**
|
||||
* Generic helper/utility functions for the Hacknet mechanic:
|
||||
* - Purchase nodes/upgrades
|
||||
* - Calculating maximum number of upgrades
|
||||
* - Processing Hacknet earnings
|
||||
* - Updating Hash Manager capacity
|
||||
* - Purchasing hash upgrades
|
||||
*
|
||||
* TODO Should probably split the different types of functions into their own modules
|
||||
*/
|
||||
import {
|
||||
HacknetNode,
|
||||
BaseCostForHacknetNode,
|
||||
@ -26,7 +36,7 @@ import {
|
||||
ITutorial
|
||||
} from "../InteractiveTutorial";
|
||||
import { Player } from "../Player";
|
||||
import { AddToAllServers, AllServers } from "../Server/AllServers";
|
||||
import { AllServers } from "../Server/AllServers";
|
||||
import { GetServerByHostname } from "../Server/ServerHelpers";
|
||||
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
|
||||
import { Page, routing } from "../ui/navigationTracking";
|
||||
@ -115,7 +125,7 @@ export function getCostOfNextHacknetServer() {
|
||||
return BaseCostForHacknetServer * Math.pow(mult, numOwned) * Player.hacknet_node_purchase_cost_mult;
|
||||
}
|
||||
|
||||
//Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node
|
||||
// Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node's level
|
||||
export function getMaxNumberLevelUpgrades(nodeObj, maxLevel) {
|
||||
if (maxLevel == null) {
|
||||
throw new Error(`getMaxNumberLevelUpgrades() called without maxLevel arg`);
|
||||
@ -149,6 +159,7 @@ export function getMaxNumberLevelUpgrades(nodeObj, maxLevel) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node's RAM
|
||||
export function getMaxNumberRamUpgrades(nodeObj, maxLevel) {
|
||||
if (maxLevel == null) {
|
||||
throw new Error(`getMaxNumberRamUpgrades() called without maxLevel arg`);
|
||||
@ -177,6 +188,7 @@ export function getMaxNumberRamUpgrades(nodeObj, maxLevel) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node's cores
|
||||
export function getMaxNumberCoreUpgrades(nodeObj, maxLevel) {
|
||||
if (maxLevel == null) {
|
||||
throw new Error(`getMaxNumberCoreUpgrades() called without maxLevel arg`);
|
||||
@ -193,7 +205,7 @@ export function getMaxNumberCoreUpgrades(nodeObj, maxLevel) {
|
||||
return levelsToMax;
|
||||
}
|
||||
|
||||
//Use a binary search to find the max possible number of upgrades
|
||||
// Use a binary search to find the max possible number of upgrades
|
||||
while (min <= max) {
|
||||
let curr = (min + max) / 2 | 0;
|
||||
if (curr != maxLevel &&
|
||||
@ -212,6 +224,7 @@ export function getMaxNumberCoreUpgrades(nodeObj, maxLevel) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node's cache
|
||||
export function getMaxNumberCacheUpgrades(nodeObj, maxLevel) {
|
||||
if (maxLevel == null) {
|
||||
throw new Error(`getMaxNumberCacheUpgrades() called without maxLevel arg`);
|
||||
|
@ -176,33 +176,28 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
|
||||
return totalCost;
|
||||
}
|
||||
|
||||
// Process this Hacknet Server in the game loop.
|
||||
// Returns the number of hashes generated
|
||||
// Process this Hacknet Server in the game loop. Returns the number of hashes generated
|
||||
process(numCycles: number=1): number {
|
||||
const seconds = numCycles * CONSTANTS.MilliPerCycle / 1000;
|
||||
|
||||
return this.hashRate * seconds;
|
||||
}
|
||||
|
||||
// Returns a boolean indicating whether the cache was successfully upgraded
|
||||
upgradeCache(levels: number): void {
|
||||
this.cache = Math.min(HacknetServerMaxCache, Math.round(this.cache + levels));
|
||||
this.updateHashCapacity();
|
||||
}
|
||||
|
||||
// Returns a boolean indicating whether the number of cores was successfully upgraded
|
||||
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
|
||||
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
|
||||
upgradeRam(levels: number, prodMult: number): boolean {
|
||||
for (let i = 0; i < levels; ++i) {
|
||||
this.maxRam *= 2;
|
||||
@ -213,9 +208,7 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whenever a script is run, we must update this server's hash rate
|
||||
*/
|
||||
// Whenever a script is run, we must update this server's hash rate
|
||||
runScript(script: RunningScript, prodMult?: number): void {
|
||||
super.runScript(script);
|
||||
if (prodMult != null && typeof prodMult === "number") {
|
||||
|
@ -40,13 +40,15 @@ import { createPopupCloseButton } from "../../utils/uiHelpers/createPopupCloseBu
|
||||
import { removeElementById } from "../../utils/uiHelpers/removeElementById";
|
||||
|
||||
/**
|
||||
* Create a pop-up box that lets the player confirm traveling to a different city
|
||||
* If settings are configured to suppress this popup, just instantly travel
|
||||
* Create a pop-up box that lets the player confirm traveling to a different city.
|
||||
* If settings are configured to suppress this popup, just instantly travel.
|
||||
* The actual "Travel" implementation is implemented in the UI, and is passed in
|
||||
* as an argument
|
||||
* as an argument.
|
||||
* @param {CityName} destination - City that the player is traveling to
|
||||
* @param {Function} travelFn - Function that changes the player's state for traveling
|
||||
*/
|
||||
type TravelFunction = (to: CityName) => void;
|
||||
export function createTravelPopup(destination: CityName, travelFn: TravelFunction) {
|
||||
export function createTravelPopup(destination: CityName, travelFn: TravelFunction): void {
|
||||
const cost = CONSTANTS.TravelCost;
|
||||
|
||||
if (Settings.SuppressTravelConfirmation) {
|
||||
@ -80,10 +82,10 @@ export function createTravelPopup(destination: CityName, travelFn: TravelFunctio
|
||||
|
||||
/**
|
||||
* Create a pop-up box that lets the player purchase a server.
|
||||
* @param ram - Amount of RAM (GB) on server
|
||||
* @param p - Player object
|
||||
* @param {number} ram - Amount of RAM (GB) on server
|
||||
* @param {IPlayer} p - Player object
|
||||
*/
|
||||
export function createPurchaseServerPopup(ram: number, p: IPlayer) {
|
||||
export function createPurchaseServerPopup(ram: number, p: IPlayer): void {
|
||||
const cost = getPurchaseServerCost(ram);
|
||||
if (cost === Infinity) {
|
||||
dialogBoxCreate("Something went wrong when trying to purchase this server. Please contact developer");
|
||||
@ -111,6 +113,7 @@ export function createPurchaseServerPopup(ram: number, p: IPlayer) {
|
||||
|
||||
/**
|
||||
* Create a popup that lets the player start a Corporation
|
||||
* @param {IPlayer} p - Player object
|
||||
*/
|
||||
export function createStartCorporationPopup(p: IPlayer) {
|
||||
if (!p.canAccessCorporation() || p.hasCorporation()) { return; }
|
||||
@ -172,8 +175,10 @@ export function createStartCorporationPopup(p: IPlayer) {
|
||||
if (worldHeader instanceof HTMLElement) {
|
||||
worldHeader.click(); worldHeader.click();
|
||||
}
|
||||
dialogBoxCreate("Congratulations! You just started your own corporation with government seed money. " +
|
||||
"You can visit and manage your company in the City");
|
||||
dialogBoxCreate(
|
||||
"Congratulations! You just started your own corporation with government seed money. " +
|
||||
"You can visit and manage your company in the City"
|
||||
);
|
||||
removeElementById(popupId);
|
||||
return false;
|
||||
}
|
||||
@ -187,21 +192,23 @@ export function createStartCorporationPopup(p: IPlayer) {
|
||||
|
||||
/**
|
||||
* Create a popup that lets the player upgrade the cores on his/her home computer
|
||||
* @param p - Player object
|
||||
* @param {IPlayer} p - Player object
|
||||
*/
|
||||
export function createUpgradeHomeCoresPopup(p: IPlayer) {
|
||||
const currentCores = p.getHomeComputer().cpuCores;
|
||||
if (currentCores >= 8) { return; } // Max of 8 cores
|
||||
|
||||
//Cost of purchasing another cost is found by indexing this array with number of current cores
|
||||
const allCosts = [0,
|
||||
10e9, // 1->2 Cores - 10 bn
|
||||
250e9, // 2->3 Cores - 250 bn
|
||||
5e12, // 3->4 Cores - 5 trillion
|
||||
100e12, // 4->5 Cores - 100 trillion
|
||||
1e15, // 5->6 Cores - 1 quadrillion
|
||||
20e15, // 6->7 Cores - 20 quadrillion
|
||||
200e15]; // 7->8 Cores - 200 quadrillion
|
||||
// Cost of purchasing another cost is found by indexing this array with number of current cores
|
||||
const allCosts = [
|
||||
0,
|
||||
10e9,
|
||||
250e9,
|
||||
5e12,
|
||||
100e12,
|
||||
1e15,
|
||||
20e15,
|
||||
200e15
|
||||
];
|
||||
const cost: number = allCosts[currentCores];
|
||||
|
||||
const yesBtn = yesNoBoxGetYesButton();
|
||||
@ -215,8 +222,10 @@ export function createUpgradeHomeCoresPopup(p: IPlayer) {
|
||||
} else {
|
||||
p.loseMoney(cost);
|
||||
p.getHomeComputer().cpuCores++;
|
||||
dialogBoxCreate("You purchased an additional CPU Core for your home computer! It now has " +
|
||||
p.getHomeComputer().cpuCores + " cores.");
|
||||
dialogBoxCreate(
|
||||
"You purchased an additional CPU Core for your home computer! It now has " +
|
||||
p.getHomeComputer().cpuCores + " cores."
|
||||
);
|
||||
}
|
||||
yesNoBoxClose();
|
||||
});
|
||||
@ -226,15 +235,17 @@ export function createUpgradeHomeCoresPopup(p: IPlayer) {
|
||||
yesNoBoxClose();
|
||||
});
|
||||
|
||||
yesNoBoxCreate("Would you like to purchase an additional CPU Core for your home computer? Each CPU Core " +
|
||||
yesNoBoxCreate(
|
||||
"Would you like to purchase an additional CPU Core for your home computer? Each CPU Core " +
|
||||
"lets you start with an additional Core Node in Hacking Missions.<br><br>" +
|
||||
"Purchasing an additional core (for a total of " + (p.getHomeComputer().cpuCores + 1) + ") will " +
|
||||
"cost " + numeralWrapper.formatMoney(cost));
|
||||
"cost " + numeralWrapper.formatMoney(cost)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a popup that lets the player upgrade the RAM on his/her home computer
|
||||
* @param p - Player object
|
||||
* @param {IPlayer} p - Player object
|
||||
*/
|
||||
export function createUpgradeHomeRamPopup(p: IPlayer) {
|
||||
const cost: number = p.getUpgradeHomeRamCost();
|
||||
@ -255,15 +266,17 @@ export function createUpgradeHomeRamPopup(p: IPlayer) {
|
||||
yesNoBoxClose();
|
||||
});
|
||||
|
||||
yesNoBoxCreate("Would you like to purchase additional RAM for your home computer? <br><br>" +
|
||||
yesNoBoxCreate(
|
||||
"Would you like to purchase additional RAM for your home computer? <br><br>" +
|
||||
"This will upgrade your RAM from " + ram + "GB to " + ram*2 + "GB. <br><br>" +
|
||||
"This will cost " + numeralWrapper.format(cost, '$0.000a'));
|
||||
"This will cost " + numeralWrapper.format(cost, '$0.000a')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attempt to purchase a TOR router
|
||||
* @param p - Player object
|
||||
* @param {IPlayer} p - Player object
|
||||
*/
|
||||
export function purchaseTorRouter(p: IPlayer) {
|
||||
if (p.hasTorRouter()) {
|
||||
@ -285,7 +298,9 @@ export function purchaseTorRouter(p: IPlayer) {
|
||||
|
||||
p.getHomeComputer().serversOnNetwork.push(darkweb.ip);
|
||||
darkweb.serversOnNetwork.push(p.getHomeComputer().ip);
|
||||
dialogBoxCreate("You have purchased a Tor router!<br>" +
|
||||
dialogBoxCreate(
|
||||
"You have purchased a Tor router!<br>" +
|
||||
"You now have access to the dark web from your home computer<br>" +
|
||||
"Use the scan/scan-analyze commands to search for the dark web connection.");
|
||||
"Use the scan/scan-analyze commands to search for the dark web connection."
|
||||
);
|
||||
}
|
||||
|
@ -32,6 +32,11 @@ export class WorkerScript {
|
||||
*/
|
||||
delay: number | null = null;
|
||||
|
||||
/**
|
||||
* Holds the Promise resolve() function for when the script is "blocked" by an async op
|
||||
*/
|
||||
delayResolve?: () => void;
|
||||
|
||||
/**
|
||||
* Stores names of all functions that have logging disabled
|
||||
*/
|
||||
@ -75,6 +80,12 @@ export class WorkerScript {
|
||||
*/
|
||||
output: string = "";
|
||||
|
||||
/**
|
||||
* Process ID. Must be an integer. Used for efficient script
|
||||
* killing and removal.
|
||||
*/
|
||||
pid: number;
|
||||
|
||||
/**
|
||||
* Script's Static RAM usage. Equivalent to underlying script's RAM usage
|
||||
*/
|
||||
@ -95,10 +106,17 @@ export class WorkerScript {
|
||||
*/
|
||||
serverIp: string;
|
||||
|
||||
constructor(runningScriptObj: RunningScript, nsFuncsGenerator?: (ws: WorkerScript) => object) {
|
||||
constructor(runningScriptObj: RunningScript, pid: number, nsFuncsGenerator?: (ws: WorkerScript) => object) {
|
||||
this.name = runningScriptObj.filename;
|
||||
this.serverIp = runningScriptObj.server;
|
||||
|
||||
const sanitizedPid = Math.round(pid);
|
||||
if (typeof sanitizedPid !== "number" || isNaN(sanitizedPid)) {
|
||||
throw new Error(`Invalid PID when constructing WorkerScript: ${pid}`);
|
||||
}
|
||||
this.pid = sanitizedPid;
|
||||
runningScriptObj.pid = sanitizedPid;
|
||||
|
||||
// Get the underlying script's code
|
||||
const server = AllServers[this.serverIp];
|
||||
if (server == null) {
|
||||
|
6
src/Netscript/WorkerScriptStartStopEventEmitter.ts
Normal file
6
src/Netscript/WorkerScriptStartStopEventEmitter.ts
Normal file
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Event emitter that triggers when scripts are started/stopped
|
||||
*/
|
||||
import { EventEmitter } from "../utils/EventEmitter";
|
||||
|
||||
export const WorkerScriptStartStopEventEmitter = new EventEmitter();
|
6
src/Netscript/WorkerScripts.ts
Normal file
6
src/Netscript/WorkerScripts.ts
Normal file
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Global pool of all active scripts (scripts that are currently running)
|
||||
*/
|
||||
import { WorkerScript } from "./WorkerScript";
|
||||
|
||||
export const workerScripts: Map<number, WorkerScript> = new Map();
|
136
src/Netscript/killWorkerScript.ts
Normal file
136
src/Netscript/killWorkerScript.ts
Normal file
@ -0,0 +1,136 @@
|
||||
/**
|
||||
* Stops an actively-running script (represented by a WorkerScript object)
|
||||
* and removes it from the global pool of active scripts.
|
||||
*/
|
||||
import { WorkerScript } from "./WorkerScript";
|
||||
import { workerScripts } from "./WorkerScripts";
|
||||
import { WorkerScriptStartStopEventEmitter } from "./WorkerScriptStartStopEventEmitter";
|
||||
|
||||
import { RunningScript } from "../Script/RunningScript";
|
||||
import { AllServers } from "../Server/AllServers";
|
||||
|
||||
import { compareArrays } from "../../utils/helpers/compareArrays";
|
||||
import { roundToTwo } from "../../utils/helpers/roundToTwo";
|
||||
|
||||
export function killWorkerScript(runningScriptObj: RunningScript, serverIp: string, rerenderUi: boolean): boolean;
|
||||
export function killWorkerScript(workerScript: WorkerScript): boolean;
|
||||
export function killWorkerScript(pid: number): boolean;
|
||||
export function killWorkerScript(script: RunningScript | WorkerScript | number, serverIp?: string, rerenderUi?: boolean): boolean {
|
||||
if (rerenderUi == null || typeof rerenderUi !== "boolean") {
|
||||
rerenderUi = true;
|
||||
}
|
||||
|
||||
if (script instanceof WorkerScript) {
|
||||
stopAndCleanUpWorkerScript(script);
|
||||
|
||||
return true;
|
||||
} else if (script instanceof RunningScript && typeof serverIp === "string") {
|
||||
// Try to kill by PID
|
||||
const res = killWorkerScriptByPid(script.pid, rerenderUi);
|
||||
if (res) { return res; }
|
||||
|
||||
// If for some reason that doesn't work, we'll try the old way
|
||||
for (const ws of workerScripts.values()) {
|
||||
if (ws.name == script.filename && ws.serverIp == serverIp &&
|
||||
compareArrays(ws.args, script.args)) {
|
||||
|
||||
stopAndCleanUpWorkerScript(ws, rerenderUi);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
} else if (typeof script === "number") {
|
||||
return killWorkerScriptByPid(script, rerenderUi);
|
||||
} else {
|
||||
console.error(`killWorkerScript() called with invalid argument:`);
|
||||
console.error(script);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function killWorkerScriptByPid(pid: number, rerenderUi: boolean=true): boolean {
|
||||
const ws = workerScripts.get(pid);
|
||||
if (ws instanceof WorkerScript) {
|
||||
stopAndCleanUpWorkerScript(ws, rerenderUi);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function stopAndCleanUpWorkerScript(workerScript: WorkerScript, rerenderUi: boolean=true): void {
|
||||
workerScript.env.stopFlag = true;
|
||||
killNetscriptDelay(workerScript);
|
||||
removeWorkerScript(workerScript, rerenderUi);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that removes the script being killed from the global pool.
|
||||
* Also handles other cleanup-time operations
|
||||
*
|
||||
* @param {WorkerScript | number} - Identifier for WorkerScript. Either the object itself, or
|
||||
* its index in the global workerScripts array
|
||||
*/
|
||||
function removeWorkerScript(workerScript: WorkerScript, rerenderUi: boolean=true): void {
|
||||
if (workerScript instanceof WorkerScript) {
|
||||
const ip = workerScript.serverIp;
|
||||
const name = workerScript.name;
|
||||
|
||||
// Get the server on which the script runs
|
||||
const server = AllServers[ip];
|
||||
if (server == null) {
|
||||
console.error(`Could not find server on which this script is running: ${ip}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Recalculate ram used on that server
|
||||
server.ramUsed = roundToTwo(server.ramUsed - workerScript.ramUsage);
|
||||
if (server.ramUsed < 0) {
|
||||
console.warn(`Server RAM usage went negative (if it's due to floating pt imprecision, it's okay): ${server.ramUsed}`);
|
||||
server.ramUsed = 0;
|
||||
}
|
||||
|
||||
// Delete the RunningScript object from that server
|
||||
for (let i = 0; i < server.runningScripts.length; ++i) {
|
||||
const runningScript = server.runningScripts[i];
|
||||
if (runningScript.filename === name && compareArrays(runningScript.args, workerScript.args)) {
|
||||
server.runningScripts.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete script from global pool (workerScripts)
|
||||
const res = workerScripts.delete(workerScript.pid);
|
||||
if (!res) {
|
||||
console.warn(`removeWorkerScript() called with WorkerScript that wasn't in the global map:`);
|
||||
console.warn(workerScript);
|
||||
}
|
||||
|
||||
if (rerenderUi) {
|
||||
WorkerScriptStartStopEventEmitter.emitEvent();
|
||||
}
|
||||
} else {
|
||||
console.error(`Invalid argument passed into removeWorkerScript():`);
|
||||
console.error(workerScript);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that interrupts a script's delay if it is in the middle of a
|
||||
* timed, blocked operation (like hack(), sleep(), etc.). This allows scripts to
|
||||
* be killed immediately even if they're in the middle of one of those long operations
|
||||
*/
|
||||
function killNetscriptDelay(workerScript: WorkerScript) {
|
||||
if (workerScript instanceof WorkerScript) {
|
||||
if (workerScript.delay) {
|
||||
clearTimeout(workerScript.delay);
|
||||
if (workerScript.delayResolve) {
|
||||
workerScript.delayResolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +1,9 @@
|
||||
import { WorkerScript } from "./Netscript/WorkerScript";
|
||||
import { getServer } from "./Server/ServerHelpers";
|
||||
|
||||
import { setTimeoutRef } from "./utils/SetTimeoutRef";
|
||||
import { parse, Node } from "../utils/acorn";
|
||||
|
||||
import { isValidIPAddress } from "../utils/helpers/isValidIPAddress";
|
||||
import { isString } from "../utils/helpers/isString";
|
||||
|
||||
export function killNetscriptDelay(workerScript) {
|
||||
if (workerScript instanceof WorkerScript) {
|
||||
if (workerScript.delay) {
|
||||
clearTimeout(workerScript.delay);
|
||||
workerScript.delayResolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function netscriptDelay(time, workerScript) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
workerScript.delay = setTimeoutRef(() => {
|
||||
|
@ -2,8 +2,8 @@ const sprintf = require("sprintf-js").sprintf;
|
||||
const vsprintf = require("sprintf-js").vsprintf;
|
||||
|
||||
import { getRamCost } from "./Netscript/RamCostGenerator";
|
||||
import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStartStopEventEmitter";
|
||||
|
||||
import { updateActiveScriptsItems } from "./ActiveScriptsUI";
|
||||
import { Augmentation } from "./Augmentation/Augmentation";
|
||||
import { Augmentations } from "./Augmentation/Augmentations";
|
||||
import {
|
||||
@ -91,6 +91,10 @@ import {
|
||||
shortStock,
|
||||
sellShort,
|
||||
} from "./StockMarket/BuyingAndSelling";
|
||||
import {
|
||||
influenceStockThroughServerHack,
|
||||
influenceStockThroughServerGrow,
|
||||
} from "./StockMarket/PlayerInfluencing";
|
||||
import { Stock } from "./StockMarket/Stock";
|
||||
import {
|
||||
StockMarket,
|
||||
@ -120,11 +124,11 @@ import {
|
||||
} from "./NetscriptBladeburner";
|
||||
import * as nsGang from "./NetscriptGang";
|
||||
import {
|
||||
workerScripts,
|
||||
killWorkerScript,
|
||||
NetscriptPorts,
|
||||
runScriptFromScript,
|
||||
} from "./NetscriptWorker";
|
||||
import { killWorkerScript } from "./Netscript/killWorkerScript";
|
||||
import { workerScripts } from "./Netscript/WorkerScripts";
|
||||
import {
|
||||
makeRuntimeRejectMsg,
|
||||
netscriptDelay,
|
||||
@ -440,7 +444,7 @@ function NetscriptFunctions(workerScript) {
|
||||
}
|
||||
return out;
|
||||
},
|
||||
hack : function(ip, { threads: requestedThreads } = {}){
|
||||
hack : function(ip, { threads: requestedThreads, stock } = {}){
|
||||
updateDynamicRam("hack", getRamCost("hack"));
|
||||
if (ip === undefined) {
|
||||
throw makeRuntimeRejectMsg(workerScript, "Hack() call has incorrect number of arguments. Takes 1 argument");
|
||||
@ -502,6 +506,9 @@ function NetscriptFunctions(workerScript) {
|
||||
workerScript.scriptRef.log("Script SUCCESSFULLY hacked " + server.hostname + " for $" + formatNumber(moneyGained, 2) + " and " + formatNumber(expGainedOnSuccess, 4) + " exp (t=" + threads + ")");
|
||||
}
|
||||
server.fortify(CONSTANTS.ServerFortifyAmount * Math.min(threads, maxThreadNeeded));
|
||||
if (stock) {
|
||||
influenceStockThroughServerHack(server, moneyGained);
|
||||
}
|
||||
return Promise.resolve(moneyGained);
|
||||
} else {
|
||||
// Player only gains 25% exp for failure?
|
||||
@ -556,7 +563,7 @@ function NetscriptFunctions(workerScript) {
|
||||
return Promise.resolve(true);
|
||||
});
|
||||
},
|
||||
grow : function(ip, { threads: requestedThreads } = {}){
|
||||
grow : function(ip, { threads: requestedThreads, stock } = {}){
|
||||
updateDynamicRam("grow", getRamCost("grow"));
|
||||
const threads = resolveNetscriptRequestedThreads(workerScript, "grow", requestedThreads);
|
||||
if (ip === undefined) {
|
||||
@ -597,6 +604,9 @@ function NetscriptFunctions(workerScript) {
|
||||
}
|
||||
workerScript.scriptRef.onlineExpGained += expGain;
|
||||
Player.gainHackingExp(expGain);
|
||||
if (stock) {
|
||||
influenceStockThroughServerGrow(server, moneyAfter - moneyBefore);
|
||||
}
|
||||
return Promise.resolve(moneyAfter/moneyBefore);
|
||||
});
|
||||
},
|
||||
@ -974,18 +984,20 @@ function NetscriptFunctions(workerScript) {
|
||||
if (ip === undefined) {
|
||||
throw makeRuntimeRejectMsg(workerScript, "killall() call has incorrect number of arguments. Takes 1 argument");
|
||||
}
|
||||
var server = getServer(ip);
|
||||
const server = getServer(ip);
|
||||
if (server == null) {
|
||||
workerScript.scriptRef.log("killall() failed. Invalid IP or hostname passed in: " + ip);
|
||||
throw makeRuntimeRejectMsg(workerScript, "killall() failed. Invalid IP or hostname passed in: " + ip);
|
||||
}
|
||||
var scriptsRunning = (server.runningScripts.length > 0);
|
||||
for (var i = server.runningScripts.length-1; i >= 0; --i) {
|
||||
killWorkerScript(server.runningScripts[i], server.ip);
|
||||
const scriptsRunning = (server.runningScripts.length > 0);
|
||||
for (let i = server.runningScripts.length-1; i >= 0; --i) {
|
||||
killWorkerScript(server.runningScripts[i], server.ip, false);
|
||||
}
|
||||
WorkerScriptStartStopEventEmitter.emitEvent();
|
||||
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.killall == null) {
|
||||
workerScript.scriptRef.log("killall(): Killing all scripts on " + server.hostname + ". May take a few minutes for the scripts to die");
|
||||
}
|
||||
|
||||
return scriptsRunning;
|
||||
},
|
||||
exit : function() {
|
||||
@ -1144,7 +1156,7 @@ function NetscriptFunctions(workerScript) {
|
||||
var oldScript = destServer.scripts[i];
|
||||
oldScript.code = sourceScript.code;
|
||||
oldScript.ramUsage = sourceScript.ramUsage;
|
||||
oldScript.module = "";
|
||||
oldScript.markUpdated();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -1948,6 +1960,7 @@ function NetscriptFunctions(workerScript) {
|
||||
}
|
||||
mode === "w" ? script.code = data : script.code += data;
|
||||
script.updateRamUsage(server.scripts);
|
||||
script.markUpdated();
|
||||
} else {
|
||||
// Write to text file
|
||||
let txtFile = getTextFile(fn, server);
|
||||
@ -2168,7 +2181,7 @@ function NetscriptFunctions(workerScript) {
|
||||
|
||||
// First element is total income of all currently running scripts
|
||||
let total = 0;
|
||||
for (const script of workerScripts) {
|
||||
for (const script of workerScripts.values()) {
|
||||
total += (script.scriptRef.onlineMoneyMade / script.scriptRef.onlineRunningTime);
|
||||
}
|
||||
res.push(total);
|
||||
@ -2199,8 +2212,8 @@ function NetscriptFunctions(workerScript) {
|
||||
updateDynamicRam("getScriptExpGain", getRamCost("getScriptExpGain"));
|
||||
if (arguments.length === 0) {
|
||||
var total = 0;
|
||||
for (var i = 0; i < workerScripts.length; ++i) {
|
||||
total += (workerScripts[i].scriptRef.onlineExpGained / workerScripts[i].scriptRef.onlineRunningTime);
|
||||
for (const ws of workerScripts.values()) {
|
||||
total += (ws.scriptRef.onlineExpGained / ws.scriptRef.onlineRunningTime);
|
||||
}
|
||||
return total;
|
||||
} else {
|
||||
|
@ -1,10 +1,22 @@
|
||||
import { makeRuntimeRejectMsg } from "./NetscriptEvaluator";
|
||||
import { Script } from "./Script/Script";
|
||||
|
||||
// Makes a blob that contains the code of a given script.
|
||||
export function makeScriptBlob(code) {
|
||||
return new Blob([code], {type: "text/javascript"});
|
||||
}
|
||||
|
||||
class ScriptUrl {
|
||||
/**
|
||||
* @param {string} filename
|
||||
* @param {string} url
|
||||
*/
|
||||
constructor(filename, url) {
|
||||
this.filename = filename;
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
|
||||
// Begin executing a user JS script, and return a promise that resolves
|
||||
// or rejects when the script finishes.
|
||||
// - script is a script to execute (see Script.js). We depend only on .filename and .code.
|
||||
@ -15,9 +27,9 @@ export function makeScriptBlob(code) {
|
||||
// running the main function of the script.
|
||||
export async function executeJSScript(scripts = [], workerScript) {
|
||||
let loadedModule;
|
||||
let urlStack = null;
|
||||
let urls = null;
|
||||
let script = workerScript.getScript();
|
||||
if (script.module === "") {
|
||||
if (shouldCompile(script, scripts)) {
|
||||
// The URL at the top is the one we want to import. It will
|
||||
// recursively import all the other modules in the urlStack.
|
||||
//
|
||||
@ -25,10 +37,11 @@ export async function executeJSScript(scripts = [], workerScript) {
|
||||
// but not really behaves like import. Particularly, it cannot
|
||||
// load fully dynamic content. So we hide the import from webpack
|
||||
// by placing it inside an eval call.
|
||||
urlStack = _getScriptUrls(script, scripts, []);
|
||||
script.module = await eval('import(urlStack[urlStack.length - 1])');
|
||||
urls = _getScriptUrls(script, scripts, []);
|
||||
script.module = new Promise(resolve => resolve(eval('import(urls[urls.length - 1].url)')));
|
||||
script.dependencies = urls.map(u => u.filename);
|
||||
}
|
||||
loadedModule = script.module;
|
||||
loadedModule = await script.module;
|
||||
|
||||
let ns = workerScript.env.vars;
|
||||
|
||||
@ -41,12 +54,31 @@ export async function executeJSScript(scripts = [], workerScript) {
|
||||
return loadedModule.main(ns);
|
||||
} finally {
|
||||
// Revoke the generated URLs
|
||||
if (urlStack != null) {
|
||||
for (const url in urlStack) URL.revokeObjectURL(url);
|
||||
if (urls != null) {
|
||||
for (const b in urls) URL.revokeObjectURL(b.url);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Returns whether we should compile the script parameter.
|
||||
*
|
||||
* @param {Script} script
|
||||
* @param {Script[]} scripts
|
||||
*/
|
||||
function shouldCompile(script, scripts) {
|
||||
if (script.module === "") return true;
|
||||
return script.dependencies.some(dep => {
|
||||
const depScript = scripts.find(s => s.filename == dep);
|
||||
|
||||
// If the script is not present on the server, we should recompile, if only to get any necessary
|
||||
// compilation errors.
|
||||
if (!depScript) return true;
|
||||
|
||||
const depIsMoreRecent = depScript.moduleSequenceNumber > script.moduleSequenceNumber
|
||||
return depIsMoreRecent;
|
||||
});
|
||||
}
|
||||
|
||||
// Gets a stack of blob urls, the top/right-most element being
|
||||
// the blob url for the named script on the named server.
|
||||
//
|
||||
@ -58,8 +90,18 @@ export async function executeJSScript(scripts = [], workerScript) {
|
||||
// different parts of the tree. That hasn't presented any problem with during
|
||||
// testing, but it might be an idea for the future. Would require a topo-sort
|
||||
// then url-izing from leaf-most to root-most.
|
||||
/**
|
||||
* @param {Script} script
|
||||
* @param {Script[]} scripts
|
||||
* @param {Script[]} seen
|
||||
* @returns {ScriptUrl[]} All of the compiled scripts, with the final one
|
||||
* in the list containing the blob corresponding to
|
||||
* the script parameter.
|
||||
*/
|
||||
// BUG: apparently seen is never consulted. Oops.
|
||||
export function _getScriptUrls(script, scripts, seen) {
|
||||
// Inspired by: https://stackoverflow.com/a/43834063/91401
|
||||
/** @type {ScriptUrl[]} */
|
||||
const urlStack = [];
|
||||
seen.push(script);
|
||||
try {
|
||||
@ -86,7 +128,7 @@ export function _getScriptUrls(script, scripts, seen) {
|
||||
|
||||
// The top url in the stack is the replacement import file for this script.
|
||||
urlStack.push(...urls);
|
||||
return [prefix, urls[urls.length - 1], suffix].join('');
|
||||
return [prefix, urls[urls.length - 1].url, suffix].join('');
|
||||
}
|
||||
);
|
||||
|
||||
@ -96,7 +138,7 @@ export function _getScriptUrls(script, scripts, seen) {
|
||||
|
||||
// If we successfully transformed the code, create a blob url for it and
|
||||
// push that URL onto the top of the stack.
|
||||
urlStack.push(URL.createObjectURL(makeScriptBlob(transformedCode)));
|
||||
urlStack.push(new ScriptUrl(script.filename, URL.createObjectURL(makeScriptBlob(transformedCode))));
|
||||
return urlStack;
|
||||
} catch (err) {
|
||||
// If there is an error, we need to clean up the URLs.
|
||||
|
@ -2,20 +2,17 @@
|
||||
* Functions for handling WorkerScripts, which are the underlying mechanism
|
||||
* that allows for scripts to run
|
||||
*/
|
||||
import { killWorkerScript } from "./Netscript/killWorkerScript";
|
||||
import { WorkerScript } from "./Netscript/WorkerScript";
|
||||
import { workerScripts } from "./Netscript/WorkerScripts";
|
||||
import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStartStopEventEmitter";
|
||||
|
||||
import {
|
||||
addActiveScriptsItem,
|
||||
deleteActiveScriptsItem,
|
||||
updateActiveScriptsItems
|
||||
} from "./ActiveScriptsUI";
|
||||
import { CONSTANTS } from "./Constants";
|
||||
import { Engine } from "./engine";
|
||||
import { Interpreter } from "./JSInterpreter";
|
||||
import {
|
||||
isScriptErrorMessage,
|
||||
makeRuntimeRejectMsg,
|
||||
killNetscriptDelay
|
||||
} from "./NetscriptEvaluator";
|
||||
import { NetscriptFunctions } from "./NetscriptFunctions";
|
||||
import { executeJSScript } from "./NetscriptJSEvaluator";
|
||||
@ -42,21 +39,19 @@ import { isString } from "../utils/StringHelperFunctions";
|
||||
|
||||
const walk = require("acorn/dist/walk");
|
||||
|
||||
//Array containing all scripts that are running across all servers, to easily run them all
|
||||
export const workerScripts = [];
|
||||
|
||||
// Netscript Ports are instantiated here
|
||||
export const NetscriptPorts = [];
|
||||
for (var i = 0; i < CONSTANTS.NumNetscriptPorts; ++i) {
|
||||
NetscriptPorts.push(new NetscriptPort());
|
||||
}
|
||||
|
||||
export function prestigeWorkerScripts() {
|
||||
for (var i = 0; i < workerScripts.length; ++i) {
|
||||
deleteActiveScriptsItem(workerScripts[i]);
|
||||
workerScripts[i].env.stopFlag = true;
|
||||
for (const ws of workerScripts.values()) {
|
||||
ws.env.stopFlag = true;
|
||||
}
|
||||
updateActiveScriptsItems(5000); //Force UI to update
|
||||
workerScripts.length = 0;
|
||||
|
||||
WorkerScriptStartStopEventEmitter.emitEvent();
|
||||
workerScripts.clear();
|
||||
}
|
||||
|
||||
// JS script promises need a little massaging to have the same guarantees as netscript
|
||||
@ -141,7 +136,7 @@ function startNetscript2Script(workerScript) {
|
||||
}
|
||||
|
||||
function startNetscript1Script(workerScript) {
|
||||
var code = workerScript.code;
|
||||
const code = workerScript.code;
|
||||
workerScript.running = true;
|
||||
|
||||
//Process imports
|
||||
@ -413,66 +408,101 @@ function processNetscript1Imports(code, workerScript) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// Loop through workerScripts and run every script that is not currently running
|
||||
export function runScriptsLoop() {
|
||||
let scriptDeleted = false;
|
||||
/**
|
||||
* Find and return the next availble PID for a script
|
||||
*/
|
||||
let pidCounter = 1;
|
||||
function generateNextPid() {
|
||||
let tempCounter = pidCounter;
|
||||
|
||||
// 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
|
||||
const ip = workerScripts[i].serverIp;
|
||||
const name = workerScripts[i].name;
|
||||
|
||||
// Recalculate ram used
|
||||
AllServers[ip].ramUsed = 0;
|
||||
for (let j = 0; j < workerScripts.length; j++) {
|
||||
if (workerScripts[j].serverIp !== ip) {
|
||||
continue;
|
||||
}
|
||||
if (j === i) { // not this one
|
||||
continue;
|
||||
}
|
||||
AllServers[ip].ramUsed += workerScripts[j].ramUsage;
|
||||
}
|
||||
|
||||
// Delete script from Active Scripts
|
||||
deleteActiveScriptsItem(workerScripts[i]);
|
||||
|
||||
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);
|
||||
// Cap the number of search iterations at some arbitrary value to avoid
|
||||
// infinite loops. We'll assume that players wont have 1mil+ running scripts
|
||||
let found = false;
|
||||
for (let i = 0; i < 1e6;) {
|
||||
if (!workerScripts.has(tempCounter + i)) {
|
||||
found = true;
|
||||
tempCounter = tempCounter + i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete script from workerScripts
|
||||
workerScripts.splice(i, 1);
|
||||
}
|
||||
}
|
||||
if (scriptDeleted) { updateActiveScriptsItems(); } // Force Update
|
||||
|
||||
|
||||
// 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")) {
|
||||
p = startNetscript2Script(workerScripts[i]);
|
||||
if (i === Number.MAX_SAFE_INTEGER - 1) {
|
||||
i = 1;
|
||||
} else {
|
||||
p = startNetscript1Script(workerScripts[i]);
|
||||
if (!(p instanceof Promise)) { continue; }
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
pidCounter = tempCounter + 1;
|
||||
if (pidCounter >= Number.MAX_SAFE_INTEGER) {
|
||||
pidCounter = 1;
|
||||
}
|
||||
|
||||
return tempCounter;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a script
|
||||
*
|
||||
* Given a RunningScript object, constructs a corresponding WorkerScript,
|
||||
* adds it to the global 'workerScripts' pool, and begins executing it.
|
||||
* @param {RunningScript} runningScriptObj - Script that's being run
|
||||
* @param {Server} server - Server on which the script is to be run
|
||||
*/
|
||||
export function addWorkerScript(runningScriptObj, server) {
|
||||
const filename = runningScriptObj.filename;
|
||||
|
||||
// Update server's ram usage
|
||||
let threads = 1;
|
||||
if (runningScriptObj.threads && !isNaN(runningScriptObj.threads)) {
|
||||
threads = runningScriptObj.threads;
|
||||
} else {
|
||||
runningScriptObj.threads = 1;
|
||||
}
|
||||
const ramUsage = roundToTwo(getRamUsageFromRunningScript(runningScriptObj) * threads);
|
||||
const ramAvailable = server.maxRam - server.ramUsed;
|
||||
if (ramUsage > ramAvailable) {
|
||||
dialogBoxCreate(
|
||||
`Not enough RAM to run script ${runningScriptObj.filename} with args ` +
|
||||
`${arrayToString(runningScriptObj.args)}. This likely occurred because you re-loaded ` +
|
||||
`the game and the script's RAM usage increased (either because of an update to the game or ` +
|
||||
`your changes to the script.)`
|
||||
);
|
||||
return;
|
||||
}
|
||||
server.ramUsed = roundToTwo(server.ramUsed + ramUsage);
|
||||
|
||||
// Get the pid
|
||||
const pid = generateNextPid();
|
||||
if (pid === -1) {
|
||||
throw new Error(
|
||||
`Failed to start script because could not find available PID. This is most ` +
|
||||
`because you have too many scripts running.`
|
||||
);
|
||||
}
|
||||
|
||||
// Create the WorkerScript. NOTE: WorkerScript ctor will set the underlying
|
||||
// RunningScript's PID as well
|
||||
const s = new WorkerScript(runningScriptObj, pid, NetscriptFunctions);
|
||||
s.ramUsage = ramUsage;
|
||||
|
||||
// Start the script's execution
|
||||
let p = null; // Script's resulting promise
|
||||
if (s.name.endsWith(".js") || s.name.endsWith(".ns")) {
|
||||
p = startNetscript2Script(s);
|
||||
} else {
|
||||
p = startNetscript1Script(s);
|
||||
if (!(p instanceof Promise)) { return; }
|
||||
}
|
||||
|
||||
// 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;
|
||||
w.env.stopFlag = true;
|
||||
killWorkerScript(s);
|
||||
w.scriptRef.log("Script finished running");
|
||||
}).catch(function(w) {
|
||||
if (w instanceof Error) {
|
||||
@ -502,6 +532,7 @@ export function runScriptsLoop() {
|
||||
w.scriptRef.log("Script crashed with runtime error");
|
||||
} else {
|
||||
w.scriptRef.log("Script killed");
|
||||
return; // Already killed, so stop here
|
||||
}
|
||||
w.running = false;
|
||||
w.env.stopFlag = true;
|
||||
@ -513,64 +544,13 @@ export function runScriptsLoop() {
|
||||
dialogBoxCreate("An unknown script died for an unknown reason. This is a bug please contact game dev");
|
||||
console.log(w);
|
||||
}
|
||||
|
||||
killWorkerScript(s);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setTimeoutRef(runScriptsLoop, 3e3);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a RunningScript object, queues that script to be run
|
||||
*/
|
||||
export function addWorkerScript(runningScriptObj, server) {
|
||||
var filename = runningScriptObj.filename;
|
||||
|
||||
//Update server's ram usage
|
||||
var threads = 1;
|
||||
if (runningScriptObj.threads && !isNaN(runningScriptObj.threads)) {
|
||||
threads = runningScriptObj.threads;
|
||||
} else {
|
||||
runningScriptObj.threads = 1;
|
||||
}
|
||||
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 " +
|
||||
arrayToString(runningScriptObj.args) + ". This likely occurred because you re-loaded " +
|
||||
"the game and the script's RAM usage increased (either because of an update to the game or " +
|
||||
"your changes to the script.)");
|
||||
return;
|
||||
}
|
||||
server.ramUsed = roundToTwo(server.ramUsed + ramUsage);
|
||||
|
||||
//Create the WorkerScript
|
||||
var s = new WorkerScript(runningScriptObj, NetscriptFunctions);
|
||||
s.ramUsage = ramUsage;
|
||||
|
||||
//Add the WorkerScript to the Active Scripts list
|
||||
addActiveScriptsItem(s);
|
||||
|
||||
//Add the WorkerScript
|
||||
workerScripts.push(s);
|
||||
// Add the WorkerScript to the global pool
|
||||
workerScripts.set(pid, s);
|
||||
WorkerScriptStartStopEventEmitter.emitEvent();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -579,8 +559,8 @@ export function addWorkerScript(runningScriptObj, server) {
|
||||
*/
|
||||
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;
|
||||
for (const ws of workerScripts.values()) {
|
||||
ws.scriptRef.onlineRunningTime += time;
|
||||
}
|
||||
}
|
||||
|
||||
@ -601,7 +581,7 @@ export function loadAllRunningScripts() {
|
||||
|
||||
// Reset modules on all scripts
|
||||
for (let i = 0; i < server.scripts.length; ++i) {
|
||||
server.scripts[i].module = "";
|
||||
server.scripts[i].markUpdated();
|
||||
}
|
||||
|
||||
if (skipScriptLoad) {
|
||||
|
@ -101,6 +101,10 @@ export interface IPlayer {
|
||||
work_money_mult: number;
|
||||
crime_success_mult: number;
|
||||
crime_money_mult: number;
|
||||
bladeburner_max_stamina_mult: number;
|
||||
bladeburner_stamina_gain_mult: number;
|
||||
bladeburner_analysis_mult: number;
|
||||
bladeburner_success_chance_mult: number;
|
||||
|
||||
// Methods
|
||||
applyForAgentJob(sing?: boolean): boolean | void;
|
||||
@ -147,6 +151,7 @@ export interface IPlayer {
|
||||
reapplyAllSourceFiles(): void;
|
||||
regenerateHp(amt: number): void;
|
||||
recordMoneySource(amt: number, source: string): void;
|
||||
setMoney(amt: number): void;
|
||||
startBladeburner(p: object): void;
|
||||
startClass(costMult: number, expMult: number, className: string): void;
|
||||
startCorporation(corpName: string, additionalShares?: number): void;
|
||||
|
@ -36,8 +36,10 @@ import {
|
||||
import { safetlyCreateUniqueServer } from "../../Server/ServerHelpers";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { SpecialServerIps, SpecialServerNames } from "../../Server/SpecialServerIps";
|
||||
import { SourceFiles, applySourceFile } from "../../SourceFile";
|
||||
import { applySourceFile } from "../../SourceFile/applySourceFile";
|
||||
import { SourceFiles } from "../../SourceFile/SourceFiles";
|
||||
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
|
||||
import { influenceStockThroughCompanyWork } from "../../StockMarket/PlayerInfluencing";
|
||||
|
||||
import Decimal from "decimal.js";
|
||||
|
||||
@ -579,8 +581,8 @@ export function startWork(companyName) {
|
||||
}
|
||||
|
||||
export function work(numCycles) {
|
||||
//Cap the number of cycles being processed to whatever would put you at
|
||||
//the work time limit (8 hours)
|
||||
// Cap the number of cycles being processed to whatever would put you at
|
||||
// the work time limit (8 hours)
|
||||
var overMax = false;
|
||||
if (this.timeWorked + (Engine._idleSpeed * numCycles) >= CONSTANTS.MillisecondsPer8Hours) {
|
||||
overMax = true;
|
||||
@ -591,18 +593,21 @@ export function work(numCycles) {
|
||||
this.workRepGainRate = this.getWorkRepGain();
|
||||
this.processWorkEarnings(numCycles);
|
||||
|
||||
//If timeWorked == 8 hours, then finish. You can only gain 8 hours worth of exp and money
|
||||
// If timeWorked == 8 hours, then finish. You can only gain 8 hours worth of exp and money
|
||||
if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer8Hours) {
|
||||
return this.finishWork(false);
|
||||
}
|
||||
|
||||
var comp = Companies[this.companyName], companyRep = "0";
|
||||
const comp = Companies[this.companyName];
|
||||
let companyRep = "0";
|
||||
if (comp == null || !(comp instanceof Company)) {
|
||||
console.error(`Could not find Company: ${this.companyName}`);
|
||||
} else {
|
||||
companyRep = comp.playerReputation;
|
||||
}
|
||||
|
||||
influenceStockThroughCompanyWork(comp, this.workRepGainRate, numCycles);
|
||||
|
||||
const position = this.jobs[this.companyName];
|
||||
|
||||
var txt = document.getElementById("work-in-progress-text");
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { deleteActiveScriptsItem } from "./ActiveScriptsUI";
|
||||
import { Augmentations } from "./Augmentation/Augmentations";
|
||||
import {
|
||||
augmentationExists,
|
||||
|
@ -5,7 +5,7 @@ import { BitNodes } from "./BitNode/BitNode";
|
||||
import { Engine } from "./engine";
|
||||
import { Player } from "./Player";
|
||||
import { prestigeSourceFile } from "./Prestige";
|
||||
import { SourceFiles, SourceFile } from "./SourceFile";
|
||||
import { SourceFiles } from "./SourceFile/SourceFiles";
|
||||
import { PlayerOwnedSourceFile } from "./SourceFile/PlayerOwnedSourceFile";
|
||||
import { Terminal } from "./Terminal";
|
||||
import { setTimeoutRef } from "./utils/SetTimeoutRef";
|
||||
@ -20,9 +20,6 @@ import {
|
||||
import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners";
|
||||
import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement";
|
||||
|
||||
|
||||
|
||||
|
||||
// Returns promise
|
||||
function writeRedPillLine(line) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
|
@ -1,4 +1,10 @@
|
||||
// Calculate a script's RAM usage
|
||||
/**
|
||||
* Implements RAM Calculation functionality.
|
||||
*
|
||||
* Uses the acorn.js library to parse a script's code into an AST and
|
||||
* recursively walk through that AST, calculating RAM usage along
|
||||
* the way
|
||||
*/
|
||||
import * as walk from "acorn-walk";
|
||||
|
||||
import { RamCalculationErrorCode } from "./RamCalculationErrorCodes";
|
||||
@ -15,19 +21,26 @@ const specialReferenceWHILE = "__SPECIAL_referenceWhile";
|
||||
// The global scope of a script is registered under this key during parsing.
|
||||
const memCheckGlobalKey = ".__GLOBAL__";
|
||||
|
||||
// Calcluates the amount of RAM a script uses. Uses parsing and AST walking only,
|
||||
// rather than NetscriptEvaluator. This is useful because NetscriptJS code does
|
||||
// not work under NetscriptEvaluator.
|
||||
/**
|
||||
* Parses code into an AST and walks through it recursively to calculate
|
||||
* RAM usage. Also accounts for imported modules.
|
||||
* @param {Script[]} otherScripts - All other scripts on the server. Used to account for imported scripts
|
||||
* @param {string} codeCopy - The code being parsed
|
||||
* @param {WorkerScript} workerScript - Object containing RAM costs of Netscript functions. Also used to
|
||||
* keep track of what functions have/havent been accounted for
|
||||
*/
|
||||
async function parseOnlyRamCalculate(otherScripts, code, workerScript) {
|
||||
try {
|
||||
// Maps dependent identifiers to their dependencies.
|
||||
//
|
||||
// The initial identifier is __SPECIAL_INITIAL_MODULE__.__GLOBAL__.
|
||||
// It depends on all the functions declared in the module, all the global scopes
|
||||
// of its imports, and any identifiers referenced in this global scope. Each
|
||||
// function depends on all the identifiers referenced internally.
|
||||
// We walk the dependency graph to calculate RAM usage, given that some identifiers
|
||||
// reference Netscript functions which have a RAM cost.
|
||||
/**
|
||||
* Maps dependent identifiers to their dependencies.
|
||||
*
|
||||
* The initial identifier is __SPECIAL_INITIAL_MODULE__.__GLOBAL__.
|
||||
* It depends on all the functions declared in the module, all the global scopes
|
||||
* of its imports, and any identifiers referenced in this global scope. Each
|
||||
* function depends on all the identifiers referenced internally.
|
||||
* We walk the dependency graph to calculate RAM usage, given that some identifiers
|
||||
* reference Netscript functions which have a RAM cost.
|
||||
*/
|
||||
let dependencyMap = {};
|
||||
|
||||
// Scripts we've parsed.
|
||||
@ -48,19 +61,20 @@ async function parseOnlyRamCalculate(otherScripts, code, workerScript) {
|
||||
}
|
||||
}
|
||||
|
||||
// Splice all the references in.
|
||||
//Spread syntax not supported in edge, use Object.assign instead
|
||||
//dependencyMap = {...dependencyMap, ...result.dependencyMap};
|
||||
// Splice all the references in
|
||||
dependencyMap = Object.assign(dependencyMap, result.dependencyMap);
|
||||
}
|
||||
|
||||
// Parse the initial module, which is the "main" script that is being run
|
||||
const initialModule = "__SPECIAL_INITIAL_MODULE__";
|
||||
parseCode(code, initialModule);
|
||||
|
||||
// Process additional modules, which occurs if the "main" script has any imports
|
||||
while (parseQueue.length > 0) {
|
||||
// Get the code from the server.
|
||||
const nextModule = parseQueue.shift();
|
||||
|
||||
// Additional modules can either be imported from the web (in which case we use
|
||||
// a dynamic import), or from other in-game scripts
|
||||
let code;
|
||||
if (nextModule.startsWith("https://") || nextModule.startsWith("http://")) {
|
||||
try {
|
||||
@ -136,10 +150,8 @@ async function parseOnlyRamCalculate(otherScripts, code, workerScript) {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if this identifier is a function in the workerscript env.
|
||||
// Check if this identifier is a function in the workerScript environment.
|
||||
// If it is, then we need to get its RAM cost.
|
||||
//
|
||||
// TODO it would be simpler to just reference a dictionary.
|
||||
try {
|
||||
function applyFuncRam(func) {
|
||||
if (typeof func === "function") {
|
||||
@ -170,7 +182,7 @@ async function parseOnlyRamCalculate(otherScripts, code, workerScript) {
|
||||
workerScript.loadedFns[ref] = true;
|
||||
}
|
||||
|
||||
// This accounts for namespaces (Bladeburner, CodingCOntract)
|
||||
// This accounts for namespaces (Bladeburner, CodingCpntract, etc.)
|
||||
let func;
|
||||
if (ref in workerScript.env.vars.bladeburner) {
|
||||
func = workerScript.env.vars.bladeburner[ref];
|
||||
@ -196,9 +208,12 @@ async function parseOnlyRamCalculate(otherScripts, code, workerScript) {
|
||||
}
|
||||
}
|
||||
|
||||
// Parses one script and calculates its ram usage, for the global scope and each function.
|
||||
// Returns a cost map and a dependencyMap for the module. Returns a reference map to be joined
|
||||
// onto the main reference map, and a list of modules that need to be parsed.
|
||||
/**
|
||||
* Helper function that parses a single script. It returns a map of all dependencies,
|
||||
* which are items in the code's AST that potentially need to be evaluated
|
||||
* for RAM usage calculations. It also returns an array of additional modules
|
||||
* that need to be parsed (i.e. are 'import'ed scripts).
|
||||
*/
|
||||
function parseOnlyCalculateDeps(code, currentModule) {
|
||||
const ast = parse(code, {sourceType:"module", ecmaVersion: 8});
|
||||
|
||||
@ -296,6 +311,12 @@ function parseOnlyCalculateDeps(code, currentModule) {
|
||||
return {dependencyMap: dependencyMap, additionalModules: additionalModules};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate's a scripts RAM Usage
|
||||
* @param {string} codeCopy - The script's code
|
||||
* @param {Script[]} otherScripts - All other scripts on the server.
|
||||
* Used to account for imported scripts
|
||||
*/
|
||||
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
|
||||
|
@ -1,5 +1,7 @@
|
||||
// Class representing a Script instance that is actively running.
|
||||
// A Script can have multiple active instances
|
||||
/**
|
||||
* 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 { Settings } from "../Settings/Settings";
|
||||
@ -22,10 +24,8 @@ export class RunningScript {
|
||||
// Script arguments
|
||||
args: any[] = [];
|
||||
|
||||
// Holds a map of servers hacked, where server = key and the value for each
|
||||
// server is an array of four numbers. The four numbers represent:
|
||||
// [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken]
|
||||
// This data is used for offline progress
|
||||
// Map of [key: server ip] -> Hacking data. Used for offline progress calculations.
|
||||
// Hacking data format: [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken]
|
||||
dataMap: IMap<number[]> = {};
|
||||
|
||||
// Script filename
|
||||
@ -56,6 +56,9 @@ export class RunningScript {
|
||||
// Number of seconds that this script has been running online
|
||||
onlineRunningTime: number = 0.01;
|
||||
|
||||
// Process ID. Must be an integer and equals the PID of corresponding WorkerScript
|
||||
pid: number = -1;
|
||||
|
||||
// How much RAM this script uses for ONE thread
|
||||
ramUsage: number = 0;
|
||||
|
||||
@ -69,22 +72,20 @@ export class RunningScript {
|
||||
if (script == null) { return; }
|
||||
this.filename = script.filename;
|
||||
this.args = args;
|
||||
|
||||
this.server = script.server; //IP Address only
|
||||
this.server = script.server;
|
||||
this.ramUsage = script.ramUsage;
|
||||
}
|
||||
|
||||
log(txt: string): void {
|
||||
if (this.logs.length > Settings.MaxLogCapacity) {
|
||||
//Delete first element and add new log entry to the end.
|
||||
//TODO Eventually it might be better to replace this with circular array
|
||||
//to improve performance
|
||||
this.logs.shift();
|
||||
}
|
||||
|
||||
let logEntry = txt;
|
||||
if (FconfSettings.ENABLE_TIMESTAMPS) {
|
||||
logEntry = "[" + getTimestamp() + "] " + logEntry;
|
||||
}
|
||||
|
||||
this.logs.push(logEntry);
|
||||
this.logUpd = true;
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
// Class representing a script file
|
||||
// This does NOT represent a script that is actively running and
|
||||
// being evaluated. See RunningScript for that
|
||||
/**
|
||||
* Class representing a script file.
|
||||
*
|
||||
* This does NOT represent a script that is actively running and
|
||||
* being evaluated. See RunningScript for that
|
||||
*/
|
||||
import { calculateRamUsage } from "./RamCalculations";
|
||||
import { Page, routing } from "../ui/navigationTracking";
|
||||
|
||||
@ -12,6 +15,8 @@ import {
|
||||
} from "../../utils/JSONReviver";
|
||||
import { roundToTwo } from "../../utils/helpers/roundToTwo";
|
||||
|
||||
let globalModuleSequenceNumber = 0;
|
||||
|
||||
export class Script {
|
||||
// Initializes a Script Object from a JSON save state
|
||||
static fromJSON(value: any): Script {
|
||||
@ -28,22 +33,33 @@ export class Script {
|
||||
// This is only applicable for NetscriptJS
|
||||
module: any = "";
|
||||
|
||||
// The timestamp when when the script was last updated.
|
||||
moduleSequenceNumber: number;
|
||||
|
||||
// Only used with NS2 scripts; the list of dependency script filenames. This is constructed
|
||||
// whenever the script is first evaluated, and therefore may be out of date if the script
|
||||
// has been updated since it was last run.
|
||||
dependencies: string[] = [];
|
||||
|
||||
// Amount of RAM this Script requres to run
|
||||
ramUsage: number = 0;
|
||||
|
||||
// IP of server that this script is on.
|
||||
server: string = "";
|
||||
|
||||
|
||||
constructor(fn: string="", code: string="", server: string="", otherScripts: Script[]=[]) {
|
||||
this.filename = fn;
|
||||
this.code = code;
|
||||
this.ramUsage = 0;
|
||||
this.server = server; // IP of server this script is on
|
||||
this.module = "";
|
||||
this.moduleSequenceNumber = ++globalModuleSequenceNumber;
|
||||
if (this.code !== "") { this.updateRamUsage(otherScripts); }
|
||||
};
|
||||
|
||||
/**
|
||||
* Download the script as a file
|
||||
*/
|
||||
download(): void {
|
||||
const filename = this.filename + ".js";
|
||||
const file = new Blob([this.code], {type: 'text/plain'});
|
||||
@ -63,10 +79,23 @@ export class Script {
|
||||
}
|
||||
}
|
||||
|
||||
// Save a script FROM THE SCRIPT EDITOR
|
||||
/**
|
||||
* Marks this script as having been updated. It will be recompiled next time something tries
|
||||
* to exec it.
|
||||
*/
|
||||
markUpdated() {
|
||||
this.module = "";
|
||||
this.moduleSequenceNumber = ++globalModuleSequenceNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a script from the script editor
|
||||
* @param {string} code - The new contents of the script
|
||||
* @param {Script[]} otherScripts - Other scripts on the server. Used to process imports
|
||||
*/
|
||||
saveScript(code: string, serverIp: string, otherScripts: Script[]): void {
|
||||
if (routing.isOn(Page.ScriptEditor)) {
|
||||
//Update code and filename
|
||||
// Update code and filename
|
||||
this.code = code.replace(/^\s+|\s+$/g, '');
|
||||
|
||||
const filenameElem: HTMLInputElement | null = document.getElementById("script-editor-filename") as HTMLInputElement;
|
||||
@ -75,18 +104,16 @@ export class Script {
|
||||
return;
|
||||
}
|
||||
this.filename = filenameElem!.value;
|
||||
|
||||
// Server
|
||||
this.server = serverIp;
|
||||
|
||||
//Calculate/update ram usage, execution time, etc.
|
||||
this.updateRamUsage(otherScripts);
|
||||
|
||||
this.module = "";
|
||||
this.markUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
// Updates the script's RAM usage based on its code
|
||||
/**
|
||||
* Calculates and updates the script's RAM usage based on its code
|
||||
* @param {Script[]} otherScripts - Other scripts on the server. Used to process imports
|
||||
*/
|
||||
async updateRamUsage(otherScripts: Script[]) {
|
||||
var res = await calculateRamUsage(this.code, otherScripts);
|
||||
if (res > 0) {
|
||||
|
@ -233,7 +233,7 @@ export class BaseServer {
|
||||
let script = this.scripts[i];
|
||||
script.code = code;
|
||||
script.updateRamUsage(this.scripts);
|
||||
script.module = "";
|
||||
script.markUpdated();
|
||||
ret.overwritten = true;
|
||||
ret.success = true;
|
||||
return ret;
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
// This could actually be a JSON file as it should be constant metadata to be imported...
|
||||
import { IMinMaxRange } from "../../types";
|
||||
import { LocationName } from "../../Locations/data/LocationNames";
|
||||
|
||||
/**
|
||||
* The metadata describing the base state of servers on the network.
|
||||
@ -82,7 +83,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 15,
|
||||
numOpenPortsRequired: 5,
|
||||
organizationName: "ECorp",
|
||||
organizationName: LocationName.AevumECorp,
|
||||
requiredHackingSkill: {
|
||||
max: 1400,
|
||||
min: 1050,
|
||||
@ -98,7 +99,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 15,
|
||||
numOpenPortsRequired: 5,
|
||||
organizationName: "MegaCorp",
|
||||
organizationName: LocationName.Sector12MegaCorp,
|
||||
requiredHackingSkill: {
|
||||
max: 1350,
|
||||
min: 1100,
|
||||
@ -117,7 +118,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 14,
|
||||
numOpenPortsRequired: 5,
|
||||
organizationName: "Bachman & Associates",
|
||||
organizationName: LocationName.AevumBachmanAndAssociates,
|
||||
requiredHackingSkill: {
|
||||
max: 1150,
|
||||
min: 900,
|
||||
@ -144,7 +145,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 14,
|
||||
numOpenPortsRequired: 5,
|
||||
organizationName: "Blade Industries",
|
||||
organizationName: LocationName.Sector12BladeIndustries,
|
||||
requiredHackingSkill: {
|
||||
max: 1200,
|
||||
min: 900,
|
||||
@ -164,7 +165,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 14,
|
||||
numOpenPortsRequired: 5,
|
||||
organizationName: "New World Order",
|
||||
organizationName: LocationName.VolhavenNWO,
|
||||
requiredHackingSkill: {
|
||||
max: 1300,
|
||||
min: 950,
|
||||
@ -190,7 +191,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 14,
|
||||
numOpenPortsRequired: 5,
|
||||
organizationName: "Clarke Incorporated",
|
||||
organizationName: LocationName.AevumClarkeIncorporated,
|
||||
requiredHackingSkill: {
|
||||
max: 1250,
|
||||
min: 950,
|
||||
@ -220,7 +221,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 13,
|
||||
numOpenPortsRequired: 5,
|
||||
organizationName: "OmniTek Incorporated",
|
||||
organizationName: LocationName.VolhavenOmniTekIncorporated,
|
||||
requiredHackingSkill: {
|
||||
max: 1100,
|
||||
min: 900,
|
||||
@ -242,7 +243,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 13,
|
||||
numOpenPortsRequired: 5,
|
||||
organizationName: "FourSigma",
|
||||
organizationName: LocationName.Sector12FourSigma,
|
||||
requiredHackingSkill: {
|
||||
max: 1250,
|
||||
min: 900,
|
||||
@ -264,7 +265,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 13,
|
||||
numOpenPortsRequired: 5,
|
||||
organizationName: "KuaiGong International",
|
||||
organizationName: LocationName.ChongqingKuaiGongInternational,
|
||||
requiredHackingSkill: {
|
||||
max: 1300,
|
||||
min: 950,
|
||||
@ -291,7 +292,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 12,
|
||||
numOpenPortsRequired: 5,
|
||||
organizationName: "Fulcrum Technologies",
|
||||
organizationName: LocationName.AevumFulcrumTechnologies,
|
||||
requiredHackingSkill: {
|
||||
max: 1250,
|
||||
min: 950,
|
||||
@ -307,7 +308,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
moneyAvailable: 1e6,
|
||||
networkLayer: 15,
|
||||
numOpenPortsRequired: 5,
|
||||
organizationName: "Fulcrum Technologies Assets",
|
||||
organizationName: LocationName.AevumFulcrumTechnologies,
|
||||
requiredHackingSkill: {
|
||||
max: 1600,
|
||||
min: 1100,
|
||||
@ -327,7 +328,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 12,
|
||||
numOpenPortsRequired: 5,
|
||||
organizationName: "Storm Technologies",
|
||||
organizationName: LocationName.IshimaStormTechnologies,
|
||||
requiredHackingSkill: {
|
||||
max: 1075,
|
||||
min: 875,
|
||||
@ -349,7 +350,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 9,
|
||||
numOpenPortsRequired: 5,
|
||||
organizationName: "DefComm",
|
||||
organizationName: LocationName.NewTokyoDefComm,
|
||||
requiredHackingSkill: {
|
||||
max: 1050,
|
||||
min: 850,
|
||||
@ -398,7 +399,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 12,
|
||||
numOpenPortsRequired: 5,
|
||||
organizationName: "Helios Labs",
|
||||
organizationName: LocationName.VolhavenHeliosLabs,
|
||||
requiredHackingSkill: {
|
||||
max: 900,
|
||||
min: 800,
|
||||
@ -425,7 +426,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 12,
|
||||
numOpenPortsRequired: 5,
|
||||
organizationName: "VitaLife",
|
||||
organizationName: LocationName.NewTokyoVitaLife,
|
||||
requiredHackingSkill: {
|
||||
max: 900,
|
||||
min: 775,
|
||||
@ -447,7 +448,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 9,
|
||||
numOpenPortsRequired: 5,
|
||||
organizationName: "Icarus Microsystems",
|
||||
organizationName: LocationName.Sector12IcarusMicrosystems,
|
||||
requiredHackingSkill: {
|
||||
max: 925,
|
||||
min: 850,
|
||||
@ -473,7 +474,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 9,
|
||||
numOpenPortsRequired: 4,
|
||||
organizationName: "Universal Energy",
|
||||
organizationName: LocationName.Sector12UniversalEnergy,
|
||||
requiredHackingSkill: {
|
||||
max: 900,
|
||||
min: 800,
|
||||
@ -575,7 +576,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 7,
|
||||
numOpenPortsRequired: 5,
|
||||
organizationName: "Galactic Cybersystems",
|
||||
organizationName: LocationName.AevumGalacticCybersystems,
|
||||
requiredHackingSkill: {
|
||||
max: 875,
|
||||
min: 825,
|
||||
@ -598,7 +599,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 7,
|
||||
numOpenPortsRequired: 5,
|
||||
organizationName: "AeroCorp",
|
||||
organizationName: LocationName.AevumAeroCorp,
|
||||
requiredHackingSkill: {
|
||||
max: 925,
|
||||
min: 850,
|
||||
@ -625,7 +626,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 8,
|
||||
numOpenPortsRequired: 5,
|
||||
organizationName: "Omnia Cybersystems",
|
||||
organizationName: LocationName.VolhavenOmniaCybersystems,
|
||||
requiredHackingSkill: {
|
||||
max: 950,
|
||||
min: 850,
|
||||
@ -700,7 +701,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 9,
|
||||
numOpenPortsRequired: 5,
|
||||
organizationName: "Solaris Space Systems",
|
||||
organizationName: LocationName.ChongqingSolarisSpaceSystems,
|
||||
requiredHackingSkill: {
|
||||
max: 850,
|
||||
min: 750,
|
||||
@ -722,7 +723,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 8,
|
||||
numOpenPortsRequired: 5,
|
||||
organizationName: "Delta One",
|
||||
organizationName: LocationName.Sector12DeltaOne,
|
||||
requiredHackingSkill: {
|
||||
max: 900,
|
||||
min: 800,
|
||||
@ -749,7 +750,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 7,
|
||||
numOpenPortsRequired: 4,
|
||||
organizationName: "Global Pharmaceuticals",
|
||||
organizationName: LocationName.NewTokyoGlobalPharmaceuticals,
|
||||
requiredHackingSkill: {
|
||||
max: 850,
|
||||
min: 750,
|
||||
@ -771,7 +772,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 10,
|
||||
numOpenPortsRequired: 4,
|
||||
organizationName: "Nova Medical",
|
||||
organizationName: LocationName.IshimaNovaMedical,
|
||||
requiredHackingSkill: {
|
||||
max: 850,
|
||||
min: 775,
|
||||
@ -845,7 +846,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 6,
|
||||
numOpenPortsRequired: 4,
|
||||
organizationName: "Lexo Corporation",
|
||||
organizationName: LocationName.VolhavenLexoCorp,
|
||||
requiredHackingSkill: {
|
||||
max: 750,
|
||||
min: 650,
|
||||
@ -871,7 +872,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 6,
|
||||
numOpenPortsRequired: 3,
|
||||
organizationName: "Rho Construction",
|
||||
organizationName: LocationName.AevumRhoConstruction,
|
||||
requiredHackingSkill: {
|
||||
max: 525,
|
||||
min: 475,
|
||||
@ -898,7 +899,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 6,
|
||||
numOpenPortsRequired: 4,
|
||||
organizationName: "Alpha Enterprises",
|
||||
organizationName: LocationName.Sector12AlphaEnterprises,
|
||||
requiredHackingSkill: {
|
||||
max: 600,
|
||||
min: 500,
|
||||
@ -924,7 +925,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 6,
|
||||
numOpenPortsRequired: 4,
|
||||
organizationName: "Aevum Police Network",
|
||||
organizationName: LocationName.AevumPolice,
|
||||
requiredHackingSkill: {
|
||||
max: 450,
|
||||
min: 400,
|
||||
@ -955,7 +956,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 5,
|
||||
numOpenPortsRequired: 3,
|
||||
organizationName: "Rothman University Network",
|
||||
organizationName: LocationName.Sector12RothmanUniversity,
|
||||
requiredHackingSkill: {
|
||||
max: 430,
|
||||
min: 370,
|
||||
@ -981,7 +982,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 5,
|
||||
numOpenPortsRequired: 5,
|
||||
organizationName: "ZB Institute of Technology Network",
|
||||
organizationName: LocationName.VolhavenZBInstituteOfTechnology,
|
||||
requiredHackingSkill: {
|
||||
max: 775,
|
||||
min: 725,
|
||||
@ -1012,7 +1013,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 5,
|
||||
numOpenPortsRequired: 3,
|
||||
organizationName: "Summit University Network",
|
||||
organizationName: LocationName.AevumSummitUniversity,
|
||||
requiredHackingSkill: {
|
||||
max: 475,
|
||||
min: 425,
|
||||
@ -1034,7 +1035,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 5,
|
||||
numOpenPortsRequired: 4,
|
||||
organizationName: "SysCore Securities",
|
||||
organizationName: LocationName.VolhavenSysCoreSecurities,
|
||||
requiredHackingSkill: {
|
||||
max: 650,
|
||||
min: 550,
|
||||
@ -1110,7 +1111,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 4,
|
||||
numOpenPortsRequired: 3,
|
||||
organizationName: "CompuTek",
|
||||
organizationName: LocationName.VolhavenCompuTek,
|
||||
requiredHackingSkill: {
|
||||
max: 400,
|
||||
min: 300,
|
||||
@ -1134,7 +1135,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
moneyAvailable: 275000000,
|
||||
networkLayer: 4,
|
||||
numOpenPortsRequired: 3,
|
||||
organizationName: "Netlink Technologies",
|
||||
organizationName: LocationName.AevumNetLinkTechnologies,
|
||||
requiredHackingSkill: {
|
||||
max: 425,
|
||||
min: 375,
|
||||
@ -1174,7 +1175,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
moneyAvailable: 2000000,
|
||||
networkLayer: 1,
|
||||
numOpenPortsRequired: 0,
|
||||
organizationName: "Food N Stuff Supermarket",
|
||||
organizationName: LocationName.Sector12FoodNStuff,
|
||||
requiredHackingSkill: 1,
|
||||
serverGrowth: 5,
|
||||
},
|
||||
@ -1196,7 +1197,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
moneyAvailable: 2500000,
|
||||
networkLayer: 1,
|
||||
numOpenPortsRequired: 0,
|
||||
organizationName: "Joe's Guns",
|
||||
organizationName: "Joes Guns",
|
||||
requiredHackingSkill: 10,
|
||||
serverGrowth: 20,
|
||||
},
|
||||
@ -1305,7 +1306,7 @@ export const serverMetadata: IServerMetadata[] = [
|
||||
},
|
||||
networkLayer: 3,
|
||||
numOpenPortsRequired: 2,
|
||||
organizationName: "Omega Software",
|
||||
organizationName: LocationName.IshimaOmegaSoftware,
|
||||
requiredHackingSkill: {
|
||||
max: 220,
|
||||
min: 180,
|
||||
|
@ -1,256 +0,0 @@
|
||||
import { Player } from "./Player";
|
||||
import { BitNodes } from "./BitNode/BitNode";
|
||||
|
||||
// Each SourceFile corresponds to a BitNode with the same number
|
||||
function SourceFile(number, info="") {
|
||||
var bitnodeKey = "BitNode" + number;
|
||||
var bitnode = BitNodes[bitnodeKey];
|
||||
if (bitnode == null) {
|
||||
throw new Error("Invalid Bit Node for this Source File");
|
||||
}
|
||||
|
||||
this.n = number;
|
||||
this.name = "Source-File " + number + ": " + bitnode.name;
|
||||
this.lvl = 1;
|
||||
this.info = info;
|
||||
this.owned = false;
|
||||
}
|
||||
|
||||
let SourceFiles = {};
|
||||
function initSourceFiles() {
|
||||
SourceFiles = {};
|
||||
SourceFiles["SourceFile1"] = new SourceFile(1, "This Source-File lets the player start with 32GB of RAM on his/her " +
|
||||
"home computer. It also increases all of the player's multipliers by:<br><br>" +
|
||||
"Level 1: 16%<br>" +
|
||||
"Level 2: 24%<br>" +
|
||||
"Level 3: 28%");
|
||||
SourceFiles["SourceFile2"] = new SourceFile(2, "This Source-File allows you to form gangs in other BitNodes " +
|
||||
"once your karma decreases to a certain value. It also increases the player's " +
|
||||
"crime success rate, crime money, and charisma multipliers by:<br><br>" +
|
||||
"Level 1: 24%<br>" +
|
||||
"Level 2: 36%<br>" +
|
||||
"Level 3: 42%");
|
||||
SourceFiles["SourceFile3"] = new SourceFile(3,"This Source-File lets you create corporations on other BitNodes (although " +
|
||||
"some BitNodes will disable this mechanic). This Source-File also increases your charisma and company salary multipliers by:<br>" +
|
||||
"Level 1: 8%<br>" +
|
||||
"Level 2: 12%<br>" +
|
||||
"Level 3: 14%");
|
||||
SourceFiles["SourceFile4"] = new SourceFile(4, "This Source-File lets you access and use the Singularity Functions in every BitNode. Every " +
|
||||
"level of this Source-File opens up more of the Singularity Functions you can use.");
|
||||
SourceFiles["SourceFile5"] = new SourceFile(5, "This Source-File grants a special new stat called Intelligence. Intelligence " +
|
||||
"is unique because it is permanent and persistent (it never gets reset back to 1). However, " +
|
||||
"gaining Intelligence experience is much slower than other stats, and it is also hidden (you won't " +
|
||||
"know when you gain experience and how much). Higher Intelligence levels will boost your production " +
|
||||
"for many actions in the game. In addition, this Source-File will unlock the getBitNodeMultipliers() " +
|
||||
"Netscript function, and will raise all of your hacking-related multipliers by:<br><br> " +
|
||||
"Level 1: 8%<br>" +
|
||||
"Level 2: 12%<br>" +
|
||||
"Level 3: 14%");
|
||||
SourceFiles["SourceFile6"] = new SourceFile(6, "This Source-File allows you to access the NSA's Bladeburner Division in other " +
|
||||
"BitNodes. In addition, this Source-File will raise both the level and experience gain rate of all your combat stats by:<br><br>" +
|
||||
"Level 1: 8%<br>" +
|
||||
"Level 2: 12%<br>" +
|
||||
"Level 3: 14%");
|
||||
SourceFiles["SourceFile7"] = new SourceFile(7, "This Source-File allows you to access the Bladeburner Netscript API in other " +
|
||||
"BitNodes. In addition, this Source-File will increase all of your Bladeburner multipliers by:<br><br>" +
|
||||
"Level 1: 8%<br>" +
|
||||
"Level 2: 12%<br>" +
|
||||
"Level 3: 14%");
|
||||
SourceFiles["SourceFile8"] = new SourceFile(8, "This Source-File grants the following benefits:<br><br>" +
|
||||
"Level 1: Permanent access to WSE and TIX API<br>" +
|
||||
"Level 2: Ability to short stocks in other BitNodes<br>" +
|
||||
"Level 3: Ability to use limit/stop orders in other BitNodes<br><br>" +
|
||||
"This Source-File also increases your hacking growth multipliers by: " +
|
||||
"<br>Level 1: 12%<br>Level 2: 18%<br>Level 3: 21%");
|
||||
SourceFiles["SourceFile9"] = new SourceFile(9, "This Source-File grants the following benefits:<br><br>" +
|
||||
"Level 1: Permanently unlocks the Hacknet Server in other BitNodes<br>" +
|
||||
"Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode<br>" +
|
||||
"Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode<br><br>" +
|
||||
"(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT " +
|
||||
"when installing Augmentations)");
|
||||
SourceFiles["SourceFile10"] = new SourceFile(10, "This Source-File unlocks Sleeve technology in other BitNodes. Each level of this " +
|
||||
"Source-File also grants you a Duplicate Sleeve");
|
||||
SourceFiles["SourceFile11"] = new SourceFile(11, "This Source-File makes it so that company favor increases BOTH the player's salary and reputation gain rate " +
|
||||
"at that company by 1% per favor (rather than just the reputation gain). This Source-File also " +
|
||||
" increases the player's company salary and reputation gain multipliers by:<br><br>" +
|
||||
"Level 1: 32%<br>" +
|
||||
"Level 2: 48%<br>" +
|
||||
"Level 3: 56%<br>");
|
||||
SourceFiles["SourceFile12"] = new SourceFile(12, "This Source-File increases all your multipliers by 1% per level. This effect is multiplicative with itself. " +
|
||||
"In other words, level N of this Source-File will result in a multiplier of 1.01^N (or 0.99^N for multipliers that decrease)");
|
||||
}
|
||||
|
||||
// Takes in a PlayerOwnedSourceFile as the "srcFile" argument
|
||||
function applySourceFile(srcFile) {
|
||||
var srcFileKey = "SourceFile" + srcFile.n;
|
||||
var sourceFileObject = SourceFiles[srcFileKey];
|
||||
if (sourceFileObject == null) {
|
||||
console.log("ERROR: Invalid source file number: " + srcFile.n);
|
||||
return;
|
||||
}
|
||||
|
||||
switch(srcFile.n) {
|
||||
case 1: // The Source Genesis
|
||||
var mult = 0;
|
||||
for (var i = 0; i < srcFile.lvl; ++i) {
|
||||
mult += (16 / (Math.pow(2, i)));
|
||||
}
|
||||
var incMult = 1 + (mult / 100);
|
||||
var decMult = 1 - (mult / 100);
|
||||
Player.hacking_chance_mult *= incMult;
|
||||
Player.hacking_speed_mult *= incMult;
|
||||
Player.hacking_money_mult *= incMult;
|
||||
Player.hacking_grow_mult *= incMult;
|
||||
Player.hacking_mult *= incMult;
|
||||
Player.strength_mult *= incMult;
|
||||
Player.defense_mult *= incMult;
|
||||
Player.dexterity_mult *= incMult;
|
||||
Player.agility_mult *= incMult;
|
||||
Player.charisma_mult *= incMult;
|
||||
Player.hacking_exp_mult *= incMult;
|
||||
Player.strength_exp_mult *= incMult;
|
||||
Player.defense_exp_mult *= incMult;
|
||||
Player.dexterity_exp_mult *= incMult;
|
||||
Player.agility_exp_mult *= incMult;
|
||||
Player.charisma_exp_mult *= incMult;
|
||||
Player.company_rep_mult *= incMult;
|
||||
Player.faction_rep_mult *= incMult;
|
||||
Player.crime_money_mult *= incMult;
|
||||
Player.crime_success_mult *= incMult;
|
||||
Player.hacknet_node_money_mult *= incMult;
|
||||
Player.hacknet_node_purchase_cost_mult *= decMult;
|
||||
Player.hacknet_node_ram_cost_mult *= decMult;
|
||||
Player.hacknet_node_core_cost_mult *= decMult;
|
||||
Player.hacknet_node_level_cost_mult *= decMult;
|
||||
Player.work_money_mult *= incMult;
|
||||
break;
|
||||
case 2: // Rise of the Underworld
|
||||
var mult = 0;
|
||||
for (var i = 0; i < srcFile.lvl; ++i) {
|
||||
mult += (24 / (Math.pow(2, i)));
|
||||
}
|
||||
var incMult = 1 + (mult / 100);
|
||||
Player.crime_money_mult *= incMult;
|
||||
Player.crime_success_mult *= incMult;
|
||||
Player.charisma_mult *= incMult;
|
||||
break;
|
||||
case 3: // Corporatocracy
|
||||
var mult = 0;
|
||||
for (var i = 0; i < srcFile.lvl; ++i) {
|
||||
mult += (8 / (Math.pow(2, i)));
|
||||
}
|
||||
var incMult = 1 + (mult / 100);
|
||||
Player.charisma_mult *= incMult;
|
||||
Player.work_money_mult *= incMult;
|
||||
break;
|
||||
case 4: // The Singularity
|
||||
// No effects, just gives access to Singularity functions
|
||||
break;
|
||||
case 5: // Artificial Intelligence
|
||||
var mult = 0;
|
||||
for (var i = 0; i < srcFile.lvl; ++i) {
|
||||
mult += (8 / (Math.pow(2, i)));
|
||||
}
|
||||
var incMult = 1 + (mult / 100);
|
||||
Player.hacking_chance_mult *= incMult;
|
||||
Player.hacking_speed_mult *= incMult;
|
||||
Player.hacking_money_mult *= incMult;
|
||||
Player.hacking_grow_mult *= incMult;
|
||||
Player.hacking_mult *= incMult;
|
||||
Player.hacking_exp_mult *= incMult;
|
||||
break;
|
||||
case 6: // Bladeburner
|
||||
var mult = 0;
|
||||
for (var i = 0; i < srcFile.lvl; ++i) {
|
||||
mult += (8 / (Math.pow(2, i)));
|
||||
}
|
||||
var incMult = 1 + (mult / 100);
|
||||
Player.strength_exp_mult *= incMult;
|
||||
Player.defense_exp_mult *= incMult;
|
||||
Player.dexterity_exp_mult *= incMult;
|
||||
Player.agility_exp_mult *= incMult;
|
||||
Player.strength_mult *= incMult;
|
||||
Player.defense_mult *= incMult;
|
||||
Player.dexterity_mult *= incMult;
|
||||
Player.agility_mult *= incMult;
|
||||
break;
|
||||
case 7: // Bladeburner 2079
|
||||
var mult = 0;
|
||||
for (var i = 0; i < srcFile.lvl; ++i) {
|
||||
mult += (8 / (Math.pow(2, i)));
|
||||
}
|
||||
var incMult = 1 + (mult / 100);
|
||||
Player.bladeburner_max_stamina_mult *= incMult;
|
||||
Player.bladeburner_stamina_gain_mult *= incMult;
|
||||
Player.bladeburner_analysis_mult *= incMult;
|
||||
Player.bladeburner_success_chance_mult *= incMult;
|
||||
break;
|
||||
case 8: // Ghost of Wall Street
|
||||
var mult = 0;
|
||||
for (var i = 0; i < srcFile.lvl; ++i) {
|
||||
mult += (12 / (Math.pow(2, i)));
|
||||
}
|
||||
var incMult = 1 + (mult / 100);
|
||||
Player.hacking_grow_mult *= incMult;
|
||||
break;
|
||||
case 9: // Hacktocracy
|
||||
// This has non-multiplier effects
|
||||
break;
|
||||
case 10: // Digital Carbon
|
||||
// No effects, just grants sleeves
|
||||
break;
|
||||
case 11: // The Big Crash
|
||||
var mult = 0;
|
||||
for (var i = 0; i < srcFile.lvl; ++i) {
|
||||
mult += (32 / (Math.pow(2, i)));
|
||||
}
|
||||
var incMult = 1 + (mult / 100);
|
||||
Player.work_money_mult *= incMult;
|
||||
Player.company_rep_mult *= incMult;
|
||||
break;
|
||||
case 12: // The Recursion
|
||||
var inc = Math.pow(1.01, srcFile.lvl);
|
||||
var dec = Math.pow(0.99, srcFile.lvl);
|
||||
|
||||
Player.hacking_chance_mult *= inc;
|
||||
Player.hacking_speed_mult *= inc;
|
||||
Player.hacking_money_mult *= inc;
|
||||
Player.hacking_grow_mult *= inc;
|
||||
Player.hacking_mult *= inc;
|
||||
|
||||
Player.strength_mult *= inc;
|
||||
Player.defense_mult *= inc;
|
||||
Player.dexterity_mult *= inc;
|
||||
Player.agility_mult *= inc;
|
||||
Player.charisma_mult *= inc;
|
||||
|
||||
Player.hacking_exp_mult *= inc;
|
||||
Player.strength_exp_mult *= inc;
|
||||
Player.defense_exp_mult *= inc;
|
||||
Player.dexterity_exp_mult *= inc;
|
||||
Player.agility_exp_mult *= inc;
|
||||
Player.charisma_exp_mult *= inc;
|
||||
|
||||
Player.company_rep_mult *= inc;
|
||||
Player.faction_rep_mult *= inc;
|
||||
|
||||
Player.crime_money_mult *= inc;
|
||||
Player.crime_success_mult *= inc;
|
||||
|
||||
Player.hacknet_node_money_mult *= inc;
|
||||
Player.hacknet_node_purchase_cost_mult *= dec;
|
||||
Player.hacknet_node_ram_cost_mult *= dec;
|
||||
Player.hacknet_node_core_cost_mult *= dec;
|
||||
Player.hacknet_node_level_cost_mult *= dec;
|
||||
|
||||
Player.work_money_mult *= inc;
|
||||
break;
|
||||
default:
|
||||
console.log("ERROR: Invalid source file number: " + srcFile.n);
|
||||
break;
|
||||
}
|
||||
|
||||
sourceFileObject.owned = true;
|
||||
}
|
||||
|
||||
export {SourceFiles, applySourceFile, initSourceFiles};
|
21
src/SourceFile/SourceFile.ts
Normal file
21
src/SourceFile/SourceFile.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { BitNodes } from "../BitNode/BitNode";
|
||||
|
||||
export class SourceFile {
|
||||
info: string;
|
||||
lvl: number = 1;
|
||||
n: number;
|
||||
name: string;
|
||||
owned: boolean = false;
|
||||
|
||||
constructor(number: number, info: string="") {
|
||||
const bitnodeKey = "BitNode" + number;
|
||||
const bitnode = BitNodes[bitnodeKey];
|
||||
if (bitnode == null) {
|
||||
throw new Error("Invalid Bit Node for this Source File");
|
||||
}
|
||||
|
||||
this.n = number;
|
||||
this.name = `Source-File ${number}: ${bitnode.name}`
|
||||
this.info = info;
|
||||
}
|
||||
}
|
64
src/SourceFile/SourceFiles.ts
Normal file
64
src/SourceFile/SourceFiles.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { SourceFile } from "./SourceFile";
|
||||
import { IMap } from "../types";
|
||||
|
||||
export const SourceFiles: IMap<SourceFile> = {};
|
||||
|
||||
SourceFiles["SourceFile1"] = new SourceFile(1, "This Source-File lets the player start with 32GB of RAM on his/her " +
|
||||
"home computer. It also increases all of the player's multipliers by:<br><br>" +
|
||||
"Level 1: 16%<br>" +
|
||||
"Level 2: 24%<br>" +
|
||||
"Level 3: 28%");
|
||||
SourceFiles["SourceFile2"] = new SourceFile(2, "This Source-File allows you to form gangs in other BitNodes " +
|
||||
"once your karma decreases to a certain value. It also increases the player's " +
|
||||
"crime success rate, crime money, and charisma multipliers by:<br><br>" +
|
||||
"Level 1: 24%<br>" +
|
||||
"Level 2: 36%<br>" +
|
||||
"Level 3: 42%");
|
||||
SourceFiles["SourceFile3"] = new SourceFile(3,"This Source-File lets you create corporations on other BitNodes (although " +
|
||||
"some BitNodes will disable this mechanic). This Source-File also increases your charisma and company salary multipliers by:<br>" +
|
||||
"Level 1: 8%<br>" +
|
||||
"Level 2: 12%<br>" +
|
||||
"Level 3: 14%");
|
||||
SourceFiles["SourceFile4"] = new SourceFile(4, "This Source-File lets you access and use the Singularity Functions in every BitNode. Every " +
|
||||
"level of this Source-File opens up more of the Singularity Functions you can use.");
|
||||
SourceFiles["SourceFile5"] = new SourceFile(5, "This Source-File grants a special new stat called Intelligence. Intelligence " +
|
||||
"is unique because it is permanent and persistent (it never gets reset back to 1). However, " +
|
||||
"gaining Intelligence experience is much slower than other stats, and it is also hidden (you won't " +
|
||||
"know when you gain experience and how much). Higher Intelligence levels will boost your production " +
|
||||
"for many actions in the game. In addition, this Source-File will unlock the getBitNodeMultipliers() " +
|
||||
"Netscript function, and will raise all of your hacking-related multipliers by:<br><br> " +
|
||||
"Level 1: 8%<br>" +
|
||||
"Level 2: 12%<br>" +
|
||||
"Level 3: 14%");
|
||||
SourceFiles["SourceFile6"] = new SourceFile(6, "This Source-File allows you to access the NSA's Bladeburner Division in other " +
|
||||
"BitNodes. In addition, this Source-File will raise both the level and experience gain rate of all your combat stats by:<br><br>" +
|
||||
"Level 1: 8%<br>" +
|
||||
"Level 2: 12%<br>" +
|
||||
"Level 3: 14%");
|
||||
SourceFiles["SourceFile7"] = new SourceFile(7, "This Source-File allows you to access the Bladeburner Netscript API in other " +
|
||||
"BitNodes. In addition, this Source-File will increase all of your Bladeburner multipliers by:<br><br>" +
|
||||
"Level 1: 8%<br>" +
|
||||
"Level 2: 12%<br>" +
|
||||
"Level 3: 14%");
|
||||
SourceFiles["SourceFile8"] = new SourceFile(8, "This Source-File grants the following benefits:<br><br>" +
|
||||
"Level 1: Permanent access to WSE and TIX API<br>" +
|
||||
"Level 2: Ability to short stocks in other BitNodes<br>" +
|
||||
"Level 3: Ability to use limit/stop orders in other BitNodes<br><br>" +
|
||||
"This Source-File also increases your hacking growth multipliers by: " +
|
||||
"<br>Level 1: 12%<br>Level 2: 18%<br>Level 3: 21%");
|
||||
SourceFiles["SourceFile9"] = new SourceFile(9, "This Source-File grants the following benefits:<br><br>" +
|
||||
"Level 1: Permanently unlocks the Hacknet Server in other BitNodes<br>" +
|
||||
"Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode<br>" +
|
||||
"Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode<br><br>" +
|
||||
"(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT " +
|
||||
"when installing Augmentations)");
|
||||
SourceFiles["SourceFile10"] = new SourceFile(10, "This Source-File unlocks Sleeve technology in other BitNodes. Each level of this " +
|
||||
"Source-File also grants you a Duplicate Sleeve");
|
||||
SourceFiles["SourceFile11"] = new SourceFile(11, "This Source-File makes it so that company favor increases BOTH the player's salary and reputation gain rate " +
|
||||
"at that company by 1% per favor (rather than just the reputation gain). This Source-File also " +
|
||||
" increases the player's company salary and reputation gain multipliers by:<br><br>" +
|
||||
"Level 1: 32%<br>" +
|
||||
"Level 2: 48%<br>" +
|
||||
"Level 3: 56%<br>");
|
||||
SourceFiles["SourceFile12"] = new SourceFile(12, "This Source-File increases all your multipliers by 1% per level. This effect is multiplicative with itself. " +
|
||||
"In other words, level N of this Source-File will result in a multiplier of 1.01^N (or 0.99^N for multipliers that decrease)");
|
176
src/SourceFile/applySourceFile.ts
Normal file
176
src/SourceFile/applySourceFile.ts
Normal file
@ -0,0 +1,176 @@
|
||||
import { PlayerOwnedSourceFile } from "./PlayerOwnedSourceFile";
|
||||
import { SourceFiles } from "./SourceFiles";
|
||||
|
||||
import { Player } from "../Player";
|
||||
|
||||
export function applySourceFile(srcFile: PlayerOwnedSourceFile) {
|
||||
const srcFileKey = "SourceFile" + srcFile.n;
|
||||
const sourceFileObject = SourceFiles[srcFileKey];
|
||||
if (sourceFileObject == null) {
|
||||
console.error(`Invalid source file number: ${srcFile.n}`);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (srcFile.n) {
|
||||
case 1: // The Source Genesis
|
||||
var mult = 0;
|
||||
for (var i = 0; i < srcFile.lvl; ++i) {
|
||||
mult += (16 / (Math.pow(2, i)));
|
||||
}
|
||||
var incMult = 1 + (mult / 100);
|
||||
var decMult = 1 - (mult / 100);
|
||||
Player.hacking_chance_mult *= incMult;
|
||||
Player.hacking_speed_mult *= incMult;
|
||||
Player.hacking_money_mult *= incMult;
|
||||
Player.hacking_grow_mult *= incMult;
|
||||
Player.hacking_mult *= incMult;
|
||||
Player.strength_mult *= incMult;
|
||||
Player.defense_mult *= incMult;
|
||||
Player.dexterity_mult *= incMult;
|
||||
Player.agility_mult *= incMult;
|
||||
Player.charisma_mult *= incMult;
|
||||
Player.hacking_exp_mult *= incMult;
|
||||
Player.strength_exp_mult *= incMult;
|
||||
Player.defense_exp_mult *= incMult;
|
||||
Player.dexterity_exp_mult *= incMult;
|
||||
Player.agility_exp_mult *= incMult;
|
||||
Player.charisma_exp_mult *= incMult;
|
||||
Player.company_rep_mult *= incMult;
|
||||
Player.faction_rep_mult *= incMult;
|
||||
Player.crime_money_mult *= incMult;
|
||||
Player.crime_success_mult *= incMult;
|
||||
Player.hacknet_node_money_mult *= incMult;
|
||||
Player.hacknet_node_purchase_cost_mult *= decMult;
|
||||
Player.hacknet_node_ram_cost_mult *= decMult;
|
||||
Player.hacknet_node_core_cost_mult *= decMult;
|
||||
Player.hacknet_node_level_cost_mult *= decMult;
|
||||
Player.work_money_mult *= incMult;
|
||||
break;
|
||||
case 2: // Rise of the Underworld
|
||||
var mult = 0;
|
||||
for (var i = 0; i < srcFile.lvl; ++i) {
|
||||
mult += (24 / (Math.pow(2, i)));
|
||||
}
|
||||
var incMult = 1 + (mult / 100);
|
||||
Player.crime_money_mult *= incMult;
|
||||
Player.crime_success_mult *= incMult;
|
||||
Player.charisma_mult *= incMult;
|
||||
break;
|
||||
case 3: // Corporatocracy
|
||||
var mult = 0;
|
||||
for (var i = 0; i < srcFile.lvl; ++i) {
|
||||
mult += (8 / (Math.pow(2, i)));
|
||||
}
|
||||
var incMult = 1 + (mult / 100);
|
||||
Player.charisma_mult *= incMult;
|
||||
Player.work_money_mult *= incMult;
|
||||
break;
|
||||
case 4: // The Singularity
|
||||
// No effects, just gives access to Singularity functions
|
||||
break;
|
||||
case 5: // Artificial Intelligence
|
||||
var mult = 0;
|
||||
for (var i = 0; i < srcFile.lvl; ++i) {
|
||||
mult += (8 / (Math.pow(2, i)));
|
||||
}
|
||||
var incMult = 1 + (mult / 100);
|
||||
Player.hacking_chance_mult *= incMult;
|
||||
Player.hacking_speed_mult *= incMult;
|
||||
Player.hacking_money_mult *= incMult;
|
||||
Player.hacking_grow_mult *= incMult;
|
||||
Player.hacking_mult *= incMult;
|
||||
Player.hacking_exp_mult *= incMult;
|
||||
break;
|
||||
case 6: // Bladeburner
|
||||
var mult = 0;
|
||||
for (var i = 0; i < srcFile.lvl; ++i) {
|
||||
mult += (8 / (Math.pow(2, i)));
|
||||
}
|
||||
var incMult = 1 + (mult / 100);
|
||||
Player.strength_exp_mult *= incMult;
|
||||
Player.defense_exp_mult *= incMult;
|
||||
Player.dexterity_exp_mult *= incMult;
|
||||
Player.agility_exp_mult *= incMult;
|
||||
Player.strength_mult *= incMult;
|
||||
Player.defense_mult *= incMult;
|
||||
Player.dexterity_mult *= incMult;
|
||||
Player.agility_mult *= incMult;
|
||||
break;
|
||||
case 7: // Bladeburner 2079
|
||||
var mult = 0;
|
||||
for (var i = 0; i < srcFile.lvl; ++i) {
|
||||
mult += (8 / (Math.pow(2, i)));
|
||||
}
|
||||
var incMult = 1 + (mult / 100);
|
||||
Player.bladeburner_max_stamina_mult *= incMult;
|
||||
Player.bladeburner_stamina_gain_mult *= incMult;
|
||||
Player.bladeburner_analysis_mult *= incMult;
|
||||
Player.bladeburner_success_chance_mult *= incMult;
|
||||
break;
|
||||
case 8: // Ghost of Wall Street
|
||||
var mult = 0;
|
||||
for (var i = 0; i < srcFile.lvl; ++i) {
|
||||
mult += (12 / (Math.pow(2, i)));
|
||||
}
|
||||
var incMult = 1 + (mult / 100);
|
||||
Player.hacking_grow_mult *= incMult;
|
||||
break;
|
||||
case 9: // Hacktocracy
|
||||
// This has non-multiplier effects
|
||||
break;
|
||||
case 10: // Digital Carbon
|
||||
// No effects, just grants sleeves
|
||||
break;
|
||||
case 11: // The Big Crash
|
||||
var mult = 0;
|
||||
for (var i = 0; i < srcFile.lvl; ++i) {
|
||||
mult += (32 / (Math.pow(2, i)));
|
||||
}
|
||||
var incMult = 1 + (mult / 100);
|
||||
Player.work_money_mult *= incMult;
|
||||
Player.company_rep_mult *= incMult;
|
||||
break;
|
||||
case 12: // The Recursion
|
||||
var inc = Math.pow(1.01, srcFile.lvl);
|
||||
var dec = Math.pow(0.99, srcFile.lvl);
|
||||
|
||||
Player.hacking_chance_mult *= inc;
|
||||
Player.hacking_speed_mult *= inc;
|
||||
Player.hacking_money_mult *= inc;
|
||||
Player.hacking_grow_mult *= inc;
|
||||
Player.hacking_mult *= inc;
|
||||
|
||||
Player.strength_mult *= inc;
|
||||
Player.defense_mult *= inc;
|
||||
Player.dexterity_mult *= inc;
|
||||
Player.agility_mult *= inc;
|
||||
Player.charisma_mult *= inc;
|
||||
|
||||
Player.hacking_exp_mult *= inc;
|
||||
Player.strength_exp_mult *= inc;
|
||||
Player.defense_exp_mult *= inc;
|
||||
Player.dexterity_exp_mult *= inc;
|
||||
Player.agility_exp_mult *= inc;
|
||||
Player.charisma_exp_mult *= inc;
|
||||
|
||||
Player.company_rep_mult *= inc;
|
||||
Player.faction_rep_mult *= inc;
|
||||
|
||||
Player.crime_money_mult *= inc;
|
||||
Player.crime_success_mult *= inc;
|
||||
|
||||
Player.hacknet_node_money_mult *= inc;
|
||||
Player.hacknet_node_purchase_cost_mult *= dec;
|
||||
Player.hacknet_node_ram_cost_mult *= dec;
|
||||
Player.hacknet_node_core_cost_mult *= dec;
|
||||
Player.hacknet_node_level_cost_mult *= dec;
|
||||
|
||||
Player.work_money_mult *= inc;
|
||||
break;
|
||||
default:
|
||||
console.log("ERROR: Invalid source file number: " + srcFile.n);
|
||||
break;
|
||||
}
|
||||
|
||||
sourceFileObject.owned = true;
|
||||
}
|
@ -6,8 +6,7 @@ import { Stock } from "./Stock";
|
||||
import {
|
||||
getBuyTransactionCost,
|
||||
getSellTransactionGain,
|
||||
processBuyTransactionPriceMovement,
|
||||
processSellTransactionPriceMovement
|
||||
processTransactionForecastMovement,
|
||||
} from "./StockMarketHelpers";
|
||||
|
||||
import { PositionTypes } from "./data/PositionTypes";
|
||||
@ -57,7 +56,7 @@ export function buyStock(stock: Stock, shares: number, workerScript: WorkerScrip
|
||||
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)}`);
|
||||
workerScript!.log(`ERROR: buyStock() failed because you do not have enough money to purchase this position. 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)}`);
|
||||
}
|
||||
@ -81,7 +80,7 @@ export function buyStock(stock: Stock, shares: number, workerScript: WorkerScrip
|
||||
const newTotal = origTotal + totalPrice - CONSTANTS.StockMarketCommission;
|
||||
stock.playerShares = Math.round(stock.playerShares + shares);
|
||||
stock.playerAvgPx = newTotal / stock.playerShares;
|
||||
processBuyTransactionPriceMovement(stock, shares, PositionTypes.Long);
|
||||
processTransactionForecastMovement(stock, shares);
|
||||
if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") {
|
||||
opts.rerenderFn();
|
||||
}
|
||||
@ -138,7 +137,7 @@ export function sellStock(stock: Stock, shares: number, workerScript: WorkerScri
|
||||
stock.playerAvgPx = 0;
|
||||
}
|
||||
|
||||
processSellTransactionPriceMovement(stock, shares, PositionTypes.Long);
|
||||
processTransactionForecastMovement(stock, shares);
|
||||
|
||||
if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") {
|
||||
opts.rerenderFn();
|
||||
@ -211,7 +210,7 @@ export function shortStock(stock: Stock, shares: number, workerScript: WorkerScr
|
||||
const newTotal = origTotal + totalPrice - CONSTANTS.StockMarketCommission;
|
||||
stock.playerShortShares = Math.round(stock.playerShortShares + shares);
|
||||
stock.playerAvgShortPx = newTotal / stock.playerShortShares;
|
||||
processBuyTransactionPriceMovement(stock, shares, PositionTypes.Short);
|
||||
processTransactionForecastMovement(stock, shares);
|
||||
|
||||
if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") {
|
||||
opts.rerenderFn();
|
||||
@ -278,7 +277,7 @@ export function sellShort(stock: Stock, shares: number, workerScript: WorkerScri
|
||||
if (stock.playerShortShares === 0) {
|
||||
stock.playerAvgShortPx = 0;
|
||||
}
|
||||
processSellTransactionPriceMovement(stock, shares, PositionTypes.Short);
|
||||
processTransactionForecastMovement(stock, shares);
|
||||
|
||||
if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") {
|
||||
opts.rerenderFn();
|
||||
|
@ -5,6 +5,7 @@ export type IStockMarket = {
|
||||
[key: string]: Stock;
|
||||
} & {
|
||||
lastUpdate: number;
|
||||
storedCycles: number;
|
||||
Orders: IOrderBook;
|
||||
}
|
||||
storedCycles: number;
|
||||
ticksUntilCycle: number;
|
||||
};
|
||||
|
@ -22,7 +22,7 @@ import { numeralWrapper } from "../ui/numeralFormat";
|
||||
|
||||
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||
|
||||
interface IProcessOrderRefs {
|
||||
export interface IProcessOrderRefs {
|
||||
rerenderFn: () => void;
|
||||
stockMarket: IStockMarket;
|
||||
symbolToStockMap: IMap<Stock>;
|
||||
@ -49,7 +49,7 @@ export function processOrders(stock: Stock, orderType: OrderTypes, posType: Posi
|
||||
}
|
||||
let stockOrders = orderBook[stock.symbol];
|
||||
if (stockOrders == null || !(stockOrders.constructor === Array)) {
|
||||
console.error(`Invalid Order book for ${stock.symbol} in processOrders()`);
|
||||
console.error(`Invalid Order book for ${stock.symbol} in processOrders(): ${stockOrders}`);
|
||||
stockOrders = [];
|
||||
return;
|
||||
}
|
||||
@ -96,7 +96,7 @@ export function processOrders(stock: Stock, orderType: OrderTypes, posType: Posi
|
||||
/**
|
||||
* Execute a Stop or Limit Order.
|
||||
* @param {Order} order - Order being executed
|
||||
* @param {IStockMarket} stockMarket - Reference to StockMarket object
|
||||
* @param {IProcessOrderRefs} refs - References to objects/functions that are required for this function
|
||||
*/
|
||||
function executeOrder(order: Order, refs: IProcessOrderRefs) {
|
||||
const stock = refs.symbolToStockMap[order.stockSymbol];
|
||||
@ -107,8 +107,6 @@ function executeOrder(order: Order, refs: IProcessOrderRefs) {
|
||||
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
|
||||
@ -120,124 +118,37 @@ function executeOrder(order: Order, refs: IProcessOrderRefs) {
|
||||
let res = true;
|
||||
let isBuy = false;
|
||||
switch (order.type) {
|
||||
case OrderTypes.LimitBuy: {
|
||||
case OrderTypes.LimitBuy:
|
||||
case OrderTypes.StopBuy:
|
||||
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: {
|
||||
case OrderTypes.LimitSell:
|
||||
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;
|
||||
res = sellStock(stock, order.shares, 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;
|
||||
res = sellShort(stock, order.shares, 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`);
|
||||
}
|
||||
`(${numeralWrapper.formatBigNumber(Math.round(order.shares))} shares)`);
|
||||
refs.rerenderFn();
|
||||
return;
|
||||
}
|
||||
@ -249,9 +160,6 @@ function executeOrder(order: Order, refs: IProcessOrderRefs) {
|
||||
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.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
78
src/StockMarket/PlayerInfluencing.ts
Normal file
78
src/StockMarket/PlayerInfluencing.ts
Normal file
@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Implementation of the mechanisms that allow the player to affect the
|
||||
* Stock Market
|
||||
*/
|
||||
import { Stock } from "./Stock";
|
||||
import { StockMarket } from "./StockMarket";
|
||||
|
||||
import { Company } from "../Company/Company";
|
||||
import { Server } from "../Server/Server";
|
||||
|
||||
// Change in second-order forecast due to hacks/grows
|
||||
export const forecastForecastChangeFromHack = 0.1;
|
||||
|
||||
// Change in second-order forecast due to company work
|
||||
export const forecastForecastChangeFromCompanyWork = 0.001;
|
||||
|
||||
/**
|
||||
* Potentially decreases a stock's second-order forecast when its corresponding
|
||||
* server is hacked. The chance of the hack decreasing the stock's second-order
|
||||
* forecast is dependent on what percentage of the server's money is hacked
|
||||
* @param {Server} server - Server being hack()ed
|
||||
* @param {number} moneyHacked - Amount of money stolen from the server
|
||||
*/
|
||||
export function influenceStockThroughServerHack(server: Server, moneyHacked: number): void {
|
||||
const orgName = server.organizationName;
|
||||
let stock: Stock | null = null;
|
||||
if (typeof orgName === "string" && orgName !== "") {
|
||||
stock = StockMarket[orgName];
|
||||
}
|
||||
if (!(stock instanceof Stock)) { return; }
|
||||
|
||||
const percTotalMoneyHacked = moneyHacked / server.moneyMax;
|
||||
if (Math.random() < percTotalMoneyHacked) {
|
||||
stock.changeForecastForecast(stock.otlkMagForecast - forecastForecastChangeFromHack);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Potentially increases a stock's second-order forecast when its corresponding
|
||||
* server is grown (grow()). The chance of the grow() to increase the stock's
|
||||
* second-order forecast is dependent on how much money is added to the server
|
||||
* @param {Server} server - Server being grow()n
|
||||
* @param {number} moneyHacked - Amount of money added to the server
|
||||
*/
|
||||
export function influenceStockThroughServerGrow(server: Server, moneyGrown: number): void {
|
||||
const orgName = server.organizationName;
|
||||
let stock: Stock | null = null;
|
||||
if (typeof orgName === "string" && orgName !== "") {
|
||||
stock = StockMarket[orgName];
|
||||
}
|
||||
if (!(stock instanceof Stock)) { return; }
|
||||
|
||||
const percTotalMoneyGrown = moneyGrown / server.moneyMax;
|
||||
if (Math.random() < percTotalMoneyGrown) {
|
||||
stock.changeForecastForecast(stock.otlkMagForecast + forecastForecastChangeFromHack);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Potentially increases a stock's second-order forecast when the player works for
|
||||
* its corresponding company.
|
||||
* @param {Company} company - Company being worked for
|
||||
* @param {number} performanceMult - Effectiveness of player's work. Affects influence
|
||||
* @param {number} cyclesOfWork - # game cycles of work being processed
|
||||
*/
|
||||
export function influenceStockThroughCompanyWork(company: Company, performanceMult: number, cyclesOfWork: number): void {
|
||||
const compName = company.name;
|
||||
let stock: Stock | null = null;
|
||||
if (typeof compName === "string" && compName !== "") {
|
||||
stock = StockMarket[compName];
|
||||
}
|
||||
if (!(stock instanceof Stock)) { return; }
|
||||
|
||||
if (Math.random() < 0.002 * cyclesOfWork) {
|
||||
const change = forecastForecastChangeFromCompanyWork * performanceMult;
|
||||
stock.changeForecastForecast(stock.otlkMagForecast + change);
|
||||
}
|
||||
}
|
@ -6,6 +6,8 @@ import {
|
||||
} from "../../utils/JSONReviver";
|
||||
import { getRandomInt } from "../../utils/helpers/getRandomInt";
|
||||
|
||||
export const StockForecastInfluenceLimit = 5;
|
||||
|
||||
export interface IConstructorParams {
|
||||
b: boolean;
|
||||
initPrice: number | IMinMaxRange;
|
||||
@ -100,6 +102,12 @@ export class Stock {
|
||||
*/
|
||||
otlkMag: number;
|
||||
|
||||
/**
|
||||
* Forecast of outlook magnitude. Essentially a second-order forecast.
|
||||
* Unlike 'otlkMag', this number is on an absolute scale from 0-100 (rather than 0-50)
|
||||
*/
|
||||
otlkMagForecast: number;
|
||||
|
||||
/**
|
||||
* Average price of stocks that the player owns in the LONG position
|
||||
*/
|
||||
@ -125,12 +133,6 @@ export class Stock {
|
||||
*/
|
||||
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
|
||||
*/
|
||||
@ -140,8 +142,7 @@ export class Stock {
|
||||
* How many share transactions remaining until a price movement occurs
|
||||
* (separately tracked for upward and downward movements)
|
||||
*/
|
||||
shareTxUntilMovementDown: number;
|
||||
shareTxUntilMovementUp: number;
|
||||
shareTxUntilMovement: number;
|
||||
|
||||
/**
|
||||
* Spread percentage. The bid/ask prices for this stock are N% above or below
|
||||
@ -173,12 +174,11 @@ export class Stock {
|
||||
this.mv = toNumber(p.mv);
|
||||
this.b = p.b;
|
||||
this.otlkMag = p.otlkMag;
|
||||
this.otlkMagForecast = this.getAbsoluteForecast();
|
||||
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;
|
||||
this.shareTxUntilMovement = this.shareTxForMovement;
|
||||
|
||||
// Total shares is determined by market cap, and is rounded to nearest 100k
|
||||
let totalSharesUnrounded: number = (p.marketCap / this.price);
|
||||
@ -189,11 +189,86 @@ export class Stock {
|
||||
this.maxShares = Math.round((this.totalShares * outstandingSharePercentage) / 1e5) * 1e5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely set the stock's second-order forecast to a new value
|
||||
*/
|
||||
changeForecastForecast(newff: number): void {
|
||||
this.otlkMagForecast = newff;
|
||||
if (this.otlkMagForecast > 100) {
|
||||
this.otlkMagForecast = 100;
|
||||
} else if (this.otlkMagForecast < 0) {
|
||||
this.otlkMagForecast = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the stock to a new price. Also updates the stock's previous price tracker
|
||||
*/
|
||||
changePrice(newPrice: number): void {
|
||||
this.lastPrice = this.price;
|
||||
this.price = newPrice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the stock's forecast during a stock market 'tick'.
|
||||
* The way a stock's forecast changes depends on various internal properties,
|
||||
* but is ultimately determined by RNG
|
||||
*/
|
||||
cycleForecast(changeAmt: number=0.1): void {
|
||||
const increaseChance = this.getForecastIncreaseChance();
|
||||
|
||||
if (Math.random() < increaseChance) {
|
||||
// Forecast increases
|
||||
if (this.b) {
|
||||
this.otlkMag += changeAmt;
|
||||
} else {
|
||||
this.otlkMag -= changeAmt;
|
||||
}
|
||||
} else {
|
||||
// Forecast decreases
|
||||
if (this.b) {
|
||||
this.otlkMag -= changeAmt;
|
||||
} else {
|
||||
this.otlkMag += changeAmt;
|
||||
}
|
||||
}
|
||||
|
||||
this.otlkMag = Math.min(this.otlkMag, 50);
|
||||
if (this.otlkMag < 0) {
|
||||
this.otlkMag *= -1;
|
||||
this.b = !this.b;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change's the stock's second-order forecast during a stock market 'tick'.
|
||||
* The change for the second-order forecast to increase is 50/50
|
||||
*/
|
||||
cycleForecastForecast(changeAmt: number=0.1): void {
|
||||
if (Math.random() < 0.5) {
|
||||
this.changeForecastForecast(this.otlkMagForecast + changeAmt);
|
||||
} else {
|
||||
this.changeForecastForecast(this.otlkMagForecast - changeAmt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* "Flip" the stock's second-order forecast. This can occur during a
|
||||
* stock market "cycle" (determined by RNG). It is used to simulate
|
||||
* RL stock market cycles and introduce volatility
|
||||
*/
|
||||
flipForecastForecast(): void {
|
||||
const diff = this.otlkMagForecast - 50;
|
||||
this.otlkMagForecast = 50 + (-1 * diff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stock's absolute forecast, which is a number between 0-100
|
||||
*/
|
||||
getAbsoluteForecast(): number {
|
||||
return this.b ? 50 + this.otlkMag : 50 - this.otlkMag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the price at which YOUR stock is bought (market ask price). Accounts for spread
|
||||
*/
|
||||
@ -208,6 +283,41 @@ export class Stock {
|
||||
return this.price * (1 - (this.spreadPerc / 100));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the chance (0-1 decimal) that a stock has of having its forecast increase
|
||||
*/
|
||||
getForecastIncreaseChance(): number {
|
||||
const diff = this.otlkMagForecast - this.getAbsoluteForecast();
|
||||
|
||||
return (50 + Math.min(Math.max(diff, -45), 45)) / 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes a stock's forecast. This is used when the stock is influenced
|
||||
* by a transaction. The stock's forecast always goes towards 50, but the
|
||||
* movement is capped by a certain threshold/limit
|
||||
*/
|
||||
influenceForecast(change: number): void {
|
||||
if (this.otlkMag > StockForecastInfluenceLimit) {
|
||||
this.otlkMag = Math.max(StockForecastInfluenceLimit, this.otlkMag - change);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes a stock's second-order forecast. This is used when the stock is
|
||||
* influenced by a transaction. The stock's second-order forecast always
|
||||
* goes towards 50.
|
||||
*/
|
||||
influenceForecastForecast(change: number): void {
|
||||
if (this.otlkMagForecast > 50) {
|
||||
this.otlkMagForecast -= change;
|
||||
this.otlkMagForecast = Math.max(50, this.otlkMagForecast);
|
||||
} else if (this.otlkMagForecast < 50) {
|
||||
this.otlkMagForecast += change;
|
||||
this.otlkMagForecast = Math.min(50, this.otlkMagForecast);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the Stock to a JSON save state.
|
||||
*/
|
||||
|
@ -4,19 +4,12 @@ import {
|
||||
shortStock,
|
||||
sellShort,
|
||||
} from "./BuyingAndSelling";
|
||||
import { IOrderBook } from "./IOrderBook";
|
||||
import { IStockMarket } from "./IStockMarket";
|
||||
import { Order } from "./Order";
|
||||
import { processOrders } from "./OrderProcessing";
|
||||
import { Stock } from "./Stock";
|
||||
import {
|
||||
getBuyTransactionCost,
|
||||
getSellTransactionGain,
|
||||
processBuyTransactionPriceMovement,
|
||||
processSellTransactionPriceMovement
|
||||
} from "./StockMarketHelpers";
|
||||
import {
|
||||
getStockMarket4SDataCost,
|
||||
getStockMarket4STixApiCost
|
||||
} from "./StockMarketCosts";
|
||||
import { TicksPerCycle } from "./StockMarketConstants";
|
||||
import { InitStockMetadata } from "./data/InitStockMetadata";
|
||||
import { OrderTypes } from "./data/OrderTypes";
|
||||
import { PositionTypes } from "./data/PositionTypes";
|
||||
@ -26,6 +19,7 @@ import { StockMarketRoot } from "./ui/Root";
|
||||
import { CONSTANTS } from "../Constants";
|
||||
import { WorkerScript } from "../Netscript/WorkerScript";
|
||||
import { Player } from "../Player";
|
||||
import { IMap } from "../types";
|
||||
|
||||
import { Page, routing } from ".././ui/navigationTracking";
|
||||
import { numeralWrapper } from ".././ui/numeralFormat";
|
||||
@ -33,17 +27,17 @@ import { numeralWrapper } from ".././ui/numeralFormat";
|
||||
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||
import { Reviver } from "../../utils/JSONReviver";
|
||||
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
|
||||
export let StockMarket = {}; // Maps full stock name -> Stock object
|
||||
export let SymbolToStockMap = {}; // Maps symbol -> Stock object
|
||||
export let StockMarket: IStockMarket | IMap<any> = {}; // Maps full stock name -> Stock object
|
||||
export let SymbolToStockMap: IMap<Stock> = {}; // Maps symbol -> Stock object
|
||||
|
||||
export function placeOrder(stock, shares, price, type, position, workerScript=null) {
|
||||
export function placeOrder(stock: Stock, shares: number, price: number, type: OrderTypes, position: PositionTypes, workerScript: WorkerScript | null=null): boolean {
|
||||
const tixApi = (workerScript instanceof WorkerScript);
|
||||
if (!(stock instanceof Stock)) {
|
||||
if (tixApi) {
|
||||
workerScript.log(`ERROR: Invalid stock passed to placeOrder() function`);
|
||||
workerScript!.log(`ERROR: Invalid stock passed to placeOrder() function`);
|
||||
} else {
|
||||
dialogBoxCreate(`ERROR: Invalid stock passed to placeOrder() function`);
|
||||
}
|
||||
@ -51,7 +45,7 @@ export function placeOrder(stock, shares, price, type, position, workerScript=nu
|
||||
}
|
||||
if (typeof shares !== "number" || typeof price !== "number") {
|
||||
if (tixApi) {
|
||||
workerScript.log("ERROR: Invalid numeric value provided for either 'shares' or 'price' argument");
|
||||
workerScript!.log("ERROR: Invalid numeric value provided for either 'shares' or 'price' argument");
|
||||
} else {
|
||||
dialogBoxCreate("ERROR: Invalid numeric value provided for either 'shares' or 'price' argument");
|
||||
}
|
||||
@ -60,7 +54,7 @@ export function placeOrder(stock, shares, price, type, position, workerScript=nu
|
||||
|
||||
const order = new Order(stock.symbol, shares, price, type, position);
|
||||
if (StockMarket["Orders"] == null) {
|
||||
const orders = {};
|
||||
const orders: IOrderBook = {};
|
||||
for (const name in StockMarket) {
|
||||
const stk = StockMarket[name];
|
||||
if (!(stk instanceof Stock)) { continue; }
|
||||
@ -73,7 +67,7 @@ export function placeOrder(stock, shares, price, type, position, workerScript=nu
|
||||
// Process to see if it should be executed immediately
|
||||
const processOrderRefs = {
|
||||
rerenderFn: displayStockMarketContent,
|
||||
stockMarket: StockMarket,
|
||||
stockMarket: StockMarket as IStockMarket,
|
||||
symbolToStockMap: SymbolToStockMap,
|
||||
}
|
||||
processOrders(stock, order.type, order.pos, processOrderRefs);
|
||||
@ -83,7 +77,15 @@ export function placeOrder(stock, shares, price, type, position, workerScript=nu
|
||||
}
|
||||
|
||||
// Returns true if successfully cancels an order, false otherwise
|
||||
export function cancelOrder(params, workerScript=null) {
|
||||
interface ICancelOrderParams {
|
||||
order?: Order;
|
||||
pos?: PositionTypes;
|
||||
price?: number;
|
||||
shares?: number;
|
||||
stock?: Stock;
|
||||
type?: OrderTypes;
|
||||
}
|
||||
export function cancelOrder(params: ICancelOrderParams, workerScript: WorkerScript | null=null): boolean {
|
||||
var tixApi = (workerScript instanceof WorkerScript);
|
||||
if (StockMarket["Orders"] == null) {return false;}
|
||||
if (params.order && params.order instanceof Order) {
|
||||
@ -113,42 +115,24 @@ export function cancelOrder(params, workerScript=null) {
|
||||
stockOrders.splice(i, 1);
|
||||
displayStockMarketContent();
|
||||
if (tixApi) {
|
||||
workerScript.scriptRef.log("Successfully cancelled order: " + orderTxt);
|
||||
workerScript!.scriptRef.log("Successfully cancelled order: " + orderTxt);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (tixApi) {
|
||||
workerScript.scriptRef.log("Failed to cancel order: " + orderTxt);
|
||||
workerScript!.scriptRef.log("Failed to cancel order: " + orderTxt);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function loadStockMarket(saveString) {
|
||||
export function loadStockMarket(saveString: string) {
|
||||
if (saveString === "") {
|
||||
StockMarket = {};
|
||||
} else {
|
||||
StockMarket = JSON.parse(saveString, Reviver);
|
||||
|
||||
// Backwards compatibility for v0.47.0
|
||||
const orderBook = StockMarket["Orders"];
|
||||
if (orderBook != null) {
|
||||
// For each order, set its 'stockSymbol' property equal to the
|
||||
// symbol of its 'stock' property
|
||||
for (const stockSymbol in orderBook) {
|
||||
const ordersForStock = orderBook[stockSymbol];
|
||||
if (Array.isArray(ordersForStock)) {
|
||||
for (const order of ordersForStock) {
|
||||
if (order instanceof Order && order.stock instanceof Stock) {
|
||||
order.stockSymbol = order.stock.symbol;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(`Converted Stock Market order book to v0.47.0 format`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,7 +152,7 @@ export function initStockMarket() {
|
||||
StockMarket[name] = new Stock(metadata);
|
||||
}
|
||||
|
||||
const orders = {};
|
||||
const orders: IOrderBook = {};
|
||||
for (const name in StockMarket) {
|
||||
const stock = StockMarket[name];
|
||||
if (!(stock instanceof Stock)) { continue; }
|
||||
@ -178,6 +162,7 @@ export function initStockMarket() {
|
||||
|
||||
StockMarket.storedCycles = 0;
|
||||
StockMarket.lastUpdate = 0;
|
||||
StockMarket.ticksUntilCycle = TicksPerCycle;
|
||||
}
|
||||
|
||||
export function initSymbolToStockMap() {
|
||||
@ -198,12 +183,14 @@ export function stockMarketCycle() {
|
||||
for (const name in StockMarket) {
|
||||
const stock = StockMarket[name];
|
||||
if (!(stock instanceof Stock)) { continue; }
|
||||
let thresh = 0.6;
|
||||
if (stock.b) { thresh = 0.4; }
|
||||
if (Math.random() < thresh) {
|
||||
|
||||
const roll = Math.random();
|
||||
if (roll < 0.45) {
|
||||
stock.b = !stock.b;
|
||||
if (stock.otlkMag < 5) { stock.otlkMag += 0.1; }
|
||||
stock.flipForecastForecast();
|
||||
}
|
||||
|
||||
StockMarket.ticksUntilCycle = TicksPerCycle;
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,6 +211,15 @@ export function processStockPrices(numCycles=1) {
|
||||
StockMarket.lastUpdate = timeNow;
|
||||
StockMarket.storedCycles -= cyclesPerStockUpdate;
|
||||
|
||||
// Cycle
|
||||
if (StockMarket.ticksUntilCycle == null || typeof StockMarket.ticksUntilCycle !== "number") {
|
||||
StockMarket.ticksUntilCycle = TicksPerCycle;
|
||||
}
|
||||
--StockMarket.ticksUntilCycle;
|
||||
if (StockMarket.ticksUntilCycle <= 0) {
|
||||
stockMarketCycle();
|
||||
}
|
||||
|
||||
var v = Math.random();
|
||||
for (const name in StockMarket) {
|
||||
const stock = StockMarket[name];
|
||||
@ -246,7 +242,7 @@ export function processStockPrices(numCycles=1) {
|
||||
const c = Math.random();
|
||||
const processOrderRefs = {
|
||||
rerenderFn: displayStockMarketContent,
|
||||
stockMarket: StockMarket,
|
||||
stockMarket: StockMarket as IStockMarket,
|
||||
symbolToStockMap: SymbolToStockMap,
|
||||
}
|
||||
if (c < chc) {
|
||||
@ -264,29 +260,24 @@ export function processStockPrices(numCycles=1) {
|
||||
}
|
||||
|
||||
let otlkMagChange = stock.otlkMag * av;
|
||||
if (stock.otlkMag <= 0.1) {
|
||||
if (stock.otlkMag < 5) {
|
||||
if (stock.otlkMag <= 1) {
|
||||
otlkMagChange = 1;
|
||||
}
|
||||
if (c < 0.5) {
|
||||
stock.otlkMag += otlkMagChange;
|
||||
} else {
|
||||
stock.otlkMag -= otlkMagChange;
|
||||
otlkMagChange *= 10;
|
||||
}
|
||||
if (stock.otlkMag > 50) { stock.otlkMag = 50; } // Cap so the "forecast" is between 0 and 100
|
||||
if (stock.otlkMag < 0) {
|
||||
stock.otlkMag *= -1;
|
||||
stock.b = !stock.b;
|
||||
}
|
||||
stock.cycleForecast(otlkMagChange);
|
||||
stock.cycleForecastForecast(otlkMagChange / 2);
|
||||
|
||||
// Shares required for price movement gradually approaches max over time
|
||||
stock.shareTxUntilMovement = Math.min(stock.shareTxUntilMovementUp + 5, stock.shareTxForMovement);
|
||||
stock.shareTxUntilMovement = Math.min(stock.shareTxUntilMovementDown + 5, stock.shareTxForMovement);
|
||||
stock.shareTxUntilMovement = Math.min(stock.shareTxUntilMovement + 10, stock.shareTxForMovement);
|
||||
}
|
||||
|
||||
displayStockMarketContent();
|
||||
}
|
||||
|
||||
let stockMarketContainer = null;
|
||||
let stockMarketContainer: HTMLElement | null = null;
|
||||
function setStockMarketContainer() {
|
||||
stockMarketContainer = document.getElementById("stock-market-container");
|
||||
document.removeEventListener("DOMContentLoaded", setStockMarketContainer);
|
||||
@ -305,6 +296,7 @@ export function displayStockMarketContent() {
|
||||
}
|
||||
|
||||
if (stockMarketContainer instanceof HTMLElement) {
|
||||
const castedStockMarket = StockMarket as IStockMarket;
|
||||
ReactDOM.render(
|
||||
<StockMarketRoot
|
||||
buyStockLong={buyStock}
|
||||
@ -315,7 +307,7 @@ export function displayStockMarketContent() {
|
||||
placeOrder={placeOrder}
|
||||
sellStockLong={sellStock}
|
||||
sellStockShort={sellShort}
|
||||
stockMarket={StockMarket}
|
||||
stockMarket={castedStockMarket}
|
||||
/>,
|
||||
stockMarketContainer
|
||||
)
|
5
src/StockMarket/StockMarketConstants.ts
Normal file
5
src/StockMarket/StockMarketConstants.ts
Normal file
@ -0,0 +1,5 @@
|
||||
/**
|
||||
* How many stock market 'ticks' before a 'cycle' is triggered.
|
||||
* A 'tick' is whenver stock prices update
|
||||
*/
|
||||
export const TicksPerCycle = 75;
|
@ -1,39 +1,15 @@
|
||||
/**
|
||||
* Stock Market Helper Functions
|
||||
*/
|
||||
import { Stock } from "./Stock";
|
||||
import { PositionTypes } from "./data/PositionTypes";
|
||||
import { CONSTANTS } from "../Constants";
|
||||
|
||||
// Amount by which a stock's forecast changes during each price movement
|
||||
export const forecastChangePerPriceMovement = 0.005;
|
||||
export const forecastChangePerPriceMovement = 0.006;
|
||||
|
||||
/**
|
||||
* Given a stock, calculates the amount by which the stock price is multiplied
|
||||
* for an 'upward' price movement. This does not actually increase the stock's price,
|
||||
* just calculates the multiplier
|
||||
* @param {Stock} stock - Stock for price movement
|
||||
* @returns {number | null} Number by which stock's price should be multiplied. Null for invalid args
|
||||
*/
|
||||
export function calculateIncreasingPriceMovement(stock: Stock): number | null {
|
||||
if (!(stock instanceof Stock)) { return null; }
|
||||
|
||||
return (1 + (stock.priceMovementPerc / 100));
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a stock, calculates the amount by which the stock price is multiplied
|
||||
* for a "downward" price movement. This does not actually increase the stock's price,
|
||||
* just calculates the multiplier
|
||||
* @param {Stock} stock - Stock for price movement
|
||||
* @returns {number | null} Number by which stock's price should be multiplied. Null for invalid args
|
||||
*/
|
||||
export function calculateDecreasingPriceMovement(stock: Stock): number | null {
|
||||
if (!(stock instanceof Stock)) { return null; }
|
||||
|
||||
return (1 - (stock.priceMovementPerc / 100));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the total cost of a "buy" transaction. This accounts for spread,
|
||||
* price movements, and commission.
|
||||
* Calculate the total cost of a "buy" transaction. This accounts for spread and commission.
|
||||
* @param {Stock} stock - Stock being purchased
|
||||
* @param {number} shares - Number of shares being transacted
|
||||
* @param {PositionTypes} posType - Long or short position
|
||||
@ -50,135 +26,15 @@ export function getBuyTransactionCost(stock: Stock, shares: number, posType: Pos
|
||||
|
||||
// If the number of shares doesn't trigger a price movement, its a simple calculation
|
||||
if (isLong) {
|
||||
if (shares <= stock.shareTxUntilMovementUp) {
|
||||
return (shares * stock.getAskPrice()) + CONSTANTS.StockMarketCommission;
|
||||
}
|
||||
} else {
|
||||
if (shares <= stock.shareTxUntilMovementDown) {
|
||||
return (shares * stock.getBidPrice()) + CONSTANTS.StockMarketCommission;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate how many iterations of price changes we need to account for
|
||||
const firstShares = isLong ? stock.shareTxUntilMovementUp : stock.shareTxUntilMovementDown;
|
||||
let remainingShares = shares - firstShares;
|
||||
let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement);
|
||||
|
||||
// The initial cost calculation takes care of the first "iteration"
|
||||
let currPrice = isLong ? stock.getAskPrice() : stock.getBidPrice();
|
||||
let totalCost = (firstShares * currPrice);
|
||||
|
||||
const increasingMvmt = calculateIncreasingPriceMovement(stock)!;
|
||||
const decreasingMvmt = calculateDecreasingPriceMovement(stock)!;
|
||||
|
||||
function processPriceMovement() {
|
||||
if (isLong) {
|
||||
currPrice *= increasingMvmt;
|
||||
} else {
|
||||
currPrice *= decreasingMvmt;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 1; i < numIterations; ++i) {
|
||||
processPriceMovement();
|
||||
|
||||
const amt = Math.min(stock.shareTxForMovement, remainingShares);
|
||||
totalCost += (amt * currPrice);
|
||||
remainingShares -= amt;
|
||||
}
|
||||
|
||||
return totalCost + CONSTANTS.StockMarketCommission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a buy transaction's resulting price AND forecast movement.
|
||||
* @param {Stock} stock - Stock being purchased
|
||||
* @param {number} shares - Number of shares being transacted
|
||||
* @param {PositionTypes} posType - Long or short position
|
||||
*/
|
||||
export function processBuyTransactionPriceMovement(stock: Stock, shares: number, posType: PositionTypes): void {
|
||||
if (isNaN(shares) || shares <= 0 || !(stock instanceof Stock)) { return; }
|
||||
|
||||
// Cap the 'shares' arg at the stock's maximum shares. This'll prevent
|
||||
// hanging in the case when a really big number is passed in
|
||||
shares = Math.min(shares, stock.maxShares);
|
||||
|
||||
const isLong = (posType === PositionTypes.Long);
|
||||
|
||||
let currPrice = stock.price;
|
||||
function processPriceMovement() {
|
||||
if (isLong) {
|
||||
currPrice *= calculateIncreasingPriceMovement(stock)!;
|
||||
} else {
|
||||
currPrice *= calculateDecreasingPriceMovement(stock)!;
|
||||
}
|
||||
}
|
||||
|
||||
// If there's only going to be one iteration
|
||||
const firstShares = isLong ? stock.shareTxUntilMovementUp : stock.shareTxUntilMovementDown;
|
||||
if (shares <= firstShares) {
|
||||
function triggerMovement() {
|
||||
processPriceMovement();
|
||||
stock.changePrice(currPrice);
|
||||
stock.otlkMag -= (forecastChangePerPriceMovement);
|
||||
}
|
||||
|
||||
if (isLong) {
|
||||
stock.shareTxUntilMovementUp -= shares;
|
||||
if (stock.shareTxUntilMovementUp <= 0) {
|
||||
stock.shareTxUntilMovementUp = stock.shareTxForMovement;
|
||||
triggerMovement();
|
||||
}
|
||||
} else {
|
||||
stock.shareTxUntilMovementDown -= shares;
|
||||
if (stock.shareTxUntilMovementDown <= 0) {
|
||||
stock.shareTxUntilMovementDown = stock.shareTxForMovement;
|
||||
triggerMovement();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate how many iterations of price changes we need to account for
|
||||
let remainingShares = shares - firstShares;
|
||||
let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement);
|
||||
|
||||
for (let i = 1; i < numIterations; ++i) {
|
||||
processPriceMovement();
|
||||
}
|
||||
|
||||
// If on the offchance we end up perfectly at the next price movement
|
||||
if (isLong) {
|
||||
stock.shareTxUntilMovementUp = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovementUp) % stock.shareTxForMovement);
|
||||
if (stock.shareTxUntilMovementUp === stock.shareTxForMovement || stock.shareTxUntilMovementUp <= 0) {
|
||||
// The shareTxUntilMovementUp ended up at 0 at the end of the "processing"
|
||||
++numIterations;
|
||||
stock.shareTxUntilMovementUp = stock.shareTxForMovement;
|
||||
processPriceMovement();
|
||||
}
|
||||
} else {
|
||||
stock.shareTxUntilMovementDown = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovementDown) % stock.shareTxForMovement);
|
||||
if (stock.shareTxUntilMovementDown === stock.shareTxForMovement || stock.shareTxUntilMovementDown <= 0) {
|
||||
// The shareTxUntilMovementDown ended up at 0 at the end of the "processing"
|
||||
++numIterations;
|
||||
stock.shareTxUntilMovementDown = stock.shareTxForMovement;
|
||||
processPriceMovement();
|
||||
}
|
||||
}
|
||||
|
||||
stock.changePrice(currPrice);
|
||||
|
||||
// Forecast always decreases in magnitude
|
||||
const forecastChange = Math.min(5, forecastChangePerPriceMovement * (numIterations - 1));
|
||||
if (stock.otlkMag > 10) {
|
||||
stock.otlkMag -= forecastChange;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the TOTAL amount of money gained from a sale (NOT net profit). This accounts
|
||||
* for spread, price movements, and commission.
|
||||
* for spread and commission.
|
||||
* @param {Stock} stock - Stock being sold
|
||||
* @param {number} shares - Number of sharse being transacted
|
||||
* @param {PositionTypes} posType - Long or short position
|
||||
@ -192,10 +48,6 @@ export function getSellTransactionGain(stock: Stock, shares: number, posType: Po
|
||||
shares = Math.min(shares, stock.maxShares);
|
||||
|
||||
const isLong = (posType === PositionTypes.Long);
|
||||
const firstShares = isLong ? stock.shareTxUntilMovementDown : stock.shareTxUntilMovementUp;
|
||||
|
||||
// If the number of shares doesn't trigger a price mvoement, its a simple calculation
|
||||
if (shares <= firstShares) {
|
||||
if (isLong) {
|
||||
return (shares * stock.getBidPrice()) - CONSTANTS.StockMarketCommission;
|
||||
} else {
|
||||
@ -205,91 +57,32 @@ export function getSellTransactionGain(stock: Stock, shares: number, posType: Po
|
||||
|
||||
return origCost + profit;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate how many iterations of price changes we need to account for
|
||||
let remainingShares = shares - firstShares;
|
||||
let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement);
|
||||
|
||||
// Helper function to calculate gain for a single iteration
|
||||
function calculateGain(thisPrice: number, thisShares: number) {
|
||||
if (isLong) {
|
||||
return thisShares * thisPrice;
|
||||
} else {
|
||||
const origCost = thisShares * stock.playerAvgShortPx;
|
||||
const profit = ((stock.playerAvgShortPx - thisPrice) * thisShares);
|
||||
|
||||
return origCost + profit;
|
||||
}
|
||||
}
|
||||
|
||||
// The initial cost calculation takes care of the first "iteration"
|
||||
let currPrice = isLong ? stock.getBidPrice() : stock.getAskPrice();
|
||||
let totalGain = calculateGain(currPrice, firstShares);
|
||||
for (let i = 1; i < numIterations; ++i) {
|
||||
// Price movement
|
||||
if (isLong) {
|
||||
currPrice *= calculateDecreasingPriceMovement(stock)!;
|
||||
} else {
|
||||
currPrice *= calculateIncreasingPriceMovement(stock)!;
|
||||
}
|
||||
|
||||
const amt = Math.min(stock.shareTxForMovement, remainingShares);
|
||||
totalGain += calculateGain(currPrice, amt);
|
||||
remainingShares -= amt;
|
||||
}
|
||||
|
||||
return totalGain - CONSTANTS.StockMarketCommission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a sell transaction's resulting price movement
|
||||
* Processes a stock's change in forecast & second-order forecast
|
||||
* whenever it is transacted
|
||||
* @param {Stock} stock - Stock being sold
|
||||
* @param {number} shares - Number of sharse being transacted
|
||||
* @param {PositionTypes} posType - Long or short position
|
||||
*/
|
||||
export function processSellTransactionPriceMovement(stock: Stock, shares: number, posType: PositionTypes): void {
|
||||
export function processTransactionForecastMovement(stock: Stock, shares: number): void {
|
||||
if (isNaN(shares) || shares <= 0 || !(stock instanceof Stock)) { return; }
|
||||
|
||||
// Cap the 'shares' arg at the stock's maximum shares. This'll prevent
|
||||
// hanging in the case when a really big number is passed in
|
||||
shares = Math.min(shares, stock.maxShares);
|
||||
|
||||
const isLong = (posType === PositionTypes.Long);
|
||||
const firstShares = isLong ? stock.shareTxUntilMovementDown : stock.shareTxUntilMovementUp;
|
||||
|
||||
let currPrice = stock.price;
|
||||
function processPriceMovement() {
|
||||
if (isLong) {
|
||||
currPrice *= calculateDecreasingPriceMovement(stock)!;
|
||||
} else {
|
||||
currPrice *= calculateIncreasingPriceMovement(stock)!;
|
||||
}
|
||||
}
|
||||
|
||||
// If there's only going to be one iteration at most
|
||||
const firstShares = stock.shareTxUntilMovement;
|
||||
if (shares <= firstShares) {
|
||||
function triggerPriceMovement() {
|
||||
processPriceMovement();
|
||||
stock.changePrice(currPrice);
|
||||
stock.otlkMag -= (forecastChangePerPriceMovement);
|
||||
stock.shareTxUntilMovement -= shares;
|
||||
if (stock.shareTxUntilMovement <= 0) {
|
||||
stock.shareTxUntilMovement = stock.shareTxForMovement;
|
||||
stock.influenceForecast(forecastChangePerPriceMovement);
|
||||
stock.influenceForecastForecast(forecastChangePerPriceMovement * (stock.mv / 100));
|
||||
}
|
||||
|
||||
if (isLong) {
|
||||
stock.shareTxUntilMovementDown -= shares;
|
||||
if (stock.shareTxUntilMovementDown <= 0) {
|
||||
stock.shareTxUntilMovementDown = stock.shareTxForMovement;
|
||||
triggerPriceMovement();
|
||||
}
|
||||
} else {
|
||||
stock.shareTxUntilMovementUp -= shares;
|
||||
if (stock.shareTxUntilMovementUp <= 0) {
|
||||
stock.shareTxUntilMovementUp = stock.shareTxForMovement;
|
||||
triggerPriceMovement();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -297,34 +90,18 @@ export function processSellTransactionPriceMovement(stock: Stock, shares: number
|
||||
let remainingShares = shares - firstShares;
|
||||
let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement);
|
||||
|
||||
for (let i = 1; i < numIterations; ++i) {
|
||||
processPriceMovement();
|
||||
}
|
||||
|
||||
// If on the offchance we end up perfectly at the next price movement
|
||||
if (isLong) {
|
||||
stock.shareTxUntilMovementDown = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovementDown) % stock.shareTxForMovement);
|
||||
if (stock.shareTxUntilMovementDown === stock.shareTxForMovement || stock.shareTxUntilMovementDown <= 0) {
|
||||
stock.shareTxUntilMovement = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovement) % stock.shareTxForMovement);
|
||||
if (stock.shareTxUntilMovement === stock.shareTxForMovement || stock.shareTxUntilMovement <= 0) {
|
||||
++numIterations;
|
||||
stock.shareTxUntilMovementDown = stock.shareTxForMovement;
|
||||
processPriceMovement();
|
||||
stock.shareTxUntilMovement = stock.shareTxForMovement;
|
||||
}
|
||||
} else {
|
||||
stock.shareTxUntilMovementUp = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovementUp) % stock.shareTxForMovement);
|
||||
if (stock.shareTxUntilMovementUp === stock.shareTxForMovement || stock.shareTxUntilMovementUp <= 0) {
|
||||
++numIterations;
|
||||
stock.shareTxUntilMovementUp = stock.shareTxForMovement;
|
||||
processPriceMovement();
|
||||
}
|
||||
}
|
||||
|
||||
stock.changePrice(currPrice);
|
||||
|
||||
// Forecast always decreases in magnitude
|
||||
const forecastChange = Math.min(5, forecastChangePerPriceMovement * (numIterations - 1));
|
||||
if (stock.otlkMag > 10) {
|
||||
stock.otlkMag -= forecastChange;
|
||||
}
|
||||
const forecastChange = forecastChangePerPriceMovement * (numIterations - 1);
|
||||
const forecastForecastChange = forecastChange * (stock.mv / 100);
|
||||
stock.influenceForecast(forecastChange);
|
||||
stock.influenceForecastForecast(forecastForecastChange);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -341,44 +118,8 @@ export function calculateBuyMaxAmount(stock: Stock, posType: PositionTypes, mone
|
||||
|
||||
const isLong = (posType === PositionTypes.Long);
|
||||
|
||||
const increasingMvmt = calculateIncreasingPriceMovement(stock);
|
||||
const decreasingMvmt = calculateDecreasingPriceMovement(stock);
|
||||
if (increasingMvmt == null || decreasingMvmt == null) { return 0; }
|
||||
|
||||
let remainingMoney = money - CONSTANTS.StockMarketCommission;
|
||||
let currPrice = isLong ? stock.getAskPrice() : stock.getBidPrice();
|
||||
|
||||
// No price movement
|
||||
const firstShares = isLong ? stock.shareTxUntilMovementUp : stock.shareTxUntilMovementDown;
|
||||
const firstIterationCost = firstShares * currPrice;
|
||||
if (remainingMoney < firstIterationCost) {
|
||||
return Math.floor(remainingMoney / currPrice);
|
||||
}
|
||||
|
||||
// We'll avoid any accidental infinite loops by having a hardcoded maximum number of
|
||||
// iterations
|
||||
let numShares = firstShares;
|
||||
remainingMoney -= firstIterationCost;
|
||||
for (let i = 0; i < 10e3; ++i) {
|
||||
if (isLong) {
|
||||
currPrice *= increasingMvmt;
|
||||
} else {
|
||||
currPrice *= decreasingMvmt;
|
||||
}
|
||||
|
||||
const affordableShares = Math.floor(remainingMoney / currPrice);
|
||||
const actualShares = Math.min(stock.shareTxForMovement, affordableShares);
|
||||
|
||||
// Can't afford any more, so we're done
|
||||
if (actualShares <= 0) { break; }
|
||||
|
||||
numShares += actualShares;
|
||||
|
||||
let cost = actualShares * currPrice;
|
||||
remainingMoney -= cost;
|
||||
|
||||
if (remainingMoney <= 0) { break; }
|
||||
}
|
||||
|
||||
return Math.floor(numShares);
|
||||
}
|
||||
|
@ -672,8 +672,8 @@ export const InitStockMetadata: IConstructorParams[] = [
|
||||
marketCap: 58e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 430,
|
||||
min: 400,
|
||||
max: 400,
|
||||
min: 200,
|
||||
},
|
||||
name: LocationName.AevumNetLinkTechnologies,
|
||||
otlkMag: 1,
|
||||
@ -750,8 +750,8 @@ export const InitStockMetadata: IConstructorParams[] = [
|
||||
marketCap: 30e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 300,
|
||||
min: 260,
|
||||
max: 275,
|
||||
min: 100,
|
||||
},
|
||||
name: "Sigma Cosmetics",
|
||||
otlkMag: 0,
|
||||
@ -761,8 +761,8 @@ export const InitStockMetadata: IConstructorParams[] = [
|
||||
min: 6,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 84e3,
|
||||
min: 24e3,
|
||||
max: 70e3,
|
||||
min: 20e3,
|
||||
},
|
||||
symbol: StockSymbols["Sigma Cosmetics"],
|
||||
},
|
||||
@ -776,8 +776,8 @@ export const InitStockMetadata: IConstructorParams[] = [
|
||||
marketCap: 42e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 400,
|
||||
min: 360,
|
||||
max: 350,
|
||||
min: 200,
|
||||
},
|
||||
name: "Joes Guns",
|
||||
otlkMag: 1,
|
||||
@ -787,8 +787,8 @@ export const InitStockMetadata: IConstructorParams[] = [
|
||||
min: 6,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 64e3,
|
||||
min: 18e3,
|
||||
max: 52e3,
|
||||
min: 15e3,
|
||||
},
|
||||
symbol: StockSymbols["Joes Guns"],
|
||||
},
|
||||
|
@ -117,12 +117,6 @@ export class StockTicker extends React.Component<IProps, IState> {
|
||||
let costTxt = `Purchasing ${numeralWrapper.formatBigNumber(qty)} shares (${this.state.position === PositionTypes.Long ? "Long" : "Short"}) ` +
|
||||
`will cost ${numeralWrapper.formatMoney(cost)}. `;
|
||||
|
||||
const amtNeededForMovement = this.state.position === PositionTypes.Long ? stock.shareTxUntilMovementUp : stock.shareTxUntilMovementDown;
|
||||
const causesMovement = qty > amtNeededForMovement;
|
||||
if (causesMovement) {
|
||||
costTxt += `WARNING: Purchasing this many shares will influence the stock price`;
|
||||
}
|
||||
|
||||
return costTxt;
|
||||
}
|
||||
|
||||
@ -151,12 +145,6 @@ export class StockTicker extends React.Component<IProps, IState> {
|
||||
let costTxt = `Selling ${numeralWrapper.formatBigNumber(qty)} shares (${this.state.position === PositionTypes.Long ? "Long" : "Short"}) ` +
|
||||
`will result in a gain of ${numeralWrapper.formatMoney(cost)}. `;
|
||||
|
||||
const amtNeededForMovement = this.state.position === PositionTypes.Long ? stock.shareTxUntilMovementDown : stock.shareTxUntilMovementUp;
|
||||
const causesMovement = qty > amtNeededForMovement;
|
||||
if (causesMovement) {
|
||||
costTxt += `WARNING: Selling this many shares will influence the stock price`;
|
||||
}
|
||||
|
||||
return costTxt;
|
||||
}
|
||||
|
||||
@ -357,11 +345,7 @@ export class StockTicker extends React.Component<IProps, IState> {
|
||||
|
||||
render() {
|
||||
// Determine if the player's intended transaction will cause a price movement
|
||||
let causesMovement: boolean = false;
|
||||
const qty = this.getQuantity();
|
||||
if (!isNaN(qty)) {
|
||||
causesMovement = qty > this.props.stock.shareTxForMovement;
|
||||
}
|
||||
|
||||
return (
|
||||
<li>
|
||||
@ -400,14 +384,6 @@ export class StockTicker extends React.Component<IProps, IState> {
|
||||
<StockTickerTxButton onClick={this.handleSellButtonClick} text={"Sell"} tooltip={this.getSellTransactionCostText()} />
|
||||
<StockTickerTxButton onClick={this.handleBuyMaxButtonClick} text={"Buy MAX"} />
|
||||
<StockTickerTxButton onClick={this.handleSellAllButtonClick} text={"Sell ALL"} />
|
||||
{
|
||||
causesMovement &&
|
||||
<p className="stock-market-price-movement-warning">
|
||||
WARNING: Buying/Selling {numeralWrapper.formatBigNumber(qty)} shares may affect
|
||||
the stock's price. This applies during the transaction itself as well. See Investopedia
|
||||
for more details.
|
||||
</p>
|
||||
}
|
||||
<StockTickerPositionText p={this.props.p} stock={this.props.stock} />
|
||||
<StockTickerOrderList
|
||||
cancelOrder={this.props.cancelOrder}
|
||||
|
@ -27,6 +27,9 @@ export function StockTickerHeaderText(props: IProps): React.ReactElement {
|
||||
let plusOrMinus = stock.b; // True for "+", false for "-"
|
||||
if (stock.otlkMag < 0) { plusOrMinus = !plusOrMinus }
|
||||
hdrText += (plusOrMinus ? "+" : "-").repeat(Math.floor(Math.abs(stock.otlkMag) / 10) + 1);
|
||||
|
||||
// Debugging:
|
||||
// hdrText += ` - ${stock.getAbsoluteForecast()} / ${stock.otlkMagForecast}`;
|
||||
}
|
||||
|
||||
let styleMarkup = {
|
||||
|
@ -53,7 +53,9 @@ import {
|
||||
import { showLiterature } from "./Literature";
|
||||
import { Message } from "./Message/Message";
|
||||
import { showMessage } from "./Message/MessageHelpers";
|
||||
import { killWorkerScript, addWorkerScript } from "./NetscriptWorker";
|
||||
import { addWorkerScript } from "./NetscriptWorker";
|
||||
import { killWorkerScript } from "./Netscript/killWorkerScript";
|
||||
import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStartStopEventEmitter";
|
||||
import { Player } from "./Player";
|
||||
import { hackWorldDaemon } from "./RedPill";
|
||||
import { RunningScript } from "./Script/RunningScript";
|
||||
@ -1145,8 +1147,9 @@ let Terminal = {
|
||||
}
|
||||
case "killall": {
|
||||
for (let i = s.runningScripts.length - 1; i >= 0; --i) {
|
||||
killWorkerScript(s.runningScripts[i], s.ip);
|
||||
killWorkerScript(s.runningScripts[i], s.ip, false);
|
||||
}
|
||||
WorkerScriptStartStopEventEmitter.emitEvent();
|
||||
post("Killing all running scripts. May take up to a few minutes for the scripts to die...");
|
||||
break;
|
||||
}
|
||||
@ -1249,7 +1252,7 @@ let Terminal = {
|
||||
}
|
||||
for (let i = 0; i < s.runningScripts.length; i++) {
|
||||
let rsObj = s.runningScripts[i];
|
||||
let res = rsObj.filename;
|
||||
let res = `(PID - ${rsObj.pid}) ${rsObj.filename}`;
|
||||
for (let j = 0; j < rsObj.args.length; ++j) {
|
||||
res += (" " + rsObj.args[j].toString());
|
||||
}
|
||||
@ -1431,7 +1434,23 @@ let Terminal = {
|
||||
return;
|
||||
}
|
||||
|
||||
post("Script Threads RAM Usage");
|
||||
// Headers
|
||||
const scriptWidth = 40;
|
||||
const pidWidth = 10;
|
||||
const threadsWidth = 16;
|
||||
|
||||
const scriptTxt = "Script";
|
||||
const pidTxt = "PID";
|
||||
const threadsTxt = "Threads";
|
||||
const ramTxt = "RAM Usage";
|
||||
|
||||
const spacesAfterScriptTxt = " ".repeat(scriptWidth - scriptTxt.length);
|
||||
const spacesAfterPidTxt = " ".repeat(pidWidth - pidTxt.length);
|
||||
const spacesAfterThreadsTxt = " ".repeat(threadsWidth - threadsTxt.length);
|
||||
|
||||
const headers = `${scriptTxt}${spacesAfterScriptTxt}${pidTxt}${spacesAfterPidTxt}${threadsTxt}${spacesAfterThreadsTxt}${ramTxt}`;
|
||||
|
||||
post(headers);
|
||||
|
||||
let currRunningScripts = s.runningScripts;
|
||||
// Iterate through scripts on current server
|
||||
@ -1439,19 +1458,30 @@ let Terminal = {
|
||||
let script = currRunningScripts[i];
|
||||
|
||||
// Calculate name padding
|
||||
let numSpacesScript = 32 - script.filename.length; // 26 -> width of name column
|
||||
if (numSpacesScript < 0) {numSpacesScript = 0;}
|
||||
let spacesScript = Array(numSpacesScript+1).join(" ");
|
||||
const numSpacesScript = Math.max(0, scriptWidth - script.filename.length);
|
||||
const spacesScript = " ".repeat(numSpacesScript);
|
||||
|
||||
// Calculate PID padding
|
||||
const numSpacesPid = Math.max(0, pidWidth - (script.pid + "").length);
|
||||
const spacesPid = " ".repeat(numSpacesPid);
|
||||
|
||||
// Calculate thread padding
|
||||
let numSpacesThread = 16 - (script.threads + "").length; // 16 -> width of thread column
|
||||
let spacesThread = Array(numSpacesThread+1).join(" ");
|
||||
const numSpacesThread = Math.max(0, threadsWidth - (script.threads + "").length);
|
||||
const spacesThread = " ".repeat(numSpacesThread);
|
||||
|
||||
// Calculate and transform RAM usage
|
||||
let ramUsage = numeralWrapper.format(getRamUsageFromRunningScript(script) * script.threads, '0.00') + " GB";
|
||||
const ramUsage = numeralWrapper.format(getRamUsageFromRunningScript(script) * script.threads, '0.00') + " GB";
|
||||
|
||||
var entry = [script.filename, spacesScript, script.threads, spacesThread, ramUsage];
|
||||
post(entry.join(""));
|
||||
const entry = [
|
||||
script.filename,
|
||||
spacesScript,
|
||||
script.pid,
|
||||
spacesPid,
|
||||
script.threads,
|
||||
spacesThread,
|
||||
ramUsage
|
||||
].join("");
|
||||
post(entry);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -65,16 +65,14 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
|
||||
solver: (data: number, ans: string) => {
|
||||
let fac: number = 2;
|
||||
let n: number = data;
|
||||
while (n > fac) {
|
||||
if (n % fac === 0) {
|
||||
while (n > ((fac-1) * (fac-1))) {
|
||||
while (n % fac === 0) {
|
||||
n = Math.round(n / fac);
|
||||
fac = 2;
|
||||
} else {
|
||||
}
|
||||
++fac;
|
||||
}
|
||||
}
|
||||
|
||||
return fac === parseInt(ans, 10);
|
||||
return (n===1 ? (fac-1) : n) === parseInt(ans, 10);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -1,29 +1,26 @@
|
||||
/**
|
||||
* Game engine. Handles the main game loop as well as the main UI pages
|
||||
*
|
||||
* TODO: Separate UI functionality into its own component
|
||||
*/
|
||||
import {
|
||||
formatNumber,
|
||||
convertTimeMsToTimeElapsedString,
|
||||
replaceAt
|
||||
} from "../utils/StringHelperFunctions";
|
||||
import { loxBoxCreate, logBoxUpdateText, logBoxOpened } from "../utils/LogBox";
|
||||
import { updateActiveScriptsItems } from "./ActiveScriptsUI";
|
||||
import { logBoxUpdateText, logBoxOpened } from "../utils/LogBox";
|
||||
import { Augmentations } from "./Augmentation/Augmentations";
|
||||
import {
|
||||
installAugmentations,
|
||||
initAugmentations,
|
||||
displayAugmentationsContent,
|
||||
PlayerOwnedAugmentation
|
||||
} from "./Augmentation/AugmentationHelpers";
|
||||
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
|
||||
|
||||
import {
|
||||
BitNodes,
|
||||
initBitNodes,
|
||||
initBitNodeMultipliers
|
||||
} from "./BitNode/BitNode";
|
||||
import { Bladeburner } from "./Bladeburner";
|
||||
import { CharacterOverviewComponent } from "./ui/React/CharacterOverview";
|
||||
import { cinematicTextFlag } from "./CinematicText";
|
||||
import { generateRandomContract } from "./CodingContractGenerator";
|
||||
import { CompanyPositions } from "./Company/CompanyPositions";
|
||||
import { initCompanies } from "./Company/Companies";
|
||||
import { Corporation } from "./Corporation/Corporation";
|
||||
import { CONSTANTS } from "./Constants";
|
||||
@ -48,46 +45,35 @@ import { LocationName } from "./Locations/data/LocationNames";
|
||||
import { LocationRoot } from "./Locations/ui/Root";
|
||||
import { checkForMessagesToSend, initMessages } from "./Message/MessageHelpers";
|
||||
import { inMission, currMission } from "./Missions";
|
||||
import { workerScripts } from "./Netscript/WorkerScripts";
|
||||
import {
|
||||
loadAllRunningScripts,
|
||||
runScriptsLoop,
|
||||
updateOnlineScriptTimes,
|
||||
} from "./NetscriptWorker";
|
||||
import { Player } from "./Player";
|
||||
import { prestigeAugmentation, prestigeSourceFile } from "./Prestige";
|
||||
import { Programs } from "./Programs/Programs";
|
||||
import { prestigeAugmentation } from "./Prestige";
|
||||
import {
|
||||
displayCreateProgramContent,
|
||||
getNumAvailableCreateProgram,
|
||||
initCreateProgramButtons
|
||||
} from "./Programs/ProgramHelpers";
|
||||
import { redPillFlag, hackWorldDaemon } from "./RedPill";
|
||||
import { redPillFlag } from "./RedPill";
|
||||
import { saveObject, loadGame } from "./SaveObject";
|
||||
import {
|
||||
getCurrentEditor,
|
||||
scriptEditorInit,
|
||||
updateScriptEditorContent
|
||||
} from "./Script/ScriptHelpers";
|
||||
import { AllServers, initForeignServers } from "./Server/AllServers";
|
||||
|
||||
import { Server } from "./Server/Server";
|
||||
import { initForeignServers } from "./Server/AllServers";
|
||||
import { Settings } from "./Settings/Settings";
|
||||
import { initSourceFiles, SourceFiles } from "./SourceFile";
|
||||
import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags";
|
||||
import { initSpecialServerIps } from "./Server/SpecialServerIps";
|
||||
import {
|
||||
SpecialServerIps,
|
||||
initSpecialServerIps
|
||||
} from "./Server/SpecialServerIps";
|
||||
import {
|
||||
StockMarket,
|
||||
SymbolToStockMap,
|
||||
initSymbolToStockMap,
|
||||
stockMarketCycle,
|
||||
processStockPrices,
|
||||
displayStockMarketContent
|
||||
} from "./StockMarket/StockMarket";
|
||||
import { Terminal, postNetburnerText } from "./Terminal";
|
||||
|
||||
import { Sleeve } from "./PersonObjects/Sleeve/Sleeve";
|
||||
import {
|
||||
clearSleevesPage,
|
||||
@ -104,14 +90,14 @@ import { displayCharacterInfo } from "./ui/displayCharacterInfo";
|
||||
import { Page, routing } from "./ui/navigationTracking";
|
||||
import { numeralWrapper } from "./ui/numeralFormat";
|
||||
import { setSettingsLabels } from "./ui/setSettingsLabels";
|
||||
|
||||
import { ActiveScriptsRoot } from "./ui/ActiveScripts/Root";
|
||||
import { initializeMainMenuHeaders } from "./ui/MainMenu/Headers";
|
||||
import { initializeMainMenuLinks, MainMenuLinks } from "./ui/MainMenu/Links";
|
||||
|
||||
import { dialogBoxCreate } from "../utils/DialogBox";
|
||||
import { gameOptionsBoxClose, gameOptionsBoxOpen } from "../utils/GameOptions";
|
||||
import { getRandomInt } from "../utils/helpers/getRandomInt";
|
||||
import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement";
|
||||
import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners";
|
||||
import { createElement } from "../utils/uiHelpers/createElement";
|
||||
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
|
||||
import { removeLoadingScreen } from "../utils/uiHelpers/removeLoadingScreen";
|
||||
@ -278,7 +264,10 @@ const Engine = {
|
||||
Engine.hideAllContent();
|
||||
Engine.Display.activeScriptsContent.style.display = "block";
|
||||
routing.navigateTo(Page.ActiveScripts);
|
||||
updateActiveScriptsItems();
|
||||
ReactDOM.render(
|
||||
<ActiveScriptsRoot p={Player} workerScripts={workerScripts} />,
|
||||
Engine.Display.activeScriptsContent
|
||||
)
|
||||
MainMenuLinks.ActiveScripts.classList.add("active");
|
||||
},
|
||||
|
||||
@ -315,8 +304,8 @@ const Engine = {
|
||||
loadAugmentationsContent: function() {
|
||||
Engine.hideAllContent();
|
||||
Engine.Display.augmentationsContent.style.display = "block";
|
||||
displayAugmentationsContent(Engine.Display.augmentationsContent);
|
||||
routing.navigateTo(Page.Augmentations);
|
||||
displayAugmentationsContent(Engine.Display.augmentationsContent);
|
||||
MainMenuLinks.Augmentations.classList.add("active");
|
||||
},
|
||||
|
||||
@ -490,16 +479,26 @@ const Engine = {
|
||||
Engine.Display.terminalContent.style.display = "none";
|
||||
Engine.Display.characterContent.style.display = "none";
|
||||
Engine.Display.scriptEditorContent.style.display = "none";
|
||||
|
||||
Engine.Display.activeScriptsContent.style.display = "none";
|
||||
ReactDOM.unmountComponentAtNode(Engine.Display.activeScriptsContent);
|
||||
|
||||
clearHacknetNodesUI();
|
||||
Engine.Display.createProgramContent.style.display = "none";
|
||||
|
||||
Engine.Display.factionsContent.style.display = "none";
|
||||
ReactDOM.unmountComponentAtNode(Engine.Display.factionContent);
|
||||
|
||||
Engine.Display.factionContent.style.display = "none";
|
||||
ReactDOM.unmountComponentAtNode(Engine.Display.factionContent);
|
||||
|
||||
Engine.Display.augmentationsContent.style.display = "none";
|
||||
ReactDOM.unmountComponentAtNode(Engine.Display.augmentationsContent);
|
||||
|
||||
Engine.Display.tutorialContent.style.display = "none";
|
||||
|
||||
Engine.Display.locationContent.style.display = "none";
|
||||
ReactDOM.unmountComponentAtNode(Engine.Display.locationContent);
|
||||
|
||||
Engine.Display.workInProgressContent.style.display = "none";
|
||||
Engine.Display.redPillContent.style.display = "none";
|
||||
Engine.Display.cinematicTextContent.style.display = "none";
|
||||
@ -778,7 +777,6 @@ const Engine = {
|
||||
checkFactionInvitations: 100,
|
||||
passiveFactionGrowth: 600,
|
||||
messages: 150,
|
||||
sCr: 1500,
|
||||
mechanicProcess: 5, // Processes certain mechanics (Corporation, Bladeburner)
|
||||
contractGeneration: 3000, // Generate Coding Contracts
|
||||
},
|
||||
@ -814,13 +812,14 @@ const Engine = {
|
||||
}
|
||||
|
||||
if (Engine.Counters.updateActiveScriptsDisplay <= 0) {
|
||||
// Always update, but make the interval longer if the page isn't active
|
||||
updateActiveScriptsItems();
|
||||
if (routing.isOn(Page.ActiveScripts)) {
|
||||
Engine.Counters.updateActiveScriptsDisplay = 5;
|
||||
} else {
|
||||
Engine.Counters.updateActiveScriptsDisplay = 10;
|
||||
ReactDOM.render(
|
||||
<ActiveScriptsRoot p={Player} workerScripts={workerScripts} />,
|
||||
Engine.Display.activeScriptsContent
|
||||
)
|
||||
}
|
||||
|
||||
Engine.Counters.updateActiveScriptsDisplay = 5;
|
||||
}
|
||||
|
||||
if (Engine.Counters.updateDisplays <= 0) {
|
||||
@ -900,13 +899,6 @@ const Engine = {
|
||||
}
|
||||
}
|
||||
|
||||
if (Engine.Counters.sCr <= 0) {
|
||||
if (Player.hasWseAccount) {
|
||||
stockMarketCycle();
|
||||
}
|
||||
Engine.Counters.sCr = 1500;
|
||||
}
|
||||
|
||||
if (Engine.Counters.mechanicProcess <= 0) {
|
||||
if (Player.corporation instanceof Corporation) {
|
||||
Player.corporation.process();
|
||||
@ -1043,9 +1035,7 @@ const Engine = {
|
||||
|
||||
// Load game from save or create new game
|
||||
if (loadGame(saveString)) {
|
||||
initBitNodes();
|
||||
initBitNodeMultipliers(Player);
|
||||
initSourceFiles();
|
||||
Engine.setDisplayElements(); // Sets variables for important DOM elements
|
||||
Engine.init(); // Initialize buttons, work, etc.
|
||||
initAugmentations(); // Also calls Player.reapplyAllAugmentations()
|
||||
@ -1166,9 +1156,7 @@ const Engine = {
|
||||
} else {
|
||||
// No save found, start new game
|
||||
console.log("Initializing new game");
|
||||
initBitNodes();
|
||||
initBitNodeMultipliers(Player);
|
||||
initSourceFiles();
|
||||
initSpecialServerIps();
|
||||
Engine.setDisplayElements(); // Sets variables for important DOM elements
|
||||
Engine.start(); // Run main game loop and Scripts loop
|
||||
@ -1531,9 +1519,6 @@ const Engine = {
|
||||
start: function() {
|
||||
// Run main loop
|
||||
Engine.idleTimer();
|
||||
|
||||
// Script-processing loop
|
||||
runScriptsLoop();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -9,8 +9,10 @@ import "../css/characteroverview.scss";
|
||||
import "../css/terminal.scss";
|
||||
import "../css/scripteditor.scss";
|
||||
import "../css/codemirror-overrides.scss";
|
||||
import "../css/activescripts.scss";
|
||||
import "../css/hacknetnodes.scss";
|
||||
import "../css/menupages.scss";
|
||||
import "../css/augmentations.scss";
|
||||
import "../css/redpill.scss";
|
||||
import "../css/stockmarket.scss";
|
||||
import "../css/workinprogress.scss";
|
||||
|
38
src/ui/ActiveScripts/Root.tsx
Normal file
38
src/ui/ActiveScripts/Root.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Root React Component for the "Active Scripts" UI page. This page displays
|
||||
* and provides information about all of the player's scripts that are currently running
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
import { ScriptProduction } from "./ScriptProduction";
|
||||
import { ServerAccordions } from "./ServerAccordions";
|
||||
|
||||
import { WorkerScript } from "../../Netscript/WorkerScript";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
|
||||
type IProps = {
|
||||
p: IPlayer;
|
||||
workerScripts: Map<number, WorkerScript>;
|
||||
}
|
||||
|
||||
export class ActiveScriptsRoot extends React.Component<IProps> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<p>
|
||||
This page displays a list of all of your scripts that are currently
|
||||
running across every machine. It also provides information about each
|
||||
script's production. The scripts are categorized by the hostname of
|
||||
the servers on which they are running.
|
||||
</p>
|
||||
|
||||
<ScriptProduction {...this.props} />
|
||||
<ServerAccordions {...this.props} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
46
src/ui/ActiveScripts/ScriptProduction.tsx
Normal file
46
src/ui/ActiveScripts/ScriptProduction.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* React Component for displaying the total production and production rate
|
||||
* of scripts on the 'Active Scripts' UI page
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
import { numeralWrapper } from "../numeralFormat";
|
||||
|
||||
import { WorkerScript } from "../../Netscript/WorkerScript";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
|
||||
type IProps = {
|
||||
p: IPlayer;
|
||||
workerScripts: Map<number, WorkerScript>;
|
||||
}
|
||||
|
||||
export function ScriptProduction(props: IProps): React.ReactElement {
|
||||
const prodRateSinceLastAug = props.p.scriptProdSinceLastAug / (props.p.playtimeSinceLastAug / 1000);
|
||||
|
||||
let onlineProduction = 0;
|
||||
for (const ws of props.workerScripts.values()) {
|
||||
onlineProduction += (ws.scriptRef.onlineMoneyMade / ws.scriptRef.onlineRunningTime);
|
||||
}
|
||||
|
||||
return (
|
||||
<p id="active-scripts-total-prod">
|
||||
Total online production of Active scripts:
|
||||
<span className="money-gold">
|
||||
<span id="active-scripts-total-production-active">
|
||||
{numeralWrapper.formatMoney(onlineProduction)}
|
||||
</span> / sec
|
||||
</span><br />
|
||||
|
||||
Total online production since last Aug installation:
|
||||
<span id="active-scripts-total-prod-aug-total" className="money-gold">
|
||||
{numeralWrapper.formatMoney(props.p.scriptProdSinceLastAug)}
|
||||
</span>
|
||||
|
||||
(<span className="money-gold">
|
||||
<span id="active-scripts-total-prod-aug-avg" className="money-gold">
|
||||
{numeralWrapper.formatMoney(prodRateSinceLastAug)}
|
||||
</span> / sec
|
||||
</span>)
|
||||
</p>
|
||||
)
|
||||
}
|
49
src/ui/ActiveScripts/ServerAccordion.tsx
Normal file
49
src/ui/ActiveScripts/ServerAccordion.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* React Component for rendering the Accordion element for a single
|
||||
* server in the 'Active Scripts' UI page
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
import { WorkerScriptAccordion } from "./WorkerScriptAccordion";
|
||||
import { Accordion } from "../React/Accordion";
|
||||
|
||||
import { BaseServer } from "../../Server/BaseServer";
|
||||
import { WorkerScript } from "../../Netscript/WorkerScript";
|
||||
|
||||
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
|
||||
|
||||
type IProps = {
|
||||
server: BaseServer;
|
||||
workerScripts: WorkerScript[];
|
||||
}
|
||||
|
||||
export function ServerAccordion(props: IProps): React.ReactElement {
|
||||
const server = props.server;
|
||||
|
||||
// Accordion's header text
|
||||
// TODO: calculate the longest hostname length rather than hard coding it
|
||||
const longestHostnameLength = 18;
|
||||
const paddedName = `${server.hostname}${" ".repeat(longestHostnameLength)}`.slice(0, Math.max(server.hostname.length, longestHostnameLength));
|
||||
const barOptions = {
|
||||
progress: server.ramUsed / server.maxRam,
|
||||
totalTicks: 30
|
||||
};
|
||||
const headerTxt = `${paddedName} ${createProgressBarText(barOptions)}`;
|
||||
|
||||
const scripts = props.workerScripts.map((ws) => {
|
||||
return (
|
||||
<WorkerScriptAccordion key={`${ws.name}_${ws.args}`} workerScript={ws} />
|
||||
)
|
||||
});
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
headerContent={
|
||||
<pre>{headerTxt}</pre>
|
||||
}
|
||||
panelContent={
|
||||
<ul>{scripts}</ul>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
109
src/ui/ActiveScripts/ServerAccordions.tsx
Normal file
109
src/ui/ActiveScripts/ServerAccordions.tsx
Normal file
@ -0,0 +1,109 @@
|
||||
/**
|
||||
* React Component for rendering the Accordion elements for all servers
|
||||
* on which scripts are running
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
import { ServerAccordion } from "./ServerAccordion";
|
||||
|
||||
import { WorkerScript } from "../../Netscript/WorkerScript";
|
||||
import { WorkerScriptStartStopEventEmitter } from "../../Netscript/WorkerScriptStartStopEventEmitter";
|
||||
import { getServer } from "../../Server/ServerHelpers";
|
||||
import { BaseServer } from "../../Server/BaseServer";
|
||||
|
||||
// Map of server hostname -> all workerscripts on that server for all active scripts
|
||||
interface IServerData {
|
||||
server: BaseServer;
|
||||
workerScripts: WorkerScript[];
|
||||
}
|
||||
|
||||
interface IServerToScriptsMap {
|
||||
[key: string]: IServerData;
|
||||
}
|
||||
|
||||
type IProps = {
|
||||
workerScripts: Map<number, WorkerScript>;
|
||||
};
|
||||
|
||||
type IState = {
|
||||
rerenderFlag: boolean;
|
||||
}
|
||||
|
||||
|
||||
const subscriberId = "ActiveScriptsUI";
|
||||
|
||||
export class ServerAccordions extends React.Component<IProps, IState> {
|
||||
serverToScriptMap: IServerToScriptsMap = {};
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
rerenderFlag: false,
|
||||
}
|
||||
|
||||
this.updateServerToScriptsMap();
|
||||
|
||||
this.rerender = this.rerender.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
WorkerScriptStartStopEventEmitter.addSubscriber({
|
||||
cb: this.rerender,
|
||||
id: subscriberId,
|
||||
})
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
WorkerScriptStartStopEventEmitter.removeSubscriber(subscriberId);
|
||||
}
|
||||
|
||||
updateServerToScriptsMap(): void {
|
||||
const map: IServerToScriptsMap = {};
|
||||
|
||||
for (const ws of this.props.workerScripts.values()) {
|
||||
const server = getServer(ws.serverIp);
|
||||
if (server == null) {
|
||||
console.warn(`WorkerScript has invalid IP address: ${ws.serverIp}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (map[server.hostname] == null) {
|
||||
map[server.hostname] = {
|
||||
server: server,
|
||||
workerScripts: [],
|
||||
};
|
||||
}
|
||||
|
||||
map[server.hostname].workerScripts.push(ws);
|
||||
}
|
||||
|
||||
this.serverToScriptMap = map;
|
||||
}
|
||||
|
||||
rerender() {
|
||||
this.updateServerToScriptsMap();
|
||||
this.setState((prevState) => {
|
||||
return { rerenderFlag: !prevState.rerenderFlag }
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const elems = Object.keys(this.serverToScriptMap).map((serverName) => {
|
||||
const data = this.serverToScriptMap[serverName];
|
||||
return (
|
||||
<ServerAccordion
|
||||
key={serverName}
|
||||
server={data.server}
|
||||
workerScripts={data.workerScripts}
|
||||
/>
|
||||
)
|
||||
});
|
||||
|
||||
return (
|
||||
<ul className="active-scripts-list" id="active-scripts-list">
|
||||
{elems}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
}
|
77
src/ui/ActiveScripts/WorkerScriptAccordion.tsx
Normal file
77
src/ui/ActiveScripts/WorkerScriptAccordion.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
/**
|
||||
* React Component for displaying a single WorkerScript's info as an
|
||||
* Accordion element
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
import { numeralWrapper } from "../numeralFormat";
|
||||
|
||||
import { Accordion } from "../React/Accordion";
|
||||
import { AccordionButton } from "../React/AccordionButton";
|
||||
|
||||
import { killWorkerScript } from "../../Netscript/killWorkerScript";
|
||||
import { WorkerScript } from "../../Netscript/WorkerScript";
|
||||
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
import { logBoxCreate } from "../../../utils/LogBox";
|
||||
import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions";
|
||||
import { arrayToString } from "../../../utils/helpers/arrayToString";
|
||||
|
||||
type IProps = {
|
||||
workerScript: WorkerScript;
|
||||
}
|
||||
|
||||
export function WorkerScriptAccordion(props: IProps): React.ReactElement {
|
||||
const workerScript = props.workerScript;
|
||||
const scriptRef = workerScript.scriptRef;
|
||||
|
||||
|
||||
const logClickHandler = logBoxCreate.bind(null, scriptRef);
|
||||
const killScript = killWorkerScript.bind(null, scriptRef, scriptRef.server);
|
||||
|
||||
function killScriptClickHandler() {
|
||||
killScript();
|
||||
dialogBoxCreate("Killing script");
|
||||
}
|
||||
|
||||
// Calculations for script stats
|
||||
const onlineMps = scriptRef.onlineMoneyMade / scriptRef.onlineRunningTime;
|
||||
const onlineEps = scriptRef.onlineExpGained / scriptRef.onlineRunningTime;
|
||||
const offlineMps = scriptRef.offlineMoneyMade / scriptRef.offlineRunningTime;
|
||||
const offlineEps = scriptRef.offlineExpGained / scriptRef.offlineRunningTime;
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
headerClass="active-scripts-script-header"
|
||||
headerContent={
|
||||
<>{props.workerScript.name}</>
|
||||
}
|
||||
panelClass="active-scripts-script-panel"
|
||||
panelContent={
|
||||
<>
|
||||
<pre>Threads: {props.workerScript.scriptRef.threads}</pre>
|
||||
<pre>Args: {arrayToString(props.workerScript.args)}</pre>
|
||||
<pre>Online Time: {convertTimeMsToTimeElapsedString(scriptRef.onlineRunningTime * 1e3)}</pre>
|
||||
<pre>Offline Time: {convertTimeMsToTimeElapsedString(scriptRef.offlineRunningTime * 1e3)}</pre>
|
||||
<pre>Total online production: {numeralWrapper.formatMoney(scriptRef.onlineMoneyMade)}</pre>
|
||||
<pre>{(Array(26).join(" ") + numeralWrapper.formatBigNumber(scriptRef.onlineExpGained) + " hacking exp")}</pre>
|
||||
<pre>Online production rate: {numeralWrapper.formatMoney(onlineMps)} / second</pre>
|
||||
<pre>{(Array(25).join(" ") + numeralWrapper.formatBigNumber(onlineEps) + " hacking exp / second")}</pre>
|
||||
<pre>Total offline production: {numeralWrapper.formatMoney(scriptRef.offlineMoneyMade)}</pre>
|
||||
<pre>{(Array(27).join(" ") + numeralWrapper.formatBigNumber(scriptRef.offlineExpGained) + " hacking exp")}</pre>
|
||||
<pre>Offline production rate: {numeralWrapper.formatMoney(offlineMps)} / second</pre>
|
||||
<pre>{(Array(26).join(" ") + numeralWrapper.formatBigNumber(offlineEps) + " hacking exp / second")}</pre>
|
||||
|
||||
<AccordionButton
|
||||
onClick={logClickHandler}
|
||||
text="Log"
|
||||
/>
|
||||
<AccordionButton
|
||||
onClick={killScriptClickHandler}
|
||||
text="Kill Script"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
@ -4,9 +4,12 @@
|
||||
import * as React from "react";
|
||||
|
||||
type IProps = {
|
||||
headerClass?: string; // Override default class
|
||||
headerContent: React.ReactElement;
|
||||
panelClass?: string; // Override default class
|
||||
panelContent: React.ReactElement;
|
||||
panelInitiallyOpened?: boolean;
|
||||
style?: string;
|
||||
}
|
||||
|
||||
type IState = {
|
||||
@ -44,19 +47,29 @@ export class Accordion extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
render() {
|
||||
let className = "accordion-header";
|
||||
if (typeof this.props.headerClass === "string") {
|
||||
className = this.props.headerClass;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button className={"accordion-header"} onClick={this.handleHeaderClick}>
|
||||
<>
|
||||
<button className={className} onClick={this.handleHeaderClick}>
|
||||
{this.props.headerContent}
|
||||
</button>
|
||||
<AccordionPanel opened={this.state.panelOpened} panelContent={this.props.panelContent} />
|
||||
</div>
|
||||
<AccordionPanel
|
||||
opened={this.state.panelOpened}
|
||||
panelClass={this.props.panelClass}
|
||||
panelContent={this.props.panelContent}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
type IPanelProps = {
|
||||
opened: boolean;
|
||||
panelClass?: string; // Override default class
|
||||
panelContent: React.ReactElement;
|
||||
}
|
||||
|
||||
@ -66,8 +79,13 @@ class AccordionPanel extends React.Component<IPanelProps, any> {
|
||||
}
|
||||
|
||||
render() {
|
||||
let className = "accordion-panel"
|
||||
if (typeof this.props.panelClass === "string") {
|
||||
className = this.props.panelClass;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={"accordion-panel"}>
|
||||
<div className={className}>
|
||||
{this.props.panelContent}
|
||||
</div>
|
||||
)
|
||||
|
52
src/ui/React/AccordionButton.tsx
Normal file
52
src/ui/React/AccordionButton.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Basic stateless button that uses the 'accordion-button' css class.
|
||||
* This class has a black background so that it does not clash with the default
|
||||
* accordion coloring
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
interface IProps {
|
||||
addClasses?: string;
|
||||
disabled?: boolean;
|
||||
id?: string;
|
||||
onClick?: (e: React.MouseEvent<HTMLElement>) => any;
|
||||
style?: object;
|
||||
text: string;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
type IInnerHTMLMarkup = {
|
||||
__html: string;
|
||||
}
|
||||
|
||||
export function AccordionButton(props: IProps): React.ReactElement {
|
||||
const hasTooltip = props.tooltip != null && props.tooltip !== "";
|
||||
|
||||
// TODO Add a disabled class for accordion buttons?
|
||||
let className = "accordion-button";
|
||||
if (hasTooltip) {
|
||||
className += " tooltip";
|
||||
}
|
||||
|
||||
if (typeof props.addClasses === "string") {
|
||||
className += ` ${props.addClasses}`;
|
||||
}
|
||||
|
||||
// Tooltip will be set using inner HTML
|
||||
let tooltipMarkup: IInnerHTMLMarkup | null;
|
||||
if (hasTooltip) {
|
||||
tooltipMarkup = {
|
||||
__html: props.tooltip!
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<button className={className} id={props.id} onClick={props.onClick} style={props.style}>
|
||||
{props.text}
|
||||
{
|
||||
hasTooltip &&
|
||||
<span className={"tooltiptext"} dangerouslySetInnerHTML={tooltipMarkup!}></span>
|
||||
}
|
||||
</button>
|
||||
)
|
||||
}
|
33
src/ui/React/AugmentationAccordion.tsx
Normal file
33
src/ui/React/AugmentationAccordion.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* React Component for displaying a single Augmentation as an accordion.
|
||||
*
|
||||
* The header of the accordion contains the Augmentation's name (and level, if
|
||||
* applicable), and the accordion's panel contains the Augmentation's description.
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
import { Accordion } from "./Accordion";
|
||||
|
||||
import { Augmentation } from "../../Augmentation/Augmentation";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
|
||||
type IProps = {
|
||||
aug: Augmentation,
|
||||
level?: number | string | null,
|
||||
}
|
||||
|
||||
export function AugmentationAccordion(props: IProps): React.ReactElement {
|
||||
let displayName = props.aug.name;
|
||||
if (props.level != null) {
|
||||
if (props.aug.name === AugmentationNames.NeuroFluxGovernor) {
|
||||
displayName += (` - Level ${props.level}`)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
headerContent={<>{displayName}</>}
|
||||
panelContent={<p dangerouslySetInnerHTML={{__html: props.aug.info}}></p>}
|
||||
/>
|
||||
)
|
||||
}
|
@ -13,12 +13,10 @@ interface IProps {
|
||||
props: object;
|
||||
}
|
||||
|
||||
export class Popup extends React.Component<IProps, any> {
|
||||
render() {
|
||||
export function Popup(props: IProps): React.ReactElement {
|
||||
return (
|
||||
<div className={"popup-box-content"} id={`${this.props.id}-content`}>
|
||||
{React.createElement(this.props.content, this.props.props)}
|
||||
<div className={"popup-box-content"} id={`${props.id}-content`}>
|
||||
{React.createElement(props.content, props.props)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
35
src/ui/React/SourceFileAccordion.tsx
Normal file
35
src/ui/React/SourceFileAccordion.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* React Component for displaying a single Source-File as an accordion.
|
||||
*
|
||||
* The header of the accordion contains the Source-Files's name and level,
|
||||
* and the accordion's panel contains the Source-File's description.
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
import { Accordion } from "./Accordion";
|
||||
|
||||
import { SourceFile } from "../../SourceFile/SourceFile";
|
||||
|
||||
type IProps = {
|
||||
level: number,
|
||||
sf: SourceFile,
|
||||
}
|
||||
|
||||
export function SourceFileAccordion(props: IProps): React.ReactElement {
|
||||
const maxLevel = props.sf.n === 3 ? "∞" : "3";
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
headerContent={
|
||||
<>
|
||||
{props.sf.name}
|
||||
<br />
|
||||
{`Level ${props.level} / ${maxLevel}`}
|
||||
</>
|
||||
}
|
||||
panelContent={
|
||||
<p dangerouslySetInnerHTML={{__html: props.sf.info}}></p>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
import * as React from "react";
|
||||
|
||||
interface IStdButtonProps {
|
||||
addClasses?: string;
|
||||
disabled?: boolean;
|
||||
id?: string;
|
||||
onClick?: (e: React.MouseEvent<HTMLElement>) => any;
|
||||
@ -17,30 +18,32 @@ type IInnerHTMLMarkup = {
|
||||
__html: string;
|
||||
}
|
||||
|
||||
export class StdButton extends React.Component<IStdButtonProps, any> {
|
||||
render() {
|
||||
const hasTooltip = this.props.tooltip != null && this.props.tooltip !== "";
|
||||
let className = this.props.disabled ? "std-button-disabled" : "std-button";
|
||||
export function StdButton(props: IStdButtonProps): React.ReactElement {
|
||||
const hasTooltip = props.tooltip != null && props.tooltip !== "";
|
||||
let className = props.disabled ? "std-button-disabled" : "std-button";
|
||||
if (hasTooltip) {
|
||||
className += " tooltip";
|
||||
}
|
||||
|
||||
if (typeof props.addClasses === "string") {
|
||||
className += ` ${props.addClasses}`;
|
||||
}
|
||||
|
||||
// Tooltip will be set using inner HTML
|
||||
let tooltipMarkup: IInnerHTMLMarkup | null;
|
||||
if (hasTooltip) {
|
||||
tooltipMarkup = {
|
||||
__html: this.props.tooltip!
|
||||
__html: props.tooltip!
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<button className={className} id={this.props.id} onClick={this.props.onClick} style={this.props.style}>
|
||||
{this.props.text}
|
||||
<button className={className} id={props.id} onClick={props.onClick} style={props.style}>
|
||||
{props.text}
|
||||
{
|
||||
hasTooltip &&
|
||||
<span className={"tooltiptext"} dangerouslySetInnerHTML={tooltipMarkup!}></span>
|
||||
}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
50
src/utils/EventEmitter.ts
Normal file
50
src/utils/EventEmitter.ts
Normal file
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Generic Event Emitter class following a subscribe/publish paradigm.
|
||||
*/
|
||||
import { IMap } from "../types";
|
||||
|
||||
type cbFn = (...args: any[]) => any;
|
||||
|
||||
export interface ISubscriber {
|
||||
/**
|
||||
* Callback function that will be run when an event is emitted
|
||||
*/
|
||||
cb: cbFn;
|
||||
|
||||
/**
|
||||
* Name/identifier for this subscriber
|
||||
*/
|
||||
id: string;
|
||||
}
|
||||
|
||||
export class EventEmitter {
|
||||
/**
|
||||
* Map of Subscriber name -> Callback function
|
||||
*/
|
||||
subscribers: IMap<cbFn> = {};
|
||||
|
||||
constructor(subs?: ISubscriber[]) {
|
||||
if (Array.isArray(subs)) {
|
||||
for (const s of subs) {
|
||||
this.addSubscriber(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addSubscriber(s: ISubscriber) {
|
||||
this.subscribers[s.id] = s.cb;
|
||||
}
|
||||
|
||||
emitEvent(...args: any[]): void {
|
||||
for (const s in this.subscribers) {
|
||||
const cb = this.subscribers[s];
|
||||
|
||||
cb(args);
|
||||
}
|
||||
}
|
||||
|
||||
removeSubscriber(id: string) {
|
||||
delete this.subscribers[id];
|
||||
}
|
||||
|
||||
}
|
@ -593,16 +593,6 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function() {
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("getStockPurchaseCost()", async function() {
|
||||
const f = ["getStockPurchaseCost"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("getStockSaleGain()", async function() {
|
||||
const f = ["getStockSaleGain"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
});
|
||||
|
||||
it("buyStock()", async function() {
|
||||
const f = ["buyStock"];
|
||||
await testNonzeroDynamicRamCost(f);
|
||||
|
@ -1,926 +0,0 @@
|
||||
import { CONSTANTS } from "../src/Constants";
|
||||
import { Player } from "../src/Player";
|
||||
import {
|
||||
buyStock,
|
||||
sellStock,
|
||||
shortStock,
|
||||
sellShort,
|
||||
} from "../src/StockMarket/BuyingAndSelling";
|
||||
import { Order } from "../src/StockMarket/Order";
|
||||
import { processOrders } from "../src/StockMarket/OrderProcessing";
|
||||
import { Stock } from "../src/StockMarket/Stock";
|
||||
import {
|
||||
deleteStockMarket,
|
||||
initStockMarket,
|
||||
initSymbolToStockMap,
|
||||
loadStockMarket,
|
||||
processStockPrices,
|
||||
StockMarket,
|
||||
SymbolToStockMap,
|
||||
} from "../src/StockMarket/StockMarket";
|
||||
import {
|
||||
calculateIncreasingPriceMovement,
|
||||
calculateDecreasingPriceMovement,
|
||||
forecastChangePerPriceMovement,
|
||||
getBuyTransactionCost,
|
||||
getSellTransactionGain,
|
||||
processBuyTransactionPriceMovement,
|
||||
processSellTransactionPriceMovement,
|
||||
} from "../src/StockMarket/StockMarketHelpers";
|
||||
import { OrderTypes } from "../src/StockMarket/data/OrderTypes"
|
||||
import { PositionTypes } from "../src/StockMarket/data/PositionTypes";
|
||||
|
||||
import { expect } from "chai";
|
||||
|
||||
console.log("Beginning Stock Market Tests");
|
||||
|
||||
describe("Stock Market Tests", function() {
|
||||
const commission = CONSTANTS.StockMarketCommission;
|
||||
|
||||
// Generic Stock object that can be used by each test
|
||||
let stock;
|
||||
const ctorParams = {
|
||||
b: true,
|
||||
initPrice: 10e3,
|
||||
marketCap: 5e9,
|
||||
mv: 1,
|
||||
name: "MockStock",
|
||||
otlkMag: 20,
|
||||
spreadPerc: 1,
|
||||
shareTxForMovement: 5e3,
|
||||
symbol: "mock",
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
function construct() {
|
||||
stock = new Stock(ctorParams);
|
||||
}
|
||||
|
||||
expect(construct).to.not.throw();
|
||||
});
|
||||
|
||||
describe("Stock Class", function() {
|
||||
describe("constructor", function() {
|
||||
it("should have default parameters", function() {
|
||||
let defaultStock;
|
||||
function construct() {
|
||||
defaultStock = new Stock();
|
||||
}
|
||||
|
||||
expect(construct).to.not.throw();
|
||||
expect(defaultStock.name).to.equal("");
|
||||
});
|
||||
|
||||
it("should properly initialize props from parameters", function() {
|
||||
expect(stock.name).to.equal(ctorParams.name);
|
||||
expect(stock.symbol).to.equal(ctorParams.symbol);
|
||||
expect(stock.price).to.equal(ctorParams.initPrice);
|
||||
expect(stock.lastPrice).to.equal(ctorParams.initPrice);
|
||||
expect(stock.b).to.equal(ctorParams.b);
|
||||
expect(stock.mv).to.equal(ctorParams.mv);
|
||||
expect(stock.shareTxForMovement).to.equal(ctorParams.shareTxForMovement);
|
||||
expect(stock.shareTxUntilMovementUp).to.equal(ctorParams.shareTxForMovement);
|
||||
expect(stock.shareTxUntilMovementDown).to.equal(ctorParams.shareTxForMovement);
|
||||
expect(stock.maxShares).to.be.below(stock.totalShares);
|
||||
expect(stock.spreadPerc).to.equal(ctorParams.spreadPerc);
|
||||
expect(stock.priceMovementPerc).to.be.a("number");
|
||||
expect(stock.priceMovementPerc).to.be.at.most(stock.spreadPerc);
|
||||
expect(stock.priceMovementPerc).to.be.at.least(0);
|
||||
});
|
||||
|
||||
it ("should properly initialize props from range-values", function() {
|
||||
let stock;
|
||||
const params = {
|
||||
b: true,
|
||||
initPrice: {
|
||||
max: 10e3,
|
||||
min: 1e3,
|
||||
},
|
||||
marketCap: 5e9,
|
||||
mv: {
|
||||
divisor: 100,
|
||||
max: 150,
|
||||
min: 50,
|
||||
},
|
||||
name: "MockStock",
|
||||
otlkMag: 10,
|
||||
spreadPerc: {
|
||||
divisor: 10,
|
||||
max: 10,
|
||||
min: 1,
|
||||
},
|
||||
shareTxForMovement: {
|
||||
max: 10e3,
|
||||
min: 5e3,
|
||||
},
|
||||
symbol: "mock",
|
||||
};
|
||||
|
||||
function construct() {
|
||||
stock = new Stock(params);
|
||||
}
|
||||
|
||||
expect(construct).to.not.throw();
|
||||
expect(stock.price).to.be.within(params.initPrice.min, params.initPrice.max);
|
||||
expect(stock.mv).to.be.within(params.mv.min / params.mv.divisor, params.mv.max / params.mv.divisor);
|
||||
expect(stock.spreadPerc).to.be.within(params.spreadPerc.min / params.spreadPerc.divisor, params.spreadPerc.max / params.spreadPerc.divisor);
|
||||
expect(stock.shareTxForMovement).to.be.within(params.shareTxForMovement.min, params.shareTxForMovement.max);
|
||||
});
|
||||
|
||||
it("should round the 'totalShare' prop to the nearest 100k", function() {
|
||||
expect(stock.totalShares % 100e3).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#changePrice()", function() {
|
||||
it("should set both the last price and current price properties", function() {
|
||||
const newPrice = 20e3;
|
||||
stock.changePrice(newPrice);
|
||||
expect(stock.lastPrice).to.equal(ctorParams.initPrice);
|
||||
expect(stock.price).to.equal(newPrice);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#getAskPrice()", function() {
|
||||
it("should return the price increased by spread percentage", function() {
|
||||
const perc = stock.spreadPerc / 100;
|
||||
expect(perc).to.be.at.most(1);
|
||||
expect(perc).to.be.at.least(0);
|
||||
|
||||
const expected = stock.price * (1 + perc);
|
||||
expect(stock.getAskPrice()).to.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#getBidPrice()", function() {
|
||||
it("should return the price decreased by spread percentage", function() {
|
||||
const perc = stock.spreadPerc / 100;
|
||||
expect(perc).to.be.at.most(1);
|
||||
expect(perc).to.be.at.least(0);
|
||||
|
||||
const expected = stock.price * (1 - perc);
|
||||
expect(stock.getBidPrice()).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("StockMarket object", function() {
|
||||
describe("Initialization", function() {
|
||||
// Keeps track of initialized stocks. Contains their symbols
|
||||
const stocks = [];
|
||||
|
||||
before(function() {
|
||||
expect(initStockMarket).to.not.throw();
|
||||
expect(initSymbolToStockMap).to.not.throw();
|
||||
});
|
||||
|
||||
it("should have Stock objects", function() {
|
||||
for (const prop in StockMarket) {
|
||||
const stock = StockMarket[prop];
|
||||
if (stock instanceof Stock) {
|
||||
stocks.push(stock.symbol);
|
||||
}
|
||||
}
|
||||
|
||||
// We'll just check that there are some stocks
|
||||
expect(stocks.length).to.be.at.least(1);
|
||||
});
|
||||
|
||||
it("should have an order book in the 'Orders' property", function() {
|
||||
expect(StockMarket).to.have.property("Orders");
|
||||
|
||||
const orderbook = StockMarket["Orders"];
|
||||
for (const symbol of stocks) {
|
||||
const ordersForStock = orderbook[symbol];
|
||||
expect(ordersForStock).to.be.an("array");
|
||||
expect(ordersForStock.length).to.equal(0);
|
||||
}
|
||||
});
|
||||
|
||||
it("should have properties for managing game cycles", function() {
|
||||
expect(StockMarket).to.have.property("storedCycles");
|
||||
expect(StockMarket["storedCycles"]).to.equal(0);
|
||||
expect(StockMarket).to.have.property("lastUpdate");
|
||||
expect(StockMarket["lastUpdate"]).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
// Because 'StockMarket' is a global object, the effects of initialization from
|
||||
// the block above should still stand
|
||||
describe("Deletion", function() {
|
||||
it("should set StockMarket to be an empty object", function() {
|
||||
expect(StockMarket).to.be.an("object").that.is.not.empty;
|
||||
deleteStockMarket();
|
||||
expect(StockMarket).to.be.an("object").that.is.empty;
|
||||
});
|
||||
});
|
||||
|
||||
describe("processStockPrices()", function() {
|
||||
before(function() {
|
||||
deleteStockMarket();
|
||||
initStockMarket();
|
||||
initSymbolToStockMap();
|
||||
});
|
||||
|
||||
it("should store cycles until it actually processes", function() {
|
||||
expect(StockMarket["storedCycles"]).to.equal(0);
|
||||
processStockPrices(10);
|
||||
expect(StockMarket["storedCycles"]).to.equal(10);
|
||||
});
|
||||
|
||||
it("should trigger a price update when it has enough cycles", function() {
|
||||
// Get the initial prices
|
||||
const initialValues = {};
|
||||
for (const stockName in StockMarket) {
|
||||
const stock = StockMarket[stockName];
|
||||
if (!(stock instanceof Stock)) { continue; }
|
||||
initialValues[stock.symbol] = {
|
||||
price: stock.price,
|
||||
otlkMag: stock.otlkMag,
|
||||
}
|
||||
}
|
||||
|
||||
// Don't know or care how many exact cycles are required
|
||||
processStockPrices(1e9);
|
||||
|
||||
// Both price and 'otlkMag' should be different
|
||||
for (const stockName in StockMarket) {
|
||||
const stock = StockMarket[stockName];
|
||||
if (!(stock instanceof Stock)) { continue; }
|
||||
expect(initialValues[stock.symbol].price).to.not.equal(stock.price);
|
||||
expect(initialValues[stock.symbol].otlkMag).to.not.equal(stock.otlkMag);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("StockToSymbolMap", function() {
|
||||
before(function() {
|
||||
deleteStockMarket();
|
||||
initStockMarket();
|
||||
initSymbolToStockMap();
|
||||
});
|
||||
|
||||
it("should map stock symbols to their corresponding Stock Objects", function() {
|
||||
for (const stockName in StockMarket) {
|
||||
const stock = StockMarket[stockName];
|
||||
if (!(stock instanceof Stock)) { continue; }
|
||||
|
||||
expect(SymbolToStockMap[stock.symbol]).to.equal(stock);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("Transaction Cost Calculator Functions", function() {
|
||||
describe("getBuyTransactionCost()", function() {
|
||||
it("should fail on invalid 'stock' argument", function() {
|
||||
const res = getBuyTransactionCost({}, 10, PositionTypes.Long);
|
||||
expect(res).to.equal(null);
|
||||
});
|
||||
|
||||
it("should fail on invalid 'shares' arg", function() {
|
||||
let res = getBuyTransactionCost(stock, NaN, PositionTypes.Long);
|
||||
expect(res).to.equal(null);
|
||||
|
||||
res = getBuyTransactionCost(stock, -1, PositionTypes.Long);
|
||||
expect(res).to.equal(null);
|
||||
});
|
||||
|
||||
it("should properly evaluate LONG transactions that doesn't trigger a price movement", function() {
|
||||
const shares = ctorParams.shareTxForMovement / 2;
|
||||
const res = getBuyTransactionCost(stock, shares, PositionTypes.Long);
|
||||
expect(res).to.equal(shares * stock.getAskPrice() + commission);
|
||||
});
|
||||
|
||||
it("should properly evaluate SHORT transactions that doesn't trigger a price movement", function() {
|
||||
const shares = ctorParams.shareTxForMovement / 2;
|
||||
const res = getBuyTransactionCost(stock, shares, PositionTypes.Short);
|
||||
expect(res).to.equal(shares * stock.getBidPrice() + commission);
|
||||
});
|
||||
|
||||
it("should properly evaluate LONG transactions that trigger price movements", function() {
|
||||
const sharesPerMvmt = ctorParams.shareTxForMovement;
|
||||
const shares = sharesPerMvmt * 3;
|
||||
const res = getBuyTransactionCost(stock, shares, PositionTypes.Long);
|
||||
|
||||
// Calculate expected cost
|
||||
const secondPrice = stock.getAskPrice() * calculateIncreasingPriceMovement(stock);
|
||||
const thirdPrice = secondPrice * calculateIncreasingPriceMovement(stock);
|
||||
let expected = (sharesPerMvmt * stock.getAskPrice()) + (sharesPerMvmt * secondPrice) + (sharesPerMvmt * thirdPrice);
|
||||
|
||||
expect(res).to.equal(expected + commission);
|
||||
});
|
||||
|
||||
it("should properly evaluate SHORT transactions that trigger price movements", function() {
|
||||
const sharesPerMvmt = ctorParams.shareTxForMovement;
|
||||
const shares = sharesPerMvmt * 3;
|
||||
const res = getBuyTransactionCost(stock, shares, PositionTypes.Short);
|
||||
|
||||
// Calculate expected cost
|
||||
const secondPrice = stock.getBidPrice() * calculateDecreasingPriceMovement(stock);
|
||||
const thirdPrice = secondPrice * calculateDecreasingPriceMovement(stock);
|
||||
let expected = (sharesPerMvmt * stock.getBidPrice()) + (sharesPerMvmt * secondPrice) + (sharesPerMvmt * thirdPrice);
|
||||
|
||||
expect(res).to.equal(expected + commission);
|
||||
});
|
||||
|
||||
it("should cap the 'shares' argument at the stock's maximum number of shares", function() {
|
||||
const maxRes = getBuyTransactionCost(stock, stock.maxShares, PositionTypes.Long);
|
||||
const exceedRes = getBuyTransactionCost(stock, stock.maxShares * 10, PositionTypes.Long);
|
||||
expect(maxRes).to.equal(exceedRes);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getSellTransactionGain()", function() {
|
||||
it("should fail on invalid 'stock' argument", function() {
|
||||
const res = getSellTransactionGain({}, 10, PositionTypes.Long);
|
||||
expect(res).to.equal(null);
|
||||
});
|
||||
|
||||
it("should fail on invalid 'shares' arg", function() {
|
||||
let res = getSellTransactionGain(stock, NaN, PositionTypes.Long);
|
||||
expect(res).to.equal(null);
|
||||
|
||||
res = getSellTransactionGain(stock, -1, PositionTypes.Long);
|
||||
expect(res).to.equal(null);
|
||||
});
|
||||
|
||||
it("should properly evaluate LONG transactions that doesn't trigger a price movement", function() {
|
||||
const shares = ctorParams.shareTxForMovement / 2;
|
||||
const res = getSellTransactionGain(stock, shares, PositionTypes.Long);
|
||||
const expected = shares * stock.getBidPrice() - commission;
|
||||
expect(res).to.equal(expected);
|
||||
});
|
||||
|
||||
it("should properly evaluate SHORT transactions that doesn't trigger a price movement", function() {
|
||||
// We need to set this property in order to calculate gains from short position
|
||||
stock.playerAvgShortPx = stock.price * 2;
|
||||
|
||||
const shares = ctorParams.shareTxForMovement / 2;
|
||||
const res = getSellTransactionGain(stock, shares, PositionTypes.Short);
|
||||
const expected = (shares * stock.playerAvgShortPx) + (shares * (stock.playerAvgShortPx - stock.getAskPrice())) - commission;
|
||||
expect(res).to.equal(expected);
|
||||
});
|
||||
|
||||
it("should properly evaluate LONG transactions that trigger price movements", function() {
|
||||
const sharesPerMvmt = ctorParams.shareTxForMovement;
|
||||
const shares = sharesPerMvmt * 3;
|
||||
const res = getSellTransactionGain(stock, shares, PositionTypes.Long);
|
||||
|
||||
// Calculated expected gain
|
||||
const mvmt = calculateDecreasingPriceMovement(stock);
|
||||
const secondPrice = stock.getBidPrice() * mvmt;
|
||||
const thirdPrice = secondPrice * mvmt;
|
||||
const expected = (sharesPerMvmt * stock.getBidPrice()) + (sharesPerMvmt * secondPrice) + (sharesPerMvmt * thirdPrice);
|
||||
|
||||
expect(res).to.equal(expected - commission);
|
||||
});
|
||||
|
||||
it("should properly evaluate SHORT transactions that trigger price movements", function() {
|
||||
// We need to set this property in order to calculate gains from short position
|
||||
stock.playerAvgShortPx = stock.price * 2;
|
||||
|
||||
const sharesPerMvmt = ctorParams.shareTxForMovement;
|
||||
const shares = sharesPerMvmt * 3;
|
||||
const res = getSellTransactionGain(stock, shares, PositionTypes.Short);
|
||||
|
||||
// Calculate expected gain
|
||||
const mvmt = calculateIncreasingPriceMovement(stock);
|
||||
const secondPrice = stock.getAskPrice() * mvmt;
|
||||
const thirdPrice = secondPrice * mvmt;
|
||||
function getGainForPrice(thisPrice) {
|
||||
const origCost = sharesPerMvmt * stock.playerAvgShortPx;
|
||||
return origCost + ((stock.playerAvgShortPx - thisPrice) * sharesPerMvmt);
|
||||
}
|
||||
const expected = getGainForPrice(stock.getAskPrice()) + getGainForPrice(secondPrice) + getGainForPrice(thirdPrice);
|
||||
|
||||
expect(res).to.equal(expected - commission);
|
||||
});
|
||||
|
||||
it("should cap the 'shares' argument at the stock's maximum number of shares", function() {
|
||||
const maxRes = getSellTransactionGain(stock, stock.maxShares, PositionTypes.Long);
|
||||
const exceedRes = getSellTransactionGain(stock, stock.maxShares * 10, PositionTypes.Long);
|
||||
expect(maxRes).to.equal(exceedRes);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Price Movement Processor Functions", function() {
|
||||
// N = 1 is the original price
|
||||
function getNthPriceIncreasing(origPrice, n) {
|
||||
let price = origPrice;
|
||||
for (let i = 1; i < n; ++i) {
|
||||
price *= calculateIncreasingPriceMovement(stock);
|
||||
}
|
||||
|
||||
return price;
|
||||
}
|
||||
|
||||
// N = 1 is the original price
|
||||
function getNthPriceDecreasing(origPrice, n) {
|
||||
let price = origPrice;
|
||||
for (let i = 1; i < n; ++i) {
|
||||
price *= calculateDecreasingPriceMovement(stock);
|
||||
}
|
||||
|
||||
return price;
|
||||
}
|
||||
|
||||
// N = 1 is the original forecast
|
||||
function getNthForecast(origForecast, n) {
|
||||
return origForecast - forecastChangePerPriceMovement * (n - 1);
|
||||
}
|
||||
|
||||
describe("processBuyTransactionPriceMovement()", function() {
|
||||
const noMvmtShares = Math.round(ctorParams.shareTxForMovement / 2.2);
|
||||
const mvmtShares = ctorParams.shareTxForMovement * 3 + noMvmtShares;
|
||||
|
||||
it("should do nothing on invalid 'stock' argument", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldTracker = stock.shareTxUntilMovementUp;
|
||||
|
||||
processBuyTransactionPriceMovement({}, mvmtShares, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(oldPrice);
|
||||
expect(stock.shareTxUntilMovementUp).to.equal(oldTracker);
|
||||
});
|
||||
|
||||
it("should do nothing on invalid 'shares' arg", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldTracker = stock.shareTxUntilMovementUp;
|
||||
|
||||
processBuyTransactionPriceMovement(stock, NaN, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(oldPrice);
|
||||
expect(stock.shareTxUntilMovementUp).to.equal(oldTracker);
|
||||
|
||||
processBuyTransactionPriceMovement(stock, -1, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(oldPrice);
|
||||
expect(stock.shareTxUntilMovementUp).to.equal(oldTracker);
|
||||
});
|
||||
|
||||
it("should properly evaluate a LONG transaction that doesn't trigger a price movement", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldForecast = stock.otlkMag;
|
||||
|
||||
processBuyTransactionPriceMovement(stock, noMvmtShares, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(oldPrice);
|
||||
expect(stock.otlkMag).to.equal(oldForecast);
|
||||
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement - noMvmtShares);
|
||||
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
|
||||
});
|
||||
|
||||
it("should properly evaluate a SHORT transaction that doesn't trigger a price movement", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldForecast = stock.otlkMag;
|
||||
|
||||
processBuyTransactionPriceMovement(stock, noMvmtShares, PositionTypes.Short);
|
||||
expect(stock.price).to.equal(oldPrice);
|
||||
expect(stock.otlkMag).to.equal(oldForecast);
|
||||
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement - noMvmtShares);
|
||||
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
|
||||
});
|
||||
|
||||
it("should properly evaluate LONG transactions that trigger price movements", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldForecast = stock.otlkMag;
|
||||
|
||||
processBuyTransactionPriceMovement(stock, mvmtShares, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(getNthPriceIncreasing(oldPrice, 4));
|
||||
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
|
||||
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement - noMvmtShares);
|
||||
});
|
||||
|
||||
it("should properly evaluate SHORT transactions that trigger price movements", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldForecast = stock.otlkMag;
|
||||
|
||||
processBuyTransactionPriceMovement(stock, mvmtShares, PositionTypes.Short);
|
||||
expect(stock.price).to.equal(getNthPriceDecreasing(oldPrice, 4));
|
||||
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
|
||||
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement - noMvmtShares);
|
||||
});
|
||||
|
||||
it("should properly evaluate LONG transactions of exactly 'shareTxForMovement' shares", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldForecast = stock.otlkMag;
|
||||
|
||||
processBuyTransactionPriceMovement(stock, stock.shareTxForMovement, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(getNthPriceIncreasing(oldPrice, 2));
|
||||
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
|
||||
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
|
||||
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
|
||||
});
|
||||
|
||||
it("should properly evaluate LONG transactions that total to 'shareTxForMovement' shares", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldForecast = stock.otlkMag;
|
||||
|
||||
processBuyTransactionPriceMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Long);
|
||||
processBuyTransactionPriceMovement(stock, stock.shareTxUntilMovementUp, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(getNthPriceIncreasing(oldPrice, 2));
|
||||
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
|
||||
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
|
||||
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
|
||||
});
|
||||
|
||||
it("should properly evaluate LONG transactions that are a multiple of 'shareTxForMovement' shares", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldForecast = stock.otlkMag;
|
||||
|
||||
processBuyTransactionPriceMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(getNthPriceIncreasing(oldPrice, 4));
|
||||
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
|
||||
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
|
||||
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
|
||||
});
|
||||
|
||||
it("should properly evaluate SHORT transactions of exactly 'shareTxForMovement' shares", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldForecast = stock.otlkMag;
|
||||
|
||||
processBuyTransactionPriceMovement(stock, stock.shareTxForMovement, PositionTypes.Short);
|
||||
expect(stock.price).to.equal(getNthPriceDecreasing(oldPrice, 2));
|
||||
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
|
||||
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
|
||||
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
|
||||
});
|
||||
|
||||
it("should properly evaluate SHORT transactions that total to 'shareTxForMovement' shares", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldForecast = stock.otlkMag;
|
||||
|
||||
processBuyTransactionPriceMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Short);
|
||||
expect(stock.shareTxUntilMovementDown).to.be.below(stock.shareTxForMovement);
|
||||
processBuyTransactionPriceMovement(stock, stock.shareTxUntilMovementDown, PositionTypes.Short);
|
||||
expect(stock.price).to.equal(getNthPriceDecreasing(oldPrice, 2));
|
||||
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
|
||||
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
|
||||
});
|
||||
|
||||
it("should properly evaluate SHORT transactions that are a multiple of 'shareTxForMovement' shares", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldForecast = stock.otlkMag;
|
||||
|
||||
processBuyTransactionPriceMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Short);
|
||||
expect(stock.price).to.equal(getNthPriceDecreasing(oldPrice, 4));
|
||||
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
|
||||
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
|
||||
});
|
||||
});
|
||||
|
||||
describe("processSellTransactionPriceMovement()", function() {
|
||||
const noMvmtShares = Math.round(ctorParams.shareTxForMovement / 2.2);
|
||||
const mvmtShares = ctorParams.shareTxForMovement * 3 + noMvmtShares;
|
||||
|
||||
it("should do nothing on invalid 'stock' argument", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldTracker = stock.shareTxUntilMovementDown;
|
||||
|
||||
processSellTransactionPriceMovement({}, mvmtShares, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(oldPrice);
|
||||
expect(stock.shareTxUntilMovementDown).to.equal(oldTracker);
|
||||
});
|
||||
|
||||
it("should do nothing on invalid 'shares' arg", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldTracker = stock.shareTxUntilMovementDown;
|
||||
|
||||
processSellTransactionPriceMovement(stock, NaN, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(oldPrice);
|
||||
expect(stock.shareTxUntilMovementDown).to.equal(oldTracker);
|
||||
|
||||
processSellTransactionPriceMovement(stock, -1, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(oldPrice);
|
||||
expect(stock.shareTxUntilMovementDown).to.equal(oldTracker);
|
||||
});
|
||||
|
||||
it("should properly evaluate a LONG transaction that doesn't trigger a price movement", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldForecast = stock.otlkMag;
|
||||
|
||||
processSellTransactionPriceMovement(stock, noMvmtShares, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(oldPrice);
|
||||
expect(stock.otlkMag).to.equal(oldForecast);
|
||||
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement - noMvmtShares);
|
||||
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
|
||||
});
|
||||
|
||||
it("should properly evaluate a SHORT transaction that doesn't trigger a price movement", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldForecast = stock.otlkMag;
|
||||
|
||||
processSellTransactionPriceMovement(stock, noMvmtShares, PositionTypes.Short);
|
||||
expect(stock.price).to.equal(oldPrice);
|
||||
expect(stock.otlkMag).to.equal(oldForecast);
|
||||
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement - noMvmtShares);
|
||||
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
|
||||
});
|
||||
|
||||
it("should properly evaluate LONG transactions that trigger price movements", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldForecast = stock.otlkMag;
|
||||
|
||||
processSellTransactionPriceMovement(stock, mvmtShares, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(getNthPriceDecreasing(oldPrice, 4));
|
||||
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
|
||||
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement - noMvmtShares);
|
||||
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
|
||||
});
|
||||
|
||||
it("should properly evaluate SHORT transactions that trigger price movements", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldForecast = stock.otlkMag;
|
||||
|
||||
processSellTransactionPriceMovement(stock, mvmtShares, PositionTypes.Short);
|
||||
expect(stock.price).to.equal(getNthPriceIncreasing(oldPrice, 4));
|
||||
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
|
||||
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement - noMvmtShares);
|
||||
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
|
||||
});
|
||||
|
||||
it("should properly evaluate LONG transactions of exactly 'shareTxForMovement' shares", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldForecast = stock.otlkMag;
|
||||
|
||||
processSellTransactionPriceMovement(stock, stock.shareTxForMovement, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(getNthPriceDecreasing(oldPrice, 2));
|
||||
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
|
||||
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
|
||||
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
|
||||
});
|
||||
|
||||
it("should properly evaluate LONG transactions that total to 'shareTxForMovement' shares", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldForecast = stock.otlkMag;
|
||||
|
||||
processSellTransactionPriceMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Long);
|
||||
expect(stock.shareTxUntilMovementDown).to.be.below(stock.shareTxForMovement);
|
||||
processSellTransactionPriceMovement(stock, stock.shareTxUntilMovementDown, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(getNthPriceDecreasing(oldPrice, 2));
|
||||
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
|
||||
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
|
||||
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
|
||||
});
|
||||
|
||||
it("should properly evaluate LONG transactions that are a multiple of 'shareTxForMovement' shares", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldForecast = stock.otlkMag;
|
||||
|
||||
processSellTransactionPriceMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Long);
|
||||
expect(stock.price).to.equal(getNthPriceDecreasing(oldPrice, 4));
|
||||
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
|
||||
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
|
||||
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
|
||||
});
|
||||
|
||||
it("should properly evaluate SHORT transactions of exactly 'shareTxForMovement' shares", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldForecast = stock.otlkMag;
|
||||
|
||||
processSellTransactionPriceMovement(stock, stock.shareTxForMovement, PositionTypes.Short);
|
||||
expect(stock.price).to.equal(getNthPriceIncreasing(oldPrice, 2));
|
||||
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
|
||||
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
|
||||
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
|
||||
});
|
||||
|
||||
it("should properly evaluate SHORT transactions that total to 'shareTxForMovement' shares", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldForecast = stock.otlkMag;
|
||||
|
||||
processSellTransactionPriceMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Short);
|
||||
expect(stock.shareTxUntilMovementUp).to.be.below(stock.shareTxForMovement);
|
||||
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
|
||||
processSellTransactionPriceMovement(stock, stock.shareTxUntilMovementUp, PositionTypes.Short);
|
||||
expect(stock.price).to.equal(getNthPriceIncreasing(oldPrice, 2));
|
||||
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
|
||||
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
|
||||
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
|
||||
});
|
||||
|
||||
it("should properly evaluate SHORT transactions that are a multiple of 'shareTxForMovement' shares", function() {
|
||||
const oldPrice = stock.price;
|
||||
const oldForecast = stock.otlkMag;
|
||||
|
||||
processSellTransactionPriceMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Short);
|
||||
expect(stock.price).to.equal(getNthPriceIncreasing(oldPrice, 4));
|
||||
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
|
||||
expect(stock.shareTxUntilMovementUp).to.equal(stock.shareTxForMovement);
|
||||
expect(stock.shareTxUntilMovementDown).to.equal(stock.shareTxForMovement);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Transaction (Buy/Sell) Functions", function() {
|
||||
const suppressDialogOpt = { suppressDialog: true };
|
||||
|
||||
describe("buyStock()", function() {
|
||||
it("should fail for invalid arguments", function() {
|
||||
expect(buyStock({}, 1, null, suppressDialogOpt)).to.equal(false);
|
||||
expect(buyStock(stock, 0, null, suppressDialogOpt)).to.equal(false);
|
||||
expect(buyStock(stock, -1, null, suppressDialogOpt)).to.equal(false);
|
||||
expect(buyStock(stock, NaN, null, suppressDialogOpt)).to.equal(false);
|
||||
});
|
||||
|
||||
it("should fail if player doesn't have enough money", function() {
|
||||
Player.setMoney(0);
|
||||
expect(buyStock(stock, 1, null, suppressDialogOpt)).to.equal(false);
|
||||
});
|
||||
|
||||
it("should not allow for transactions that exceed the maximum shares", function() {
|
||||
const maxShares = stock.maxShares;
|
||||
expect(buyStock(stock, maxShares + 1, null, suppressDialogOpt)).to.equal(false);
|
||||
});
|
||||
|
||||
it("should return true and properly update stock properties for successful transactions", function() {
|
||||
const shares = 1e3;
|
||||
const cost = getBuyTransactionCost(stock, shares, PositionTypes.Long);
|
||||
Player.setMoney(cost);
|
||||
|
||||
expect(buyStock(stock, shares, null, suppressDialogOpt)).to.equal(true);
|
||||
expect(stock.playerShares).to.equal(shares);
|
||||
expect(stock.playerAvgPx).to.be.above(0);
|
||||
expect(Player.money.toNumber()).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("sellStock()", function() {
|
||||
it("should fail for invalid arguments", function() {
|
||||
expect(sellStock({}, 1, null, suppressDialogOpt)).to.equal(false);
|
||||
expect(sellStock(stock, 0, null, suppressDialogOpt)).to.equal(false);
|
||||
expect(sellStock(stock, -1, null, suppressDialogOpt)).to.equal(false);
|
||||
expect(sellStock(stock, NaN, null, suppressDialogOpt)).to.equal(false);
|
||||
});
|
||||
|
||||
it("should fail if player doesn't have any shares", function() {
|
||||
Player.setMoney(0);
|
||||
expect(sellStock(stock, 1, null, suppressDialogOpt)).to.equal(false);
|
||||
});
|
||||
|
||||
it("should not allow for transactions that exceed the maximum shares", function() {
|
||||
const maxShares = stock.maxShares;
|
||||
expect(sellStock(stock, maxShares + 1, null, suppressDialogOpt)).to.equal(false);
|
||||
});
|
||||
|
||||
it("should return true and properly update stock properties for successful transactions", function() {
|
||||
const shares = 1e3;
|
||||
stock.playerShares = shares;
|
||||
stock.playerAvgPx = stock.price;
|
||||
const gain = getSellTransactionGain(stock, shares, PositionTypes.Long);
|
||||
Player.setMoney(0);
|
||||
|
||||
expect(sellStock(stock, shares, null, suppressDialogOpt)).to.equal(true);
|
||||
expect(stock.playerShares).to.equal(0);
|
||||
expect(stock.playerAvgPx).to.equal(0);
|
||||
expect(Player.money.toNumber()).to.equal(gain);
|
||||
});
|
||||
|
||||
it("should cap the number of sharse sold to however many the player owns", function() {
|
||||
const attemptedShares = 2e3;
|
||||
const actualShares = 1e3;
|
||||
stock.playerShares = actualShares;
|
||||
stock.playerAvgPx = stock.price;
|
||||
const gain = getSellTransactionGain(stock, actualShares, PositionTypes.Long);
|
||||
Player.setMoney(0);
|
||||
|
||||
expect(sellStock(stock, attemptedShares, null, suppressDialogOpt)).to.equal(true);
|
||||
expect(stock.playerShares).to.equal(0);
|
||||
expect(stock.playerAvgPx).to.equal(0);
|
||||
expect(Player.money.toNumber()).to.equal(gain);
|
||||
});
|
||||
|
||||
it("should properly update stock properties for partial transactions", function() {
|
||||
const shares = 1e3;
|
||||
const origPrice = stock.price;
|
||||
stock.playerShares = 2 * shares;
|
||||
stock.playerAvgPx = origPrice;
|
||||
const gain = getSellTransactionGain(stock, shares, PositionTypes.Long);
|
||||
Player.setMoney(0);
|
||||
|
||||
expect(sellStock(stock, shares, null, suppressDialogOpt)).to.equal(true);
|
||||
expect(stock.playerShares).to.equal(shares);
|
||||
expect(stock.playerAvgPx).to.equal(origPrice);
|
||||
expect(Player.money.toNumber()).to.equal(gain);
|
||||
});
|
||||
});
|
||||
|
||||
describe("shortStock()", function() {
|
||||
it("should fail for invalid arguments", function() {
|
||||
expect(shortStock({}, 1, null, suppressDialogOpt)).to.equal(false);
|
||||
expect(shortStock(stock, 0, null, suppressDialogOpt)).to.equal(false);
|
||||
expect(shortStock(stock, -1, null, suppressDialogOpt)).to.equal(false);
|
||||
expect(shortStock(stock, NaN, null, suppressDialogOpt)).to.equal(false);
|
||||
});
|
||||
|
||||
it("should fail if player doesn't have enough money", function() {
|
||||
Player.setMoney(0);
|
||||
expect(shortStock(stock, 1, null, suppressDialogOpt)).to.equal(false);
|
||||
});
|
||||
|
||||
it("should not allow for transactions that exceed the maximum shares", function() {
|
||||
const maxShares = stock.maxShares;
|
||||
expect(shortStock(stock, maxShares + 1, null, suppressDialogOpt)).to.equal(false);
|
||||
});
|
||||
|
||||
it("should return true and properly update stock properties for successful transactions", function() {
|
||||
const shares = 1e3;
|
||||
const cost = getBuyTransactionCost(stock, shares, PositionTypes.Short);
|
||||
Player.setMoney(cost);
|
||||
|
||||
expect(shortStock(stock, shares, null, suppressDialogOpt)).to.equal(true);
|
||||
expect(stock.playerShortShares).to.equal(shares);
|
||||
expect(stock.playerAvgShortPx).to.be.above(0);
|
||||
expect(Player.money.toNumber()).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("sellShort()", function() {
|
||||
it("should fail for invalid arguments", function() {
|
||||
expect(sellShort({}, 1, null, suppressDialogOpt)).to.equal(false);
|
||||
expect(sellShort(stock, 0, null, suppressDialogOpt)).to.equal(false);
|
||||
expect(sellShort(stock, -1, null, suppressDialogOpt)).to.equal(false);
|
||||
expect(sellShort(stock, NaN, null, suppressDialogOpt)).to.equal(false);
|
||||
});
|
||||
|
||||
it("should fail if player doesn't have any shares", function() {
|
||||
Player.setMoney(0);
|
||||
expect(sellShort(stock, 1, null, suppressDialogOpt)).to.equal(false);
|
||||
});
|
||||
|
||||
it("should not allow for transactions that exceed the maximum shares", function() {
|
||||
const maxShares = stock.maxShares;
|
||||
expect(sellShort(stock, maxShares + 1, null, suppressDialogOpt)).to.equal(false);
|
||||
});
|
||||
|
||||
it("should return true and properly update stock properties for successful transactions", function() {
|
||||
const shares = 1e3;
|
||||
stock.playerShortShares = shares;
|
||||
stock.playerAvgShortPx = stock.price;
|
||||
const gain = getSellTransactionGain(stock, shares, PositionTypes.Short);
|
||||
Player.setMoney(0);
|
||||
|
||||
expect(sellShort(stock, shares, null, suppressDialogOpt)).to.equal(true);
|
||||
expect(stock.playerShortShares).to.equal(0);
|
||||
expect(stock.playerAvgShortPx).to.equal(0);
|
||||
expect(Player.money.toNumber()).to.equal(gain);
|
||||
});
|
||||
|
||||
it("should cap the number of sharse sold to however many the player owns", function() {
|
||||
const attemptedShares = 2e3;
|
||||
const actualShares = 1e3;
|
||||
stock.playerShortShares = actualShares;
|
||||
stock.playerAvgShortPx = stock.price;
|
||||
const gain = getSellTransactionGain(stock, actualShares, PositionTypes.Short);
|
||||
Player.setMoney(0);
|
||||
|
||||
expect(sellShort(stock, attemptedShares, null, suppressDialogOpt)).to.equal(true);
|
||||
expect(stock.playerShortShares).to.equal(0);
|
||||
expect(stock.playerAvgShortPx).to.equal(0);
|
||||
expect(Player.money.toNumber()).to.equal(gain);
|
||||
});
|
||||
|
||||
it("should properly update stock properties for partial transactions", function() {
|
||||
const shares = 1e3;
|
||||
const origPrice = stock.price;
|
||||
stock.playerShortShares = 2 * shares;
|
||||
stock.playerAvgShortPx = origPrice;
|
||||
const gain = getSellTransactionGain(stock, shares, PositionTypes.Short);
|
||||
Player.setMoney(0);
|
||||
|
||||
expect(sellShort(stock, shares, null, suppressDialogOpt)).to.equal(true);
|
||||
expect(stock.playerShortShares).to.equal(shares);
|
||||
expect(stock.playerAvgShortPx).to.equal(origPrice);
|
||||
expect(Player.money.toNumber()).to.equal(gain);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Order Class", function() {
|
||||
it("should throw on invalid arguments", function() {
|
||||
function invalid1() {
|
||||
return new Order({}, 1, 1, OrderTypes.LimitBuy, PositionTypes.Long);
|
||||
}
|
||||
function invalid2() {
|
||||
return new Order("FOO", "z", 0, OrderTypes.LimitBuy, PositionTypes.Short);
|
||||
}
|
||||
function invalid3() {
|
||||
return new Order("FOO", 1, {}, OrderTypes.LimitBuy, PositionTypes.Short);
|
||||
}
|
||||
function invalid4() {
|
||||
return new Order("FOO", 1, NaN, OrderTypes.LimitBuy, PositionTypes.Short);
|
||||
}
|
||||
function invalid5() {
|
||||
return new Order("FOO", NaN, 0, OrderTypes.LimitBuy, PositionTypes.Short);
|
||||
}
|
||||
|
||||
expect(invalid1).to.throw();
|
||||
expect(invalid2).to.throw();
|
||||
expect(invalid3).to.throw();
|
||||
expect(invalid4).to.throw();
|
||||
expect(invalid5).to.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Order Processing", function() {
|
||||
|
||||
});
|
||||
});
|
1310
test/StockMarketTests.ts
Normal file
1310
test/StockMarketTests.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,5 @@
|
||||
import { KEY } from "./helpers/keyCodes";
|
||||
|
||||
/**
|
||||
* Create and display a pop-up dialog box.
|
||||
* This dialog box does not allow for any interaction and should close when clicking
|
||||
@ -9,28 +11,31 @@ let dialogBoxes = [];
|
||||
$(document).click(function(event) {
|
||||
if (dialogBoxOpened && dialogBoxes.length >= 1) {
|
||||
if (!$(event.target).closest(dialogBoxes[0]).length){
|
||||
dialogBoxes[0].remove();
|
||||
dialogBoxes.splice(0, 1);
|
||||
if (dialogBoxes.length == 0) {
|
||||
dialogBoxOpened = false;
|
||||
} else {
|
||||
dialogBoxes[0].style.visibility = "visible";
|
||||
}
|
||||
closeTopmostDialogBox();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Dialog box close buttons
|
||||
$(document).on('click', '.dialog-box-close-button', function( event ) {
|
||||
if (dialogBoxOpened && dialogBoxes.length >= 1) {
|
||||
function closeTopmostDialogBox() {
|
||||
if (!dialogBoxOpened || dialogBoxes.length === 0) return;
|
||||
dialogBoxes[0].remove();
|
||||
dialogBoxes.splice(0, 1);
|
||||
dialogBoxes.shift();
|
||||
if (dialogBoxes.length == 0) {
|
||||
dialogBoxOpened = false;
|
||||
} else {
|
||||
dialogBoxes[0].style.visibility = "visible";
|
||||
}
|
||||
}
|
||||
|
||||
// Dialog box close buttons
|
||||
$(document).on('click', '.dialog-box-close-button', function( event ) {
|
||||
closeTopmostDialogBox();
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", function (event) {
|
||||
if (event.keyCode == KEY.ESC && dialogBoxOpened) {
|
||||
closeTopmostDialogBox();
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,68 +0,0 @@
|
||||
import {killWorkerScript} from "../src/NetscriptWorker";
|
||||
import {clearEventListeners} from "./uiHelpers/clearEventListeners";
|
||||
import {arrayToString} from "./helpers/arrayToString";
|
||||
|
||||
$(document).keydown(function(event) {
|
||||
if (logBoxOpened && event.keyCode == 27) {
|
||||
logBoxClose();
|
||||
}
|
||||
});
|
||||
|
||||
function logBoxInit() {
|
||||
var closeButton = document.getElementById("log-box-close");
|
||||
logBoxClose();
|
||||
|
||||
//Close Dialog box
|
||||
closeButton.addEventListener("click", function() {
|
||||
logBoxClose();
|
||||
return false;
|
||||
});
|
||||
document.getElementById("log-box-text-header").style.display = "inline-block";
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", logBoxInit, false);
|
||||
|
||||
function logBoxClose() {
|
||||
logBoxOpened = false;
|
||||
var logBox = document.getElementById("log-box-container");
|
||||
logBox.style.display = "none";
|
||||
}
|
||||
|
||||
function logBoxOpen() {
|
||||
logBoxOpened = true;
|
||||
|
||||
var logBox = document.getElementById("log-box-container");
|
||||
logBox.style.display = "block";
|
||||
}
|
||||
|
||||
|
||||
var logBoxOpened = false;
|
||||
var logBoxCurrentScript = null;
|
||||
function logBoxCreate(script) {
|
||||
logBoxCurrentScript = script;
|
||||
var killScriptBtn = clearEventListeners("log-box-kill-script");
|
||||
killScriptBtn.addEventListener("click", ()=>{
|
||||
killWorkerScript(script, script.server);
|
||||
return false;
|
||||
});
|
||||
document.getElementById('log-box-kill-script').style.display = "inline-block";
|
||||
logBoxOpen();
|
||||
document.getElementById("log-box-text-header").innerHTML =
|
||||
logBoxCurrentScript.filename + " " + arrayToString(logBoxCurrentScript.args) + ":<br><br>";
|
||||
logBoxCurrentScript.logUpd = true;
|
||||
logBoxUpdateText();
|
||||
}
|
||||
|
||||
function logBoxUpdateText() {
|
||||
var txt = document.getElementById("log-box-text");
|
||||
if (logBoxCurrentScript && logBoxOpened && txt && logBoxCurrentScript.logUpd) {
|
||||
txt.innerHTML = "";
|
||||
for (var i = 0; i < logBoxCurrentScript.logs.length; ++i) {
|
||||
txt.innerHTML += logBoxCurrentScript.logs[i];
|
||||
txt.innerHTML += "<br>";
|
||||
}
|
||||
logBoxCurrentScript.logUpd = false;
|
||||
}
|
||||
}
|
||||
|
||||
export {logBoxCreate, logBoxUpdateText, logBoxOpened, logBoxCurrentScript};
|
106
utils/LogBox.ts
Normal file
106
utils/LogBox.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import { killWorkerScript } from "../src/Netscript/killWorkerScript";
|
||||
import { RunningScript } from "../src/Script/RunningScript";
|
||||
|
||||
import { clearEventListeners } from "./uiHelpers/clearEventListeners";
|
||||
import { arrayToString } from "./helpers/arrayToString";
|
||||
|
||||
import { KEY } from "./helpers/keyCodes";
|
||||
|
||||
document.addEventListener("keydown", function(event: KeyboardEvent) {
|
||||
if (logBoxOpened && event.keyCode == KEY.ESC) {
|
||||
logBoxClose();
|
||||
}
|
||||
});
|
||||
|
||||
let logBoxContainer: HTMLElement | null;
|
||||
let textHeader: HTMLElement | null;
|
||||
let logText: HTMLElement | null;
|
||||
|
||||
function logBoxInit(): void {
|
||||
// Initialize Close button click listener
|
||||
const closeButton = document.getElementById("log-box-close");
|
||||
if (closeButton == null) {
|
||||
console.error(`Could not find LogBox's close button`);
|
||||
return;
|
||||
}
|
||||
|
||||
closeButton.addEventListener("click", function() {
|
||||
logBoxClose();
|
||||
return false;
|
||||
});
|
||||
|
||||
// Initialize text header
|
||||
textHeader = document.getElementById("log-box-text-header");
|
||||
if (textHeader instanceof HTMLElement) {
|
||||
textHeader.style.display = "inline-block";
|
||||
}
|
||||
|
||||
// Initialize references to other DOM elements
|
||||
logBoxContainer = document.getElementById("log-box-container");
|
||||
logText = document.getElementById("log-box-text");
|
||||
|
||||
logBoxClose();
|
||||
|
||||
document.removeEventListener("DOMContentLoaded", logBoxInit);
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", logBoxInit);
|
||||
|
||||
function logBoxClose() {
|
||||
logBoxOpened = false;
|
||||
if (logBoxContainer instanceof HTMLElement) {
|
||||
logBoxContainer.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
function logBoxOpen() {
|
||||
logBoxOpened = true;
|
||||
|
||||
if (logBoxContainer instanceof HTMLElement) {
|
||||
logBoxContainer.style.display = "block";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export let logBoxOpened = false;
|
||||
let logBoxCurrentScript: RunningScript | null = null;
|
||||
export function logBoxCreate(script: RunningScript) {
|
||||
logBoxCurrentScript = script;
|
||||
|
||||
const killScriptBtn = clearEventListeners("log-box-kill-script");
|
||||
if (killScriptBtn == null) {
|
||||
console.error(`Could not find LogBox's 'Kill Script' button`);
|
||||
return;
|
||||
}
|
||||
|
||||
killScriptBtn.addEventListener("click", () => {
|
||||
killWorkerScript(script, script.server, true);
|
||||
return false;
|
||||
});
|
||||
|
||||
killScriptBtn.style.display = "inline-block";
|
||||
|
||||
logBoxOpen();
|
||||
|
||||
if (textHeader instanceof HTMLElement) {
|
||||
textHeader.innerHTML = `${logBoxCurrentScript.filename} ${arrayToString(logBoxCurrentScript.args)}:<br><br>`;
|
||||
} else {
|
||||
console.warn(`LogBox's Text Header DOM element is null`);
|
||||
}
|
||||
|
||||
logBoxCurrentScript.logUpd = true;
|
||||
logBoxUpdateText();
|
||||
}
|
||||
|
||||
export function logBoxUpdateText() {
|
||||
if (!(logText instanceof HTMLElement)) { return; }
|
||||
|
||||
if (logBoxCurrentScript && logBoxOpened && logBoxCurrentScript.logUpd) {
|
||||
logText.innerHTML = "";
|
||||
for (let i = 0; i < logBoxCurrentScript.logs.length; ++i) {
|
||||
logText.innerHTML += logBoxCurrentScript.logs[i];
|
||||
logText.innerHTML += "<br>";
|
||||
}
|
||||
logBoxCurrentScript.logUpd = false;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user