Compare commits

...

105 Commits

Author SHA1 Message Date
danielyxie
433b399de9 v0.47.1 2019-06-28 09:28:08 -07:00
danielyxie
58d04c0cbb Updated documentation changelog 2019-06-27 00:01:46 -07:00
danielyxie
e3a74f23a1 ps and top Terminal commands now show script pid. Updated version and changelog 2019-06-27 00:01:06 -07:00
danielyxie
3a374de210 killWorkerScript() now takes an optional argument for whether to rerenderUI. This is used to batch UI updates on killall() 2019-06-24 22:48:54 -07:00
danielyxie
4cc6437408 Updated WorkerScript-related code for the workerScripts array->map change 2019-06-24 22:48:54 -07:00
danielyxie
821725cf4d Initial commit for converting workerScripts pool to Map data structure 2019-06-24 22:48:54 -07:00
danielyxie
931de230ae Minor rebalancing to stock market. Updated documentation and tests for recent changes 2019-06-11 00:18:14 -07:00
danielyxie
7301946236 Added and Updated Stock Market tests for the new changes 2019-06-09 21:23:48 -07:00
danielyxie
a15041da75 More rebalancing for stock market changes. Transactions now affect second-order forecast (very slightly) 2019-06-09 15:12:33 -07:00
danielyxie
00f8c0a51f Removed debug stuff for beta branch 2019-06-04 21:31:45 -07:00
danielyxie
63483837bc Fixed version in changelog 2019-06-03 23:10:10 -07:00
danielyxie
dc5f4e6694 Updated documentation for new Stock Market changes. More rebalancing for recent stock market changes 2019-06-03 23:05:25 -07:00
danielyxie
35f8a5115a Finished implementing player influencing on stock 2nd-order forecasts. Balanced recent stock market changes 2019-06-03 22:21:36 -07:00
danielyxie
8398fd47f0 Changed the way stock market cycles occur. Stock second-order forecast now changes normally like before 2019-06-02 23:29:56 -07:00
danielyxie
9d7c869c0a Merge branch 'dev' of https://github.com/danielyxie/bitburner into dev 2019-06-02 20:57:57 -07:00
danielyxie
74587f269e Updated changelog 2019-06-02 20:57:39 -07:00
danielyxie
6a3ffff3ad Merge pull request #630 from jaguilar/ns2_recompile_when_script_write
Recompile scripts when updated via ns.write
2019-06-02 20:56:21 -07:00
J
2f8eac07ee fix indentation 2019-06-02 23:53:07 -04:00
J
3eaefa01f9 Merge branch 'dev' into ns2_recompile_when_script_write 2019-06-02 23:50:57 -04:00
danielyxie
b250af913d Merge pull request #629 from jaguilar/ns2_recompile_stale_deps
recompile ns2 scripts if dependencies change
2019-06-02 20:46:32 -07:00
danielyxie
0b4968d148 Re-added the getStockPurchaseCost() and getStockSaleGain() functions so we don't break user scripts 2019-06-02 20:28:02 -07:00
J
8817d179c6 Merge branch 'dev' into ns2_recompile_stale_deps 2019-06-02 23:03:20 -04:00
James Aguilar
e5e3fec1a9 close dialog boxes on escape 2019-06-02 20:00:14 -07:00
Heikki Aitakangas
eecb0c0f01 Update script.module immediately, so we avoid accidentally evaling the same module several times 2019-06-02 19:58:01 -07:00
James Aguilar
d45689c7df use sequence numbers rather than timestamps for script expiration 2019-06-02 20:26:06 -04:00
James Aguilar
2201dfc371 fix typo 2019-06-02 15:40:13 -04:00
James Aguilar
3ef9042051 fix two cases where markUpdated was not properly called 2019-06-02 15:38:45 -04:00
James Aguilar
1236ad252b markUpdated() for Script 2019-06-02 15:32:35 -04:00
James Aguilar
65331ab22e recompile ns2 scripts if dependencies change 2019-06-02 15:21:08 -04:00
danielyxie
c485fdfa87 Removed stock market price movement. Now only forecast is influenced by big transactions 2019-05-22 19:12:06 -07:00
danielyxie
6effda29a9 Implemented second-order forecasts for stocks 2019-05-22 17:23:30 -07:00
danielyxie
7035154454 Merge branch 'master' of https://github.com/danielyxie/bitburner into dev 2019-05-20 14:45:30 -07:00
danielyxie
d7f3ab9177 Tempered the affect that stock market txs have on stock forecasts 2019-05-20 14:44:51 -07:00
danielyxie
3660dde75f Merge branch 'master' of https://github.com/danielyxie/bitburner into dev 2019-05-19 14:07:16 -07:00
danielyxie
99688b78c7 Temporary rebalancing for v0.47.0 2019-05-19 14:06:53 -07:00
danielyxie
6841f24932 Optimized Largest Prime factor coding contract solver. 2019-05-19 13:56:49 -07:00
Sotisi
9f94d0838a removed faulty else, was left prior by accident. 2019-05-17 15:54:36 -07:00
Sotisi
086fc67ecc Fixed O(n) runtime of "Find Largest Prime Factor" for high primes to 0(sqrt(n)). 2019-05-17 15:54:36 -07:00
danielyxie
a2551f98c2 Fixed documentation typos in v0.47.0 2019-05-17 15:51:28 -07:00
danielyxie
95c928afc9 Merge branch 'dev' of https://github.com/danielyxie/bitburner into dev 2019-05-17 13:51:48 -07:00
danielyxie
8a00e6e532 Merge pull request #614 from danielyxie/active-scripts-ui-and-implementation-rework
Active scripts ui and implementation rework
2019-05-17 13:50:55 -07:00
danielyxie
287a97aea6 Fixed merge conflcits with dev 2019-05-17 13:50:27 -07:00
danielyxie
664267bff0 Removed unused imports in engine 2019-05-17 13:47:35 -07:00
danielyxie
2597b33f81 Finished refactoring augmentations page UI to use react 2019-05-17 13:47:35 -07:00
danielyxie
9442b348e6 Refactored SourceFile-related code to TypeScript 2019-05-17 13:47:35 -07:00
danielyxie
3b7f9c9fb0 Fixed issues with Active Scripts UI. Implemented event emitter for Active Scripts UI 2019-05-17 13:41:16 -07:00
danielyxie
20ca7533b0 Merge pull request #612 from danielyxie/dev
v0.47.0
2019-05-17 13:09:04 -07:00
danielyxie
15a324a946 v0.47.0 release 2019-05-17 13:07:11 -07:00
danielyxie
94175877d7 Changed stock market price movements so that upward and downward movements use different trackers. Forecast can no longer be inverted due to price movements. Updated stock market unit tests 2019-05-16 23:55:21 -07:00
danielyxie
c1ec3c5eba Finished refactoring Active Scripts UI into React/TypeScript. Currently untested 2019-05-16 23:44:59 -07:00
danielyxie
42804b0cd3 Refactored 'workerScripts' array and killWorkerScript() fn to be their own modules in TypeScript 2019-05-15 23:05:36 -07:00
danielyxie
b1248521f3 Removed unused imports in engine 2019-05-15 00:37:11 -07:00
danielyxie
b744997c72 Finished refactoring augmentations page UI to use react 2019-05-15 00:15:07 -07:00
danielyxie
2d37409392 Refactored SourceFile-related code to TypeScript 2019-05-14 20:56:59 -07:00
danielyxie
bd02e724e5 Fixed several more bugs. Rebalanced stock market changes to make the effects a bit less potent for now 2019-05-14 04:23:55 -07:00
danielyxie
fef7aaba8f Adding more directory-related unit tests. Several more bug fixes and QoL improvements 2019-05-14 01:35:37 -07:00
danielyxie
1775ea86ff Fixed corporation bug 2019-05-11 20:03:36 -07:00
danielyxie
b0918d7bd3 Fixed numerous reported bugs. Refactored some of the directory-related code. Added documentation for MasonDs changes to hack/grow/weaken 2019-05-11 19:20:20 -07:00
danielyxie
29e0ce5f96 Fixed merge conflicts 2019-05-11 02:20:09 -07:00
Mason Dechaineux
44c26165f4 (feat) optional threads argument to netscript functions hack, weaken, grow 2019-05-11 02:05:40 -07:00
danielyxie
9dd68947f1 Added Dynamic RAM calculation unit tests 2019-05-10 02:24:50 -07:00
danielyxie
db5fdb1fcb Last fixes to unit test build configuration 2019-05-09 19:51:56 -07:00
danielyxie
74e72854d8 Configured unit test webpack build to work 2019-05-09 19:36:04 -07:00
danielyxie
ece246b391 Converted mocha unit tests to run using mocha-webpack (mochapack) package 2019-05-09 19:03:13 -07:00
danielyxie
cdb5dfec62 Resolved more circular dependencies. Installed acorn-walk and imported it in RamCalculations using ES6 modules. Fixed bugs in stock market rework 2019-05-06 18:01:06 -07:00
danielyxie
8a5b6f6cbc Refactored stock buying/selling code into its own file. Refactored WorkerScript & NetscriptEnvironment into their own Typescript classes. Refactored a ton of code to remove circular dependencies 2019-05-04 21:03:40 -07:00
danielyxie
585e1ac7aa Stock transactions can now influence forecast in addition to price. Several more minor bug/UI fixes 2019-05-01 15:20:14 -07:00
danielyxie
8726946d4a Merge branch 'dev' of https://github.com/danielyxie/bitburner into dev 2019-04-30 02:27:58 -07:00
danielyxie
064008d200 Removed 'age' stat from Corporation employees 2019-04-30 02:27:48 -07:00
danielyxie
d955280f90 Re-sleeves can no longer have the Neuroflux Governor aug. This is to prevent bugs 2019-04-30 02:27:13 -07:00
danielyxie
580a7fac24 Bug fixes for v0.47.0. Fixed the BUY MAX feature for new stock market. Added collapse/expand tickers buttons for new stock market UI 2019-04-29 20:54:20 -07:00
danielyxie
9df054dd0c Merge branch 'dev' of https://github.com/danielyxie/bitburner into dev 2019-04-28 23:22:02 -07:00
Mason Dechaineux
8fa7b112e1 Correctly throw error when ns.run() is called with 0 threads 2019-04-28 23:21:55 -07:00
danielyxie
dd9df0a18c Fixed Stock Market UI. Added documentation for stock market changes 2019-04-28 23:21:32 -07:00
danielyxie
3a601a015d Added unit tests for Stock Market. Removed dependencies from package.json, since they're sourced directly in tests/index.html 2019-04-28 23:21:32 -07:00
danielyxie
87b4698d5b Fixed issues relating to stock price movements 2019-04-28 23:21:32 -07:00
danielyxie
67632ced09 Fixed Stock Market UI issues. Added warnings for price movements 2019-04-28 23:21:32 -07:00
danielyxie
d7fb335815 Fixed Stock market UI compilation errors and removed refactored code 2019-04-28 23:21:32 -07:00
danielyxie
4809a21e38 Finished React components for new Stock Market UI 2019-04-28 23:21:32 -07:00
danielyxie
6b3646e981 Added spread and price movement properties to stocks. Refactored Stock Market implementation code 2019-04-28 23:21:32 -07:00
danielyxie
0c64bf470a Merge branch 'master' of https://github.com/danielyxie/bitburner into dev 2019-04-20 23:37:51 -07:00
danielyxie
99e034921e Updated docuemntation changelog for v0.46.3 2019-04-20 23:37:37 -07:00
danielyxie
7a3a3de7d1 v0.46.3 2019-04-20 23:37:37 -07:00
danielyxie
f91c5bd7b9 Updated docuemntation changelog for v0.46.3 2019-04-19 22:29:33 -07:00
danielyxie
bcb198220d v0.46.3 2019-04-19 22:27:33 -07:00
danielyxie
bf1af6a68c Merge branch 'master' of https://github.com/danielyxie/bitburner into dev 2019-04-16 01:22:22 -07:00
danielyxie
3dd2975c61 Fixed issue with faction reputation donation bug calculating invalid rep gain 2019-04-16 01:20:52 -07:00
danielyxie
33f1e0cb3c Fixed several typos. TechVendor location UI will now properly update when you purchase TOR router. 'Manage Gang' faction option should show up properly 2019-04-16 01:20:52 -07:00
danielyxie
7514f63dcd Fixed issue with faction reputation donation bug calculating invalid rep gain 2019-04-16 01:19:29 -07:00
danielyxie
b6ff73391d Fixed several typos. TechVendor location UI will now properly update when you purchase TOR router. 'Manage Gang' faction option should show up properly 2019-04-15 02:49:49 -07:00
danielyxie
369ea8d381 Merge pull request #597 from danielyxie/dev
Dev
2019-04-14 02:26:37 -07:00
danielyxie
a7296c512c Fix merge conflicts 2019-04-14 02:26:47 -07:00
danielyxie
8f70817c10 Adding tooltips to new purchase augmentation ui 2019-04-14 02:21:43 -07:00
danielyxie
7417fb6ef8 v0.46.2 2019-04-14 02:08:10 -07:00
danielyxie
d044739f1c v0.46.2 2019-04-14 02:07:29 -07:00
danielyxie
3d1684f825 Fix Terminal wget bug. Issue #593 2019-04-14 02:07:29 -07:00
danielyxie
f6af9e94ab Gang bug fixes. Issues #574 and #575 2019-04-14 02:07:29 -07:00
danielyxie
215cf59e0b Fixed comment styling for all top-level src files 2019-04-14 02:07:29 -07:00
danielyxie
0d14cd6e7e Improved module import styling for all top-level src files 2019-04-14 02:07:29 -07:00
danielyxie
98a04e4932 Fix Terminal wget bug. Issue #593 2019-04-13 00:33:30 -07:00
danielyxie
8d33c5b571 Gang bug fixes. Issues #574 and #575 2019-04-13 00:26:49 -07:00
danielyxie
221b81d802 Fixed comment styling for all top-level src files 2019-04-12 20:47:17 -07:00
danielyxie
df89cc5002 Improved module import styling for all top-level src files 2019-04-12 20:47:17 -07:00
danielyxie
3b6b37f8a6 Merge pull request #594 from danielyxie/dev
v0.46.1
2019-04-12 16:53:19 -07:00
danielyxie
cf2acb8844 Updated documentation changelog for v0.46.1 2019-04-12 16:54:11 -07:00
219 changed files with 18349 additions and 9542 deletions

6
.gitignore vendored
View File

@@ -3,6 +3,6 @@ Netburner.txt
/doc/build
/node_modules
/dist/*.map
/tests/*.map
/tests/*.bundle.*
/tests/*.css
/test/*.map
/test/*.bundle.*
/test/*.css

View File

@@ -1,6 +1,7 @@
# Bitburner
Bitburner is a cyberpunk hacking-themed incremental game. The game can be
played at https://danielyxie.github.io/bitburner.
Bitburner is a programming-based [incremental game](https://en.wikipedia.org/wiki/Incremental_game)
that revolves around hacking and cyberpunk themes.
The game can be played at https://danielyxie.github.io/bitburner.
# Documentation
The game's official documentation can be found on [Read The

126
css/activescripts.scss Normal file
View 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
View 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;
}
}

View File

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

View File

@@ -75,7 +75,7 @@
margin: 4px;
padding: 2px;
resize: none;
width: 50%;
width: 60%;
}
#script-editor-status {

View File

@@ -7,35 +7,45 @@
p {
font-size: $defaultFontSize * 0.8125;
}
a {
font-size: $defaultFontSize * 0.875;
}
h2 {
}
.stock-market-info-and-purchases {
> h2 {
display: block;
margin-top: 10px;
margin-left: 10px;
}
> p {
display: block;
margin-left: 10px;
width: 70%;
}
> a, > button {
margin: 10px;
}
}
#stock-market-list li {
button {
font-size: $defaultFontSize;
#stock-market-list {
list-style: none;
li {
button {
font-size: $defaultFontSize;
}
}
}
#stock-market-container p {
padding: 6px;
margin: 6px;
width: 70%;
}
#stock-market-container a {
margin: 10px;
}
#stock-market-watchlist-filter {
display: block;
margin: 5px 5px 5px 10px;
padding: 4px;
width: 50%;
margin-left: 10px;
}
.stock-market-input {
@@ -47,14 +57,36 @@
color: var(--my-font-color);
}
.stock-market-price-movement-warning {
border: 1px solid white;
color: red;
margin: 2px;
padding: 2px;
}
.stock-market-position-text {
color: #fff;
display: inline-block;
display: block;
p {
color: #fff;
display: inline-block;
margin: 4px;
}
h3 {
margin: 4px;
}
}
.stock-market-order-list {
overflow-y: auto;
max-height: 100px;
li {
color: #fff;
padding: 4px;
}
}
.stock-market-order-cancel-btn {

View File

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

File diff suppressed because one or more lines are too long

2
dist/engineStyle.bundle.js vendored Normal file
View File

@@ -0,0 +1,2 @@
!function(n){function t(t){for(var e,i,f=t[0],c=t[1],l=t[2],p=0,s=[];p<f.length;p++)i=f[p],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

View File

@@ -1,46 +1,3 @@
/* COLORS */
/* Attributes */
/**
* Customized styling for the Code Mirror editor
*/
#codemirror-form-wrapper {
height: 80%;
margin: 10px 0px 0px 6px; }
.CodeMirror {
height: 100%;
width: 100%;
border: 2px solid var(--my-highlight-color);
z-index: 1;
font-family: "Lucida Console", "Lucida Sans Unicode", "Fira Mono", "Consolas", "Courier New", Courier, monospace, "Times New Roman";
font-size: 16px; }
/**
* Highlight matches
*/
.cm-matchhighlight {
background-color: #8F908A; }
.CodeMirror-selection-highlight-scrollbar {
background-color: #8F908A; }
/**
* Show Invisibles
*/
.cm-whitespace::before {
position: absolute;
pointer-events: none;
color: #404F7D; }
/**
* Vim command display
*/
#codemirror-vim-command-display-wrapper {
background-color: white;
font-size: 13px;
height: 30px;
margin-left: 6px; }
/* COLORS */
/* Attributes */
/* COLORS */
@@ -304,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;
@@ -893,7 +850,7 @@ button {
margin: 4px;
padding: 2px;
resize: none;
width: 50%; }
width: 60%; }
#script-editor-status {
float: left;
@@ -933,6 +890,147 @@ button {
/* Specified overrides for Code mirror Editor are defined in codemirror-override.scss */
/* COLORS */
/* Attributes */
/**
* Customized styling for the Code Mirror editor
*/
#codemirror-form-wrapper {
height: 80%;
margin: 10px 0px 0px 6px; }
.CodeMirror {
height: 100%;
width: 100%;
border: 2px solid var(--my-highlight-color);
z-index: 1;
font-family: "Lucida Console", "Lucida Sans Unicode", "Fira Mono", "Consolas", "Courier New", Courier, monospace, "Times New Roman";
font-size: 16px; }
/**
* Highlight matches
*/
.cm-matchhighlight {
background-color: #8F908A; }
.CodeMirror-selection-highlight-scrollbar {
background-color: #8F908A; }
/**
* Show Invisibles
*/
.cm-whitespace::before {
position: absolute;
pointer-events: none;
color: #404F7D; }
/**
* Vim command display
*/
#codemirror-vim-command-display-wrapper {
background-color: white;
font-size: 13px;
height: 30px;
margin-left: 6px; }
/* COLORS */
/* Attributes */
.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 */
/**
@@ -1313,25 +1298,30 @@ button {
font-size: 13px; }
#stock-market-container a {
font-size: 14px; }
#stock-market-container h2 {
margin-top: 10px;
margin-left: 10px;
display: block; }
#stock-market-list li button {
font-size: 16px; }
.stock-market-info-and-purchases > h2 {
display: block;
margin-top: 10px;
margin-left: 10px; }
#stock-market-container p {
padding: 6px;
margin: 6px;
.stock-market-info-and-purchases > p {
display: block;
margin-left: 10px;
width: 70%; }
#stock-market-container a {
.stock-market-info-and-purchases > a, .stock-market-info-and-purchases > button {
margin: 10px; }
#stock-market-list {
list-style: none; }
#stock-market-list li button {
font-size: 16px; }
#stock-market-watchlist-filter {
width: 50%;
margin-left: 10px; }
display: block;
margin: 5px 5px 5px 10px;
padding: 4px;
width: 50%; }
.stock-market-input {
display: inline-block;
@@ -1341,13 +1331,28 @@ button {
border: 1px solid #fff;
color: var(--my-font-color); }
.stock-market-price-movement-warning {
border: 1px solid white;
color: red;
margin: 2px;
padding: 2px; }
.stock-market-position-text {
color: #fff;
display: inline-block; }
display: block; }
.stock-market-position-text p {
color: #fff;
display: inline-block;
margin: 4px; }
.stock-market-position-text h3 {
margin: 4px; }
.stock-market-order-list {
overflow-y: auto;
max-height: 100px; }
.stock-market-order-list li {
color: #fff;
padding: 4px; }
.stock-market-order-cancel-btn {
background-color: #000;
@@ -4866,4 +4871,4 @@ html {
margin-right: 0px; }
/*# sourceMappingURL=engine.css.map*/
/*# sourceMappingURL=engineStyle.css.map*/

623
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -86,8 +86,11 @@ Sleeve memory dictates what a sleeve's synchronization will be when its reset by
switching BitNodes. For example, if a sleeve has a memory of 10, then when you
switch BitNodes its synchronization will initially be set to 10, rather than 1.
Memory can only be increased by purchasing upgrades from The Covenant.
It is a persistent stat, meaning it never gets reset back to 1.
Memory can only be increased by purchasing upgrades from The Covenant. Just like
the ability to purchase additional sleeves, this is only available in BitNodes-10
and above, and is only available after defeating BitNode-10 at least once.
Memory is a persistent stat, meaning it never gets reset back to 1.
The maximum possible value for a sleeve's memory is 100.
Re-sleeving

View File

@@ -40,7 +40,7 @@ solutions. Some may be numbers, others may be strings or arrays.
If a contract asks for a specific solution format, then
use that. Otherwise, follow these rules when submitting solutions:
* String-type solutions should not have quotation marks surrounding
* String-type solutions should **not** have quotation marks surrounding
the string (unless specifically asked for). Only quotation
marks that are part of the actual string solution should be included.
* Array-type solutions should be submitted with each element

View File

@@ -7,10 +7,14 @@ buy and sell stocks in order to make money.
The WSE can be found in the 'City' tab, and is accessible in every city.
Automating the Stock Market
^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can write scripts to perform automatic and algorithmic trading on the Stock Market.
See :ref:`netscript_tixapi` for more details.
Fundamentals
------------
The Stock Market is not as simple as "buy at price X and sell at price Y". The following
are several fundamental concepts you need to understand about the stock market.
.. note:: For those that have experience with finance/trading/investing, please be aware
that the game's stock market does not function exactly like it does in the real
world. So these concepts below should seem similar, but won't be exactly the same.
Positions: Long vs Short
^^^^^^^^^^^^^^^^^^^^^^^^
@@ -21,16 +25,73 @@ is the exact opposite. In a Short position you purchase shares of a stock and
earn a profit if the price of that stock decreases. This is also called 'shorting'
a stock.
NOTE: Shorting stocks is not available immediately, and must be unlocked later in the
game.
.. 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)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The **bid price** is the maximum price at which someone will buy a stock on the
stock market.
The **ask price** is the minimum price that a seller is willing to receive for a stock
on the stock market
The ask price will always be higher than the bid price (This is because if a seller
is willing to receive less than the bid price, that transaction is guaranteed to
happen). The difference between the bid and ask price is known as the **spread**.
A stock's "price" will be the average of the bid and ask price.
The bid and ask price are important because these are the prices at which a
transaction actually occurs. If you purchase a stock in the long position, the cost
of your purchase depends on that stock's ask price. If you then try to sell that
stock (still in the long position), the price at which you sell is the stock's
bid price. Note that this is reversed for a short position. Purchasing a stock
in the short position will occur at the stock's bid price, and selling a stock
in the short position will occur at the stock's ask price.
Transactions Influencing 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:
Order Types
^^^^^^^^^^^
There are three different types of orders you can make to buy or sell stocks on the exchange:
Market Order, Limit Order, and Stop Order.
Note that Limit Orders and Stop Orders are not available immediately, and must be unlocked
later in the game.
.. note:: Limit Orders and Stop Orders are not available immediately, and must be unlocked
later in the game.
When you place a Market Order to buy or sell a stock, the order executes immediately at
whatever the current price of the stock is. For example if you choose to short a stock
@@ -71,3 +132,77 @@ A Limit Order to sell will execute if the stock's price <= order's price
A Stop Order to buy will execute if the stock's price <= order's price
A Stop Order to sell will execute if the stock's price >= order's price.
.. _gameplay_stock_market_player_actions_influencing_stock:
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
---------------------------
You can write scripts to perform automatic and algorithmic trading on the Stock Market.
See :ref:`netscript_tixapi` for more details.
Under the Hood
--------------
Stock prices are updated very ~6 seconds.
Whether a stock's price moves up or down is determined by RNG. However,
stocks have properties that can influence the way their price moves. These properties
are hidden, although some of them can be made visible by purchasing the
Four Sigma (4S) Market Data upgrade. Some examples of these properties are:
* Volatility
* Likelihood of increasing or decreasing (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")
Each stock has its own unique values for these properties.
Offline Progression
-------------------
The Stock Market does not change or process anything while the game has closed.
However, it does accumulate time when offline. This accumulated time allows
the stock market to run 50% faster when the game is opened again. This means
that stock prices will update every ~4 seconds instead of 6.

View File

@@ -3,6 +3,101 @@
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:
* Implemented spread. Stock's now have bid and ask prices at which transactions occur
* Large transactions will now influence a stock's price and forecast
* This "influencing" can take effect in the middle of a transaction
* See documentation for more details on these changes
* Added getStockAskPrice(), getStockBidPrice() Netscript functions to the TIX API
* Added getStockPurchaseCost(), getStockSaleGain() Netscript functions to the TIX API
* Re-sleeves can no longer have the NeuroFlux Governor augmentation
* This is just a temporary patch until the mechanic gets re-worked
* hack(), grow(), and weaken() functions now take optional arguments for number of threads to use (by MasonD)
* codingcontract.attempt() now takes an optional argument that allows you to configure the function to return a contract's reward
* Adjusted RAM costs of Netscript Singularity functions (mostly increased)
* Adjusted RAM cost of codingcontract.getNumTriesRemaining() Netscript function
* Netscript Singularity functions no longer cost extra RAM outside of BitNode-4
* Corporation employees no longer have an "age" stat
* Gang Wanted level gain rate capped at 100 (per employee)
* Script startup/kill is now processed every 3 seconds, instead of 6 seconds
* getHackTime(), getGrowTime(), and getWeakenTime() now return Infinity if called on a Hacknet Server
* Money/Income tracker now displays money lost from hospitalizations
* Exported saves now have a unique filename based on current BitNode and timestamp
* Maximum number of Hacknet Servers decreased from 25 to 20
* Bug Fix: Corporation employees stats should no longer become negative
* Bug Fix: Fixed sleeve.getInformation() throwing error in certain scenarios
* Bug Fix: Coding contracts should no longer generate on the w0r1d_d43m0n server
* Bug Fix: Duplicate Sleeves now properly have access to all Augmentations if you have a gang
* Bug Fix: getAugmentationsFromFaction() & purchaseAugmentation() functions should now work properly if you have a gang
* Bug Fix: Fixed issue that caused messages (.msg) to be sent when refreshing/reloading the game
* Bug Fix: Purchasing hash upgrades for Bladeburner/Corporation when you don't actually have access to those mechanics no longer gives hashes
* Bug Fix: run(), exec(), and spawn() Netscript functions now throw if called with 0 threads
* Bug Fix: Faction UI should now automatically update reputation
* Bug Fix: Fixed purchase4SMarketData()
* Bug Fix: Netscript1.0 now works properly for multiple 'namespace' imports (import * as namespace from "script")
* Bug Fix: Terminal 'wget' command now correctly evaluates directory paths
* Bug Fix: wget(), write(), and scp() Netscript functions now fail if an invalid filepath is passed in
* Bug Fix: Having Corporation warehouses at full capacity should no longer freeze game in certain conditions
* Bug Fix: Prevented an exploit that allows you to buy multiple copies of an Augmentation by holding the 'Enter' button
* Bug Fix: gang.getOtherGangInformation() now properly returns a deep copy
* Bug Fix: Fixed getScriptIncome() returning an undefined value
* Bug Fix: Fixed an issue with Hacknet Server hash rate not always updating
v0.46.3 - 4/20/2019
-------------------
* Added a new Augmentation: The Shadow's Simulacrum
* Improved tab autocompletion feature in Terminal so that it works better with directories
* Bug Fix: Tech vendor location UI now properly refreshed when purchasing a TOR router
* Bug Fix: Fixed UI issue with faction donations
* Bug Fix: The money statistics & breakdown should now properly track money earned from Hacknet Server (hashes -> money)
* Bug Fix: Fixed issue with changing input in 'Minimum Path Sum in a Triangle' coding contract problem
* Fixed several typos in various places
v0.46.2 - 4/14/2019
-------------------
* Source-File 2 now allows you to form gangs in other BitNodes when your karma reaches a very large negative value
* (Karma is a hidden stat and is lowered by committing crimes)
* Gang changes:
* Bug Fix: Gangs can no longer clash with themselve
* Bug Fix: Winning against another gang should properly reduce their power
* Bug Fix: Terminal 'wget' command now works properly
* Bug Fix: Hacknet Server Hash upgrades now properly reset upon installing Augs/switching BitNodes
* Bug Fix: Fixed button for creating Corporations
v0.46.1 - 4/12/2019
-------------------
* Added a very rudimentary directory system to the Terminal
* Details here: https://bitburner.readthedocs.io/en/latest/basicgameplay/terminal.html#filesystem-directories
* Added numHashes(), hashCost(), and spendHashes() functions to the Netscript Hacknet Node API
* 'Generate Coding Contract' hash upgrade is now more expensive
* 'Generate Coding Contract' hash upgrade now generates the contract randomly on the server, rather than on home computer
* The cost of selling hashes for money no longer increases each time
* Selling hashes for money now costs 4 hashes (in exchange for $1m)
* Bug Fix: Hacknet Node earnings should work properly when game is inactive/offline
* Bug Fix: Duplicate Sleeve augmentations are now properly reset when switching to a new BitNode
v0.46.0 - 4/3/2019
------------------
* Added BitNode-9: Hacktocracy

View File

@@ -64,9 +64,9 @@ documentation_title = '{0} Documentation'.format(project)
# built documents.
#
# The short X.Y version.
version = '0.46'
version = '0.47'
# The full version, including alpha/beta/rc tags.
release = '0.46.0'
release = '0.47.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View File

@@ -2,10 +2,11 @@ Guides & Tips
=============
Getting Started Guide for Intermediate Programmers
What BitNode should I do?
Beginners FAQ
.. toctree::
:maxdepth: 3
Getting Started Guide for Beginner Programmers <guidesandtips/gettingstartedguideforbeginnerprogrammers>
What BitNode should I do?<guidesandtips/recommendedbitnodeorder>

View File

@@ -0,0 +1,490 @@
What BitNode should I do?
=========================
.. warning:: This page contains spoilers regarding the game's story/plot-line.
After destroying their first :ref:`BitNode <gameplay_bitnodes>`, many players
wonder which BitNode they should tackle next. This guide hopefully helps answer
that question.
Overview of each BitNode
------------------------
BitNode-1: Source Genesis
^^^^^^^^^^^^^^^^^^^^^^^^^
Description
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.
This is the first BitNode that you play through. It has no special
modifications or mechanics.
Source-File
:Max Level: 3
This Source-File lets the player start with 32GB of RAM on his/her home computer when
entering a new BitNode, and also increases all of the player's multipliers by:
* Level 1: 16%
* Level 2: 24%
* Level 3: 28%
Difficulty
The easiest BitNode
BitNode-2: Rise of the Underworld
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Description
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
factions quickly rose to the top of the modern world.
In this BitNode:
* Your hacking level is reduced by 20%
* The growth rate and maximum amount of money available on servers are significantly decreased
* The amount of money gained from crimes and Infiltration is tripled
* Certain Factions (Slum Snakes, Tetrads, The Syndicate, The Dark Army, Speakers for the Dead,
NiteSec, The Black Hand) give the player the ability to form and manage their own gangs. These gangs
will earn the player money and reputation with the corresponding Faction
* Every Augmentation in the game will be available through the Factions listed above
* For every Faction NOT listed above, reputation gains are halved
* You will no longer gain passive reputation with Factions
Source-File
:Max Level: 3
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:
* Level 1: 24%
* Level 2: 36%
* Level 3: 42%
Difficulty
Fairly easy, as hacking is still very profitable and the costs of various purchases/upgrades
is not increased. The gang mechanic may seem strange as its very different from anything
else, but it can be very powerful once you get the hang of it.
BitNode-3: Corporatocracy
^^^^^^^^^^^^^^^^^^^^^^^^^
Description
Our greatest illusion is that a healthy society can revolve around a
single-minded pursuit of wealth.
Sometime in the early 21st century economic and political globalization turned
the world into a corporatocracy, and it never looked back. Now, the privileged
elite will happily bankrupt their own countrymen, decimate their own community,
and evict their neighbors from houses in their desperate bid to increase their wealth.
In this BitNode you can create and manage your own corporation. Running a successful corporation
has the potential of generating massive profits. All other forms of income are reduced by 75%. Furthermore:
* The price and reputation cost of all Augmentations is tripled
* The starting and maximum amount of money on servers is reduced by 75%
* Server growth rate is reduced by 80%
* You now only need 75 favour with a faction in order to donate to it, rather than 150
Source-File
:Max Level: 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:
* Level 1: 8%
* Level 2: 12%
* Level 3: 14%
Difficulty
Somewhat-steep learning curve as you learn how to use and manage Corporations. Afterwards,
however, the BitNode is easy as Corporations can be very profitable.
BitNode-4: The Singularity
^^^^^^^^^^^^^^^^^^^^^^^^^^
Description
The Singularity has arrived. The human race is gone, replaced by artificially superintelligent
beings that are more machine than man.
In this BitNode, progressing is significantly harder:
* Experience gain rates for all stats are reduced.
* Most methods of earning money will now give significantly less.
In this BitNode you will gain access to a new set of Netscript Functions known as Singularity Functions.
These functions allow you to control most aspects of the game through scripts, including
working for factions/companies, purchasing/installing Augmentations, and creating programs.
Source-File
:Max Level: 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.
Difficulty:
Depending on what Source-Files you have unlocked before attempting this BitNode,
it can range from easy to moderate.
BitNode-5: Artificial Intelligence
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Description
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
by 1's and 0's. They were wrong.
In this BitNode:
* The base security level of servers is doubled
* The starting money on servers is halved, but the maximum money remains the same
* Most methods of earning money now give significantly less
* Infiltration gives 50% more reputation and money
* Corporations have 50% lower valuations and are therefore less profitable
* Augmentations are more expensive
* Hacking experience gain rates are reduced
Source-File
:Max Level: 3
This Source-File grants you 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 :js:func:`getBitNodeMultipliers()` Netscript function,
and will also raise all of your hacking-related multipliers by:
* Level 1: 8%
* Level 2: 12%
* Level 3: 14%
Difficulty
Depending on what Source-Files you have unlocked before attempting this BitNode, it
can range from easy to moderate.
BitNode-6: Bladeburners
^^^^^^^^^^^^^^^^^^^^^^^
Description
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
the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, and more intelligent
than the humans that had created them.
In this BitNode you will be able to access the Bladeburner Division at the NSA, which provides
a new mechanic for progression. Furthermore:
* Hacking and Hacknet Nodes will be less profitable
* Your hacking level is reduced by 65%
* Hacking experience gain from scripts is reduced by 75%
* Corporations have 80% lower valuations and are therefore less profitable
* Working for companies is 50% less profitable
* Crimes and Infiltration are 25% less profitable
Source-File
:Max Level: 3
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:
* Level 1: 8%
* Level 2: 12%
* Level 3: 14%
Difficulty
Initially difficult due to the fact that hacking is no longer profitable and you have
to learn a new mechanic. After you get the hang of the Bladeburner mechanic, however,
it becomes moderately easy.
BitNode-7: Bladeburners 2079
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Description
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. Many argue that this was the first
sentient AI ever created. This resulted in Synthoid models that were stronger, faster,
and more intelligent than the humans that had created them.
In this BitNode you will be able to access the Bladeburner API, which allows you to access
Bladeburner functionality through Netscript. Furthermore:
* The rank you gain from Bladeburner contracts/operations is reduced by 40%
* Bladeburner skills cost twice as many skill points
* Augmentations are 3x more expensive
* Hacking and Hacknet Nodes will be significantly less profitable
* Your hacking level is reduced by 65%
* Hacking experience gain from scripts is reduced by 75%
* Corporations have 80% lower valuations and are therefore less profitable
* Working for companies is 50% less profitable
* Crimes and Infiltration are 25% less profitable
Source-File
:Max Level: 3
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:
* Level 1: 8%
* Level 2: 12%
* Level 3: 14%
Difficulty
Slightly more difficult than BitNode-6. However, you will be able to automate more
aspects of the Bladeburner feature, which means it will be more passive.
BitNode-8: Ghost of Wall Street
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Description
You are trying to make a name for yourself as an up-and-coming hedge fund manager on Wall Street.
In this BitNode:
* You start with $250 million
* The only way to earn money is by trading on the stock market
* You start with a WSE membership and access to the TIX API
* You are able to short stocks and place different types of orders (limit/stop)
* You can immediately donate to factions to gain reputation
Source-File
:Max Level: 3
This Source-File grants the following benefits:
* Level 1: Permanent access to WSE and TIX API
* Level 2: Ability to short stocks in other BitNodes
* Level 3: Ability to use limit/stop orders in other BitNodes
This Source-File also increases your hacking growth multipliers by:
* Level 1: 12%
* Level 2: 18%
* Level 3: 21%
Difficulty
Very difficult until you unlock the Four Sigma (4S) Market Data API. After you
unlock the API however, it becomes moderately easy.
BitNode-9: Hacktocracy
^^^^^^^^^^^^^^^^^^^^^^
Description
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 abandoned the project and dissociated themselves from it.
This BitNode unlocks the Hacknet Server, an upgraded version of the Hacknet Node. Hacknet Servers generate
hashes, which can be spent on a variety of different upgrades.
In this BitNode:
* Your stats are significantly decreased
* You cannnot purchase additional servers
* Hacking is significantly less profitable
Source-File
:Max Level: 3
This Source-File grants the following benefits:
* Level 1: Permanently unlocks the Hacknet Server in other BitNodes
* Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode
* Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode
(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT
when installing Augmentation
Difficulty
Hard
BitNode-10: Digital Carbon
^^^^^^^^^^^^^^^^^^^^^^^^^^
Description
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 human consciousness. Mankind had finally achieved immortality - at least for those
that could afford it.
This BitNode unlocks Sleeve technology. Sleeve technology allows you to:
1. Re-sleeve: Purchase and transfer your consciousness into a new body
2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks synchronously
In this BitNode:
* Your stats are significantly decreased
* All methods of gaining money are half as profitable (except Stock Market)
* Purchased servers are more expensive, have less max RAM, and a lower maximum limit
* Augmentations are 5x as expensive and require twice as much reputation
Source-File
:Max Level: 3
This Source-File unlocks Sleeve technology in other BitNodes.
Each level of this Source-File also grants you a Duplicate Sleeve
Difficulty
Hard
BitNode-11: The Big Crash
^^^^^^^^^^^^^^^^^^^^^^^^^
Description
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.
In many countries, the high cost of trying to deal with the civil disorder bankrupted the governments. In all of this chaos and confusion, hackers
were able to steal billions of dollars from the world's largest electronic banks, prompting an international banking crisis as
governments were unable to bail out insolvent banks. Now, the world is slowly crumbling in the middle of the biggest economic crisis of all time.
In this BitNode:
* Your hacking stat and experience gain are halved
* The starting and maximum amount of money available on servers is significantly decreased
* The growth rate of servers is significantly reduced
* Weakening a server is twice as effective
* Company wages are decreased by 50%
* Corporation valuations are 99% lower and are therefore significantly less profitable
* Hacknet Node production is significantly decreased
* Crime and Infiltration are more lucrative
* Augmentations are twice as expensive
Source-File
:Max Level: 3
Destroying this BitNode will give you Source-File 11, or if you already have this Source-File it will
upgrade its level up to a maximum of 3. 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:
* Level 1: 32%
* Level 2: 48%
* Level 3: 56%
Difficulty
Hard
BitNode-12: The Recursion
^^^^^^^^^^^^^^^^^^^^^^^^^
Description
Every time this BitNode is destroyed, it becomes slightly harder.
Source-File
:Max Level: Infinity
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)
Difficulty
Initially very easy, but then it (obviously) becomes harder as you continue to do it.
Recommended BitNodes
--------------------
As a player, you are not forced to tackle the BitNodes in any particular order. You are
free to choose whichever ones you want. The "best" order can vary between players,
depending on what you like to do any what kind of player you are. In general, here
are the recommended BitNodes for different things:
For fast progression
^^^^^^^^^^^^^^^^^^^^
.. note:: This does not recommend the absolute fastest path, as I don't know what
exactly the fastest path is. But it does recommend the BitNodes that are
commonly considered to be optimal by players.
1. Repeat **BitNode-1: Source Genesis** until you max out its Source-File. Its Source-File
is extremely powerful, as it raises all multipliers by a significant amount.
2. Repeat **BitNode-12: The Recursion** several times. This BitNode will be extremely easy the
first few times you tackle it, and its Source-File raises all multipliers. Furthermore,
its effect stacks multiplicatively with itself and other Source-Files/Augmentations,
which gets better as time goes on
3. Do **BitNode-5: Artificial Intelligence** once or twice. The intelligence stat it unlocks
will gradually build up as you continue to play the game, and will be helpful
in the future. The Source-File also provides hacking multipliers, which are
strong because hacking is typically one of the best ways of earning money.
4. (Optional) Consider doing **BitNode-4: The Singularity**. Its Source-File does not directly make you
more powerful in any way, but it does unlock :ref:`netscript_singularityfunctions` which
let you automate significantly more aspects of the game.
5. Do **BitNode-3: Corporatocracy** once to unlock the Corporation mechanic. This mechanic
has high profit potential.
6. Do **BitNode-6: Bladeburners** once to unlock the Bladeburners mechanic. The Bladeburner
mechanic is useful for some of the future BitNodes (such as 9 and 10).
7. Do **BitNode-9: Hacktocracy** to unlock the Hacknet Server mechanic. You can
consider repeating it as well, as its Level 2 and 3 effects are pretty helpful as well.
.. todo:: To be continued as more BitNodes get added
For the strongest Source-Files
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Note that the strongest Source-Files are typically rewarded by the hardest BitNodes.
The strongest Source-File is that from **BitNode-1: Source Genesis**, as it raises
all multipliers by a significant amount.
Similarly, the Source-File from **BitNode-12: The Recursion** is also very strong
because it raises all multipliers. Each level of Source-File 12 is fairly weak,
but its effectiveness gets better over time since the effects of Source-Files and
Augmentations are multiplicative with each other.
The Source-File from **BitNode-9: Hacktocracy** is good because it unlocks the Hacknet
Server mechanic. The Hacknet Server mechanic causes Hacknet Nodes to produce a new
currency called *hashes*, rather than money. *Hashes* can be spent on powerful upgrades
that benefit your hacking, Corporation, Bladeburner, etc.
The Duplicate Sleeves granted by the Source-File from **BitNode-10: Digital Carbon**
are strong, but only after you have several of them and have spent some time/money upgrading
them.
For more scripting/hacking
^^^^^^^^^^^^^^^^^^^^^^^^^^
**BitNode-4: The Singularity** unlocks the :ref:`netscript_singularityfunctions`, which
can be used to automate many different aspects of the game, including working for factions/companies,
purchasing & installing Augmentations, and creating programs
**BitNode-6** and **BitNode-7** unlock Bladeburner and its corresponding
:ref:`Netscript API <netscript_bladeburnerapi>`. This allows you to automate an entire
new mechanic.
**BitNode-2: Rise of the Underworld** also unlocks a new mechanic and Netscript API for automating
it (the Gang mechanic). However, it is not as interesting as Bladeburner (in my opinion)
**BitNode-9: Hacktocracy** unlocks the Hacknet Server mechanic and several new
functions in the :ref:`Hacknet Node API <netscript_hacknetnodeapi>` for using it.
For new mechanics
^^^^^^^^^^^^^^^^^
**BitNode-2: Rise of the Underworld** unlocks a new mechanic in which you can
manage a gang. Gangs earn you money and can be very profitable once they get large
and powerful. The biggest benefit of gangs, however, is that they make all
Augmentations available to you through their corresponding faction.
**BitNode-3: Corporatocracy** unlocks a new mechanic in which you can manage a
corporation. You can earn money through Corporations by selling your stocks, or by
configuring your corporation to pay dividends to shareholders. If your Corporation
gets big enough, it can also bribe factions in exchange for faction reputation.
**BitNode-6: Bladeburners** unlocks a new mechanic that centers around combat rather
than hacking. The main benefit of the Bladeburner mechanic is that it offers a new
method of destroying a BitNode.
**BitNode-9: Hacktocracy** unlocks the Hacknet Server, which is an upgraded version of a
Hacknet Node. The Hacknet Server generates a computational unit called a *hash*. *Hashes*
can be spent on a variety of different upgrades that can benefit your hacking,
Corporation, Bladeburner progress, and more. It transforms the Hacknet Node from a
simple money-generator to a more interesting mechanic.
**BitNode-10: Digital Carbon** unlocks two new mechanics: Re-Sleeving and
Duplicate Sleeves.
For a Challenge
^^^^^^^^^^^^^^^
In general, the higher BitNodes are more difficult than the lower ones.
**BitNode-12: The Recursion** is an obvious exception as it gets progressively harder.
**BitNode-8: Ghost of Wall Street** provides a unique challenge as the only method
of earning money in that BitNode is through trading at the stock market.

View File

@@ -5,8 +5,9 @@
Welcome to Bitburner's documentation!
=====================================
Bitburner is a cyberpunk-themed `incremental game <https://en.wikipedia.org/wiki/Incremental_game>`_ that is currently in the
early beta stage of development. The game `can be played here <https://danielyxie.github.io/bitburner/>`_.
Bitburner is a programming-based `incremental game <https://en.wikipedia.org/wiki/Incremental_game>`_
that revolves around hacking and cyberpunk themes. The game is currently in the
early beta stage of development. It `can be played here <https://danielyxie.github.io/bitburner/>`_.
What is Bitburner?
------------------
@@ -26,6 +27,7 @@ secrets that you've been searching for.
Script Editors <scripteditors>
Game Frozen or Stuck? <gamefrozen>
Guides & Tips <guidesandtips>
Tools & Resources <toolsandresources>
Changelog <changelog>
Donate <https://paypal.me/danielyxie>

View File

@@ -1,9 +1,16 @@
grow() Netscript Function
=========================
.. js:function:: grow(hostname/ip)
.. js:function:: grow(hostname/ip[, opts={}])
:param string hostname/ip: IP or hostname of the target server to grow
:param object opts: Optional parameters for configuring function behavior. Properties:
* threads (*number*) - Number of threads to use for this function.
Must be less than or equal to the number of threads the script is running with.
* 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
@@ -19,3 +26,4 @@ grow() Netscript Function
Example::
grow("foodnstuff");
grow("foodnstuff", { threads: 5 }); // Only use 5 threads to grow

View File

@@ -1,9 +1,16 @@
hack() Netscript Function
=========================
.. js:function:: hack(hostname/ip)
.. js:function:: hack(hostname/ip[, opts={}])
:param string hostname/ip: IP or hostname of the target server to hack
:param object opts: Optional parameters for configuring function behavior. Properties:
* threads (*number*) - Number of threads to use for this function.
Must be less than or equal to the number of threads the script is running with.
* 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
@@ -20,3 +27,4 @@ hack() Netscript Function
hack("foodnstuff");
hack("10.1.2.3");
hack("foodnstuff", { threads: 5 }); // Only use 5 threads to hack

View File

@@ -21,4 +21,13 @@ hackAnalyzeThreads() Netscript Function
If this function returns 50, this means that if your next `hack()` call
is run on a script with 50 threads, it will steal $1m from the `foodnstuff` server.
**Warning**: The value returned by this function isn't necessarily a whole number.
.. warning:: The value returned by this function isn't necessarily a whole number.
.. warning:: It is possible for this function to return :code:`Infinity` or :code:`NaN` in
certain uncommon scenarios. This is because in JavaScript:
* :code:`0 / 0 = NaN`
* :code:`N / 0 = Infinity` for 0 < N < Infinity.
For example, if a server has no money available and you want to hack some positive
amount from it, then the function would return :code:`Infinity` because
this would be impossible.

View File

@@ -1,9 +1,14 @@
weaken() Netscript Function
===========================
.. js:function:: weaken(hostname/ip)
.. js:function:: weaken(hostname/ip[, opts={}])
:param string hostname/ip: IP or hostname of the target server to weaken
:param object opts: Optional parameters for configuring function behavior. Properties:
* threads (*number*) - Number of threads to use for this function.
Must be less than or equal to the number of threads the script is running with.
:returns: The amount by which the target server's security level was decreased. This is equivalent to 0.05 multiplied
by the number of script threads
:RAM cost: 0.15 GB
@@ -18,3 +23,4 @@ weaken() Netscript Function
Example::
weaken("foodnstuff");
weaken("foodnstuff", { threads: 5 }); // Only use 5 threads to weaken

View File

@@ -1,13 +1,20 @@
attempt() Netscript Function
============================
.. js:function:: attempt(answer, fn[, hostname/ip=current ip])
.. js:function:: attempt(answer, fn[, hostname/ip=current ip, opts={}])
:param answer: Solution for the contract
:param string fn: Filename of the contract
:param string hostname/ip: Hostname or IP of the server containing the contract.
Optional. Defaults to current server if not provided
:param object opts: Optional parameters for configuring function behavior. Properties:
* returnReward (*boolean*) If truthy, then the function will return a string
that states the contract's reward when it is successfully solved.
Attempts to solve the Coding Contract with the provided solution.
:returns: Boolean indicating whether the solution was correct
:returns: Boolean indicating whether the solution was correct. If the :code:`returnReward`
option is configured, then the function will instead return a string. If the
contract is successfully solved, the string will contain a description of the
contract's reward. Otherwise, it will be an empty string.

View File

@@ -8,6 +8,7 @@ recruitMember() Netscript Function
Attempt to recruit a new gang member.
Possible reasons for failure:
* Cannot currently recruit a new member
* There already exists a member with the specified name

View File

@@ -3,7 +3,7 @@ hashCost() Netscript Function
.. warning:: This page contains spoilers for the game
.. js:function:: hashCost(upgName, upgTarget)
.. js:function:: hashCost(upgName)
:param string upgName: Name of upgrade to get the cost of. Must be an exact match

View File

@@ -8,8 +8,7 @@ later in the game
.. warning:: This page contains spoilers for the game
The Gang API is unlocked in BitNode-2. Currently, BitNode-2 is the only location
where the Gang mechanic is accessible. This may change in the future
The Gang mechanic and the Gang API are unlocked in BitNode-2.
**Gang API functions must be accessed through the 'gang' namespace**

View File

@@ -15,11 +15,15 @@ access even after you 'reset' by installing Augmentations
.. toctree::
:caption: API Functions:
getStockSymbols() <tixapi/getStockSymbols>
getStockPrice() <tixapi/getStockPrice>
getStockAskPrice() <tixapi/getStockAskPrice>
getStockBidPrice() <tixapi/getStockBidPrice>
getStockPosition() <tixapi/getStockPosition>
getStockMaxShares() <tixapi/getStockMaxShares>
getStockPurchaseCost() <tixapi/getStockPurchaseCost>
getStockSaleGain() <tixapi/getStockSaleGain>
buyStock() <tixapi/buyStock>
sellStock() <tixapi/sellStock>
shortStock() <tixapi/shortStock>

View File

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

View File

@@ -5,7 +5,7 @@ workForFaction() Netscript Function
:param string factionName: Name of faction to work for. CASE-SENSITIVE
:param string workType:
Type of work to perform for the faction
Type of work to perform for the faction:
* hacking/hacking contracts/hackingcontracts
* field/fieldwork/field work

View File

@@ -61,5 +61,5 @@ getInformation() Netscript Function
workChaExpGain: charisma exp gained from work,
workMoneyGain: money gained from work,
},
workRepGain: sl.getRepGain(),
workRepGain: Reputation gain rate when working for factions or companies
}

View File

@@ -1,5 +1,5 @@
getSleeveAugmentations() Netscript Function
=======================================
===========================================
.. js:function:: getSleeveAugmentations(sleeveNumber)

View File

@@ -6,7 +6,11 @@ getOrders() Netscript Function
:RAM cost: 2.5 GB
Returns your order book for the stock market. This is an object containing information
for all the Limit and Stop Orders you have in the stock market.
for all the :ref:`Limit and Stop Orders <gameplay_stock_market_order_types>`
you have in the stock market.
.. note:: This function isn't accessible until you have unlocked the ability to use
Limit and Stop Orders.
The object has the following structure::

View File

@@ -0,0 +1,12 @@
getStockAskPrice() Netscript Function
=====================================
.. js:function:: getStockAskPrice(sym)
:param string sym: Stock symbol
:RAM cost: 2 GB
Given a stock's symbol, returns the ask price of that stock (the symbol is a sequence
of two to four capital letters, **not** the name of the company to which that stock belongs).
See :ref:`gameplay_stock_market_spread` for details on what the ask price is.

View File

@@ -0,0 +1,12 @@
getStockBidPrice() Netscript Function
=====================================
.. js:function:: getStockBidPrice(sym)
:param string sym: Stock symbol
:RAM cost: 2 GB
Given a stock's symbol, returns the bid price of that stock (the symbol is a sequence
of two to four capital letters, **not** the name of the company to which that stock belongs).
See :ref:`gameplay_stock_market_spread` for details on what the bid price is.

View File

@@ -6,9 +6,12 @@ getStockPrice() Netscript Function
:param string sym: Stock symbol
:RAM cost: 2 GB
Returns the price of a stock, given its symbol (NOT the company name). The symbol is a sequence
of two to four capital letters.
Given a stock's symbol, returns the price of that stock (the symbol is a sequence
of two to four capital letters, **not** the name of the company to which that stock belongs).
.. note:: The stock's price is the average of its bid and ask price.
See :ref:`gameplay_stock_market_spread` for details on what this means.
Example::
getStockPrice("FISG");
getStockPrice("FSIG");

View File

@@ -0,0 +1,14 @@
getStockPurchaseCost() Netscript Function
=========================================
.. js:function:: getStockPurchaseCost(sym, shares, posType)
:param string sym: Stock symbol
:param number shares: Number of shares to purchase
:param string posType: Specifies whether the order is a "Long" or "Short" position.
The values "L" or "S" can also be used.
:RAM cost: 2 GB
Calculates and returns how much it would cost to buy a given number of
shares of a stock. This takes into account :ref:`spread <gameplay_stock_market_spread>`
and commission fees.

View File

@@ -0,0 +1,14 @@
getStockSaleGain() Netscript Function
=====================================
.. js:function:: getStockSaleGain(sym, shares, posType)
:param string sym: Stock symbol
: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>`
and commission fees.

View File

@@ -19,8 +19,10 @@ placeOrder() Netscript Function
NOT case-sensitive.
:RAM cost: 2.5 GB
Places an order on the stock market. This function only works for `Limit and Stop Orders <http://bitburner.wikia.com/wiki/Stock_Market#Order_Types>`_.
The ability to place limit and stop orders is **not** immediately available to the player and must be unlocked later on in the game.
Places an order on the stock market. This function only works
for :ref:`Limit and Stop Orders <gameplay_stock_market_order_types>`.
Returns true if the order is successfully placed, and false otherwise.
.. note:: The ability to place limit and stop orders is **not** immediately available to
the player and must be unlocked later on in the game.

View File

@@ -0,0 +1,23 @@
Tools & Resources
=================
Official Script Repository
--------------------------
There are plans to create an official repository of Bitburner scripts. As of right now,
this is not a priority and has not been started. However, if you'd like
to contribute scripts now, you can find the repository
`here <https://github.com/bitburner-official/bitburner-scripts>`_ and submit pull requests.
Visual Studio Code Extension
----------------------------
One user created a Bitburner extension for the Visual Studio Code (VSCode) editor.
This extension includes several features such as:
* Dynamic RAM calculation
* RAM Usage breakdown
* Typescript definition files with jsdoc comments
* Netscript syntax highlighting
You can find more information and download links
`on this reddit post <https://www.reddit.com/r/Bitburner/comments/bh48y2/visual_studio_code_ram_calculator_extra/>`_.

View File

@@ -25,7 +25,7 @@
ga('create', 'UA-100157497-1', 'auto');
ga('send', 'pageview');
</script>
<link rel="shortcut icon" href="favicon.ico"><link href="dist/vendor.css" rel="stylesheet"><link href="dist/engine.css" rel="stylesheet"></head>
<link rel="shortcut icon" href="favicon.ico"><link href="dist/vendor.css" rel="stylesheet"><link href="dist/engineStyle.css" rel="stylesheet"></head>
<body>
<div id="entire-game-container" style="visibility:hidden;">
<div id="mainmenu-container">
@@ -115,7 +115,7 @@
<div id="script-editor-filename-wrapper">
<p id="script-editor-filename-tag"> <strong style="background-color:#555;">Script name: </strong></p>
<input id="script-editor-filename" type="text" maxlength="30" tabindex="1"/>
<input id="script-editor-filename" type="text" maxlength="100" tabindex="1"/>
</div>
<div id="ace-editor"></div>
@@ -221,8 +221,6 @@
<!-- Single Faction info (when you select a faction from the Factions menu) -->
<div id="faction-container" class="generic-menupage-container"></div>
<div id="faction-augmentations-container" class="generic-menupage-container"></div>
<!-- Augmentations -->
<div id="augmentations-container" class="generic-menupage-container"></div>
@@ -279,53 +277,7 @@
</div>
<div id="stock-market-container" class="generic-menupage-container">
<p>
Welcome to the World Stock Exchange (WSE)! <br/><br/>
To begin trading, you must first purchase an account. WSE accounts will persist
after you 'reset' by installing Augmentations.
</p>
<a id="stock-market-buy-account" class="a-link-button-inactive"> Buy WSE Account </a>
<a id="stock-market-investopedia" class="a-link-button">Investopedia</a>
<h2> Trade Information eXchange (TIX) API </h2>
<p>
TIX, short for Trade Information eXchange, is the communications protocol supported by the WSE.
Purchasing access to the TIX API lets you write code to create your own algorithmic/automated
trading strategies.
<br/><br/>
If you purchase access to the TIX API, you will retain that access even after
you 'reset' by installing Augmentations.
</p>
<a id="stock-market-buy-tix-api" class="a-link-button-inactive">Buy Trade Information eXchange (TIX) API Access</a>
<h2> Four Sigma (4S) Market Data Feed </h2>
<p>
Four Sigma's (4S) Market Data Feed provides information about stocks
that will help your trading strategies.
<br/><br/>
If you purchase access to 4S Market Data and/or the 4S TIX API, you will
retain that access even after you 'reset' by installing Augmentations.
</p>
<a id="stock-market-buy-4s-data" class="a-link-button-inactive tooltip">
Buy 4S Market Data Feed
</a>
<div class="help-tip-big" id="stock-market-4s-data-help-tip">?</div>
<a id="stock-market-buy-4s-tix-api" class="a-link-button-inactive tooltip">
Buy 4S Market Data TIX API Access
</a>
<p id="stock-market-commission"> </p>
<a id="stock-market-mode" class="a-link-button tooltip"></a>
<a id="stock-market-expand-tickers" class="a-link-button tooltip">Expand tickers</a>
<a id="stock-market-collapse-tickers" class="a-link-button tooltip">Collapse tickers</a>
<br/><br/>
<input id="stock-market-watchlist-filter" class="text-input" type="text" placeholder="Filter Stocks by symbol (comma-separated list)"/>
<a id="stock-market-watchlist-filter-update" class="a-link-button"> Update Watchlist </a>
<ul id="stock-market-list" style="list-style:none;">
</ul>
<!-- React Component -->
</div>
<!-- Log Box -->
@@ -636,7 +588,7 @@
<p>If the game fails to load, consider <a href="?noScripts">killing all scripts</a></p>
</div>
</div>
<script type="text/javascript" src="dist/vendor.bundle.js"></script><script type="text/javascript" src="dist/engine.bundle.js"></script></body>
<script type="text/javascript" src="dist/vendor.bundle.js"></script><script type="text/javascript" src="dist/engine.bundle.js"></script><script type="text/javascript" src="dist/engineStyle.bundle.js"></script></body>
<!-- Misc Scripts -->
<script src="src/ThirdParty/raphael.min.js"></script>

1252
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,8 +9,9 @@
"@types/numeral": "0.0.25",
"@types/react": "^16.8.6",
"@types/react-dom": "^16.8.2",
"acorn": "^5.0.0",
"acorn": "^5.7.3",
"acorn-dynamic-import": "^2.0.0",
"acorn-walk": "^6.1.1",
"ajv": "^5.1.5",
"ajv-keywords": "^2.0.0",
"async": "^2.6.1",
@@ -44,12 +45,13 @@
"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",
"bundle-loader": "~0.5.0",
"chai": "^4.1.2",
"chai-as-promised": "^7.1.1",
"chai": "^4.2.0",
"css-loader": "^0.28.11",
"es6-promise-polyfill": "^1.1.1",
"eslint": "^4.19.1",
@@ -59,15 +61,18 @@
"i18n-webpack-plugin": "^1.0.0",
"istanbul": "^0.4.5",
"js-beautify": "^1.5.10",
"jsdom": "^15.0.0",
"jsdom-global": "^3.0.2",
"json5": "^1.0.1",
"less": "^3.9.0",
"less-loader": "^4.1.0",
"lodash": "^4.17.10",
"mini-css-extract-plugin": "^0.4.1",
"mkdirp": "^0.5.1",
"mocha": "^5.2.0",
"mocha-lcov-reporter": "^1.0.0",
"mocha": "^6.1.4",
"mochapack": "^1.1.1",
"node-sass": "^4.10.0",
"null-loader": "^1.0.0",
"raw-loader": "~0.5.0",
"sass-loader": "^7.0.3",
"script-loader": "~0.7.0",
@@ -106,13 +111,15 @@
"start:dev": "webpack-dev-server --progress --env.devServer --mode development",
"build": "webpack --mode production",
"build:dev": "webpack --mode development",
"build:test": "webpack --config webpack.config-test.js",
"lint": "npm run lint:typescript & npm run lint:javascript & npm run lint:style",
"lint:javascript": "eslint *.js ./src/**/*.js ./tests/**/*.js ./utils/**/*.js",
"lint:style": "stylelint ./css/*",
"lint:typescript": "tslint --project . --exclude **/*.d.ts --format stylish src/**/*.ts utils/**/*.ts",
"preinstall": "node ./scripts/engines-check.js",
"test": "mochapack --webpack-config webpack.config-test.js -r jsdom-global/register ./test/index.js",
"watch": "webpack --watch --mode production",
"watch:dev": "webpack --watch --mode development"
},
"version": "0.45.0"
"version": "0.47.0"
}

View File

@@ -1,328 +0,0 @@
import {workerScripts,
killWorkerScript} from "./NetscriptWorker";
import { Player } from "./Player";
import { getServer } from "./Server/ServerHelpers";
import {numeralWrapper} from "./ui/numeralFormat";
import {dialogBoxCreate} from "../utils/DialogBox";
import {createAccordionElement} from "../utils/uiHelpers/createAccordionElement";
import {arrayToString} from "../utils/helpers/arrayToString";
import {createElement} from "../utils/uiHelpers/createElement";
import {createProgressBarText} from "../utils/helpers/createProgressBarText";
import {exceptionAlert} from "../utils/helpers/exceptionAlert";
import {getElementById} from "../utils/uiHelpers/getElementById";
import {logBoxCreate} from "../utils/LogBox";
import {formatNumber,
convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
import {removeChildrenFromElement} from "../utils/uiHelpers/removeChildrenFromElement";
import {removeElement} from "../utils/uiHelpers/removeElement";
import {roundToTwo} from "../utils/helpers/roundToTwo";
import {Page, routing} from "./ui/navigationTracking";
/* {
* serverName: {
* header: Server Header Element
* panel: Server Panel List (ul) element
* scripts: {
* script id: Ref to Script information
* }
* }
* ...
*/
let ActiveScriptsUI = {};
let 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, '&nbsp;');
};
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.log("WARNING: 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.log("ERROR: 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));
}
//Update the ActiveScriptsItems array
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 in case someone decides to start a bunch of scripts all at once...
let 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);
}
}
if (!routing.isOn(Page.ActiveScripts)) {return;}
var total = 0;
for (var i = 0; i < workerScripts.length; ++i) {
try {
total += updateActiveScriptsItemContent(workerScripts[i]);
} catch(e) {
exceptionAlert(e);
}
}
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;
}
//Updates the content of the given item in the Active Scripts list
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, "&nbsp;");
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, "&nbsp;");
//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, "&nbsp;");
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, "&nbsp;");
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};

View File

@@ -1,5 +1,5 @@
import { IMap } from "./types";
import { post } from "./ui/postToTerminal";
import { IMap } from "./types";
import { post } from "./ui/postToTerminal";
export let Aliases: IMap<string> = {};
export let GlobalAliases: IMap<string> = {};
@@ -20,7 +20,7 @@ export function loadGlobalAliases(saveString: string): void {
}
}
//Print all aliases to terminal
// Prints all aliases to terminal
export function printAliases(): void {
for (var name in Aliases) {
if (Aliases.hasOwnProperty(name)) {
@@ -34,7 +34,7 @@ export function printAliases(): void {
}
}
//True if successful, false otherwise
// Returns true if successful, false otherwise
export function parseAliasDeclaration(dec: string, global: boolean=false) {
var re = /^([_|\w|!|%|,|@]+)="(.+)"$/;
var matches = dec.match(re);
@@ -90,8 +90,10 @@ export function removeAlias(name: string): boolean {
return false;
}
//Returns the original string with any aliases substituted in
//Aliases only applied to "whole words", one level deep
/**
* Returns the original string with any aliases substituted in.
* Aliases are only applied to "whole words", one level deep
*/
export function substituteAliases(origCommand: string): string {
const commandArray = origCommand.split(" ");
if (commandArray.length > 0){

View File

@@ -10,6 +10,7 @@ import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviv
interface IConstructorParams {
info: string;
isSpecial?: boolean;
moneyCost: number;
name: string;
prereqs?: string[];
@@ -62,6 +63,9 @@ export class Augmentation {
// Description of what this Aug is and what it does
info: string = "";
// Any Augmentation not immediately available in BitNode-1 is special (e.g. Bladeburner augs)
isSpecial: boolean = false;
// Augmentation level - for repeatable Augs like NeuroFlux Governor
level: number = 0;
@@ -90,6 +94,10 @@ export class Augmentation {
this.baseCost = params.moneyCost * CONSTANTS.AugmentationCostMultiplier * BitNodeMultipliers.AugmentationMoneyCost;
this.startingCost = this.baseCost;
if (params.isSpecial) {
this.isSpecial = true;
}
this.level = 0;
// Set multipliers

View File

@@ -49,6 +49,7 @@ export let AugmentationNames: IMap<string> = {
PCDNINeuralNetwork: "PC Direct-Neural Interface NeuroNet Injector",
ADRPheromone1: "ADR-V1 Pheromone Gene",
ADRPheromone2: "ADR-V2 Pheromone Gene",
ShadowsSimulacrum: "The Shadow's Simulacrum",
HacknetNodeCPUUpload: "Hacknet Node CPU Architecture Neural-Upload",
HacknetNodeCacheUpload: "Hacknet Node Cache Architecture Neural-Upload",
HacknetNodeNICUpload: "Hacknet Node NIC Architecture Neural-Upload",

View 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}</>
)
}

View 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>
</>
)
}
}

View 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)"
/>
</>
)
}

View 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}</>
);
}

View 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>
</>
)
}

View 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>
)
}

View 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>
)
}
}

View File

@@ -25,232 +25,232 @@ 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",
"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 " +
"modifications or mechanics.<br><br>" +
"Destroying this BitNode will give you Source-File 1, or if you already have " +
"this Source-File it will upgrade its level up to a maximum of 3. This Source-File " +
"lets the player start with 32GB of RAM on his/her home computer when entering a " +
"new BitNode, and also increases all of the player's multipliers by:<br><br>" +
"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
"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 " +
"factions quickly rose to the top of the modern world.<br><br>" +
"In this BitNode:<br><br>" +
"Your hacking level is reduced by 20%<br>" +
"The growth rate and maximum amount of money available on servers are significantly decreased<br>" +
"The amount of money gained from crimes and Infiltration is tripled<br>" +
"Certain Factions (Slum Snakes, Tetrads, The Syndicate, The Dark Army, Speakers for the Dead, " +
"NiteSec, The Black Hand) give the player the ability to form and manage their own gangs. These gangs " +
"will earn the player money and reputation with the corresponding Faction<br>" +
"Every Augmentation in the game will be available through the Factions listed above<br>" +
"For every Faction NOT listed above, reputation gains are halved<br>" +
"You will no longer gain passive reputation with Factions<br><br>" +
"Destroying this BitNode will give you Source-File 2, or if you already have this Source-File it will " +
"upgrade its level up to a maximum of 3. This Source-File 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%");
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 " +
"the world into a corporatocracy, and it never looked back. Now, the privileged " +
"elite will happily bankrupt their own countrymen, decimate their own community, " +
"and evict their neighbors from houses in their desperate bid to increase their wealth.<br><br>" +
"In this BitNode you can create and manage your own corporation. Running a successful corporation " +
"has the potential of generating massive profits. All other forms of income are reduced by 75%. Furthermore: <br><br>" +
"The price and reputation cost of all Augmentations is tripled<br>" +
"The starting and maximum amount of money on servers is reduced by 75%<br>" +
"Server growth rate is reduced by 80%<br>" +
"You now only need 75 favour with a faction in order to donate to it, rather than 150<br><br>" +
"Destroying this BitNode will give you Source-File 3, or if you already have this Source-File it will " +
"upgrade its level up to a maximum of 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%");
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 " +
"for all stats are reduced. Most methods of earning money will now give significantly less.<br><br>" +
"In this BitNode you will gain access to a new set of Netscript Functions known as Singularity Functions. " +
"These functions allow you to control most aspects of the game through scripts, including working for factions/companies, " +
"purchasing/installing Augmentations, and creating programs.<br><br>" +
"Destroying this BitNode will give you Source-File 4, or if you already have this Source-File it will " +
"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", "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 " +
"by 1's and 0's. They were wrong.<br><br>" +
"In this BitNode:<br><br>" +
"The base security level of servers is doubled<br>" +
"The starting money on servers is halved, but the maximum money remains the same<br>" +
"Most methods of earning money now give significantly less<br>" +
"Infiltration gives 50% more reputation and money<br>" +
"Corporations have 50% lower valuations and are therefore less profitable<br>" +
"Augmentations are more expensive<br>" +
"Hacking experience gain rates are reduced<br><br>" +
"Destroying this BitNode will give you Source-File 5, or if you already have this Source-File it will " +
"upgrade its level up to a maximum of 3. This Source-File grants you 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. <br><br>" +
"In addition, this Source-File will unlock the getBitNodeMultipliers() Netscript function, " +
"and will also raise all of your hacking-related multipliers by:<br><br>" +
"Level 1: 8%<br>" +
"Level 2: 12%<br>" +
"Level 3: 14%");
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 " +
"the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, and more intelligent " +
"than the humans that had created them.<br><br>" +
"In this BitNode you will be able to access the Bladeburner Division at the NSA, which provides a new mechanic " +
"for progression. Furthermore:<br><br>" +
"Hacking and Hacknet Nodes will be less profitable<br>" +
"Your hacking level is reduced by 65%<br>" +
"Hacking experience gain from scripts is reduced by 75%<br>" +
"Corporations have 80% lower valuations and are therefore less profitable<br>" +
"Working for companies is 50% less profitable<br>" +
"Crimes and Infiltration are 25% less profitable<br><br>" +
"Destroying this BitNode will give you Source-File 6, or if you already have this Source-File it will upgrade " +
"its level up to a maximum of 3. 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%");
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. " +
"Many argue that this was the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, " +
"and more intelligent than the humans that had created them.<br><br>" +
"In this BitNode you will be able to access the Bladeburner API, which allows you to access Bladeburner " +
"functionality through Netscript. Furthermore: <br><br>" +
"The rank you gain from Bladeburner contracts/operations is reduced by 40%<br>" +
"Bladeburner skills cost twice as many skill points<br>" +
"Augmentations are 3x more expensive<br>" +
"Hacking and Hacknet Nodes will be significantly less profitable<br>" +
"Your hacking level is reduced by 65%<br>" +
"Hacking experience gain from scripts is reduced by 75%<br>" +
"Corporations have 80% lower valuations and are therefore less profitable<br>" +
"Working for companies is 50% less profitable<br>" +
"Crimes and Infiltration are 25% less profitable<br><br>" +
"Destroying this BitNode will give you Source-File 7, or if you already have this Source-File it will upgrade " +
"its level up to a maximum of 3. 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%");
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>" +
"The only way to earn money is by trading on the stock market<br>" +
"You start with a WSE membership and access to the TIX API<br>" +
"You are able to short stocks and place different types of orders (limit/stop)<br>" +
"You can immediately donate to factions to gain reputation<br><br>" +
"Destroying this BitNode will give you Source-File 8, or if you already have this Source-File it will " +
"upgrade its level up to a maximum of 3. 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%");
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 " +
"abandoned the project and dissociated themselves from it.<br><br>" +
"This BitNode unlocks the Hacknet Server, an upgraded version of the Hacknet Node. Hacknet Servers generate " +
"hashes, which can be spent on a variety of different upgrades.<br><br>" +
"In this BitNode:<br><br>" +
"Your stats are significantly decreased<br>" +
"You cannnot purchase additional servers<br>" +
"Hacking is significantly less profitable<br><br>" +
"Destroying this BitNode will give you Source-File 9, or if you already have this Source-File it will " +
"upgrade its level up to a maximum of 3. 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)");
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 " +
"human consciousness. Mankind had finally achieved immortality - at least for those that could afford it.<br><br>" +
"This BitNode unlocks Sleeve technology. Sleeve technology allows you to:<br><br>" +
"1. Re-sleeve: Purchase and transfer your consciousness into a new body<br>" +
"2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks synchronously<br><br>" +
"In this BitNode:<br><br>" +
"Your stats are significantly decreased<br>" +
"All methods of gaining money are half as profitable (except Stock Market)<br>" +
"Purchased servers are more expensive, have less max RAM, and a lower maximum limit<br>" +
"Augmentations are 5x as expensive and require twice as much reputation<br><br>" +
"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.",
"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>" +
"In many countries, the high cost of trying to deal with the civil disorder bankrupted the governments. In all of this chaos and confusion, hackers " +
"were able to steal billions of dollars from the world's largest electronic banks, prompting an international banking crisis as " +
"governments were unable to bail out insolvent banks. Now, the world is slowly crumbling in the middle of the biggest economic crisis of all time.<br><br>" +
"In this BitNode:<br><br>" +
"Your hacking stat and experience gain are halved<br>" +
"The starting and maximum amount of money available on servers is significantly decreased<br>" +
"The growth rate of servers is significantly reduced<br>" +
"Weakening a server is twice as effective<br>" +
"Company wages are decreased by 50%<br>" +
"Corporation valuations are 99% lower and are therefore significantly less profitable<br>" +
"Hacknet Node production is significantly decreased<br>" +
"Crime and Infiltration are more lucrative<br>" +
"Augmentations are twice as expensive<br><br>" +
"Destroying this BitNode will give you Source-File 11, or if you already have this Source-File it will " +
"upgrade its level up to a maximum of 3. 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%");
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");
}
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 " +
"modifications or mechanics.<br><br>" +
"Destroying this BitNode will give you Source-File 1, or if you already have " +
"this Source-File it will upgrade its level up to a maximum of 3. This Source-File " +
"lets the player start with 32GB of RAM on his/her home computer when entering a " +
"new BitNode, and also increases all of the player's multipliers by:<br><br>" +
"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
"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 " +
"factions quickly rose to the top of the modern world.<br><br>" +
"In this BitNode:<br><br>" +
"Your hacking level is reduced by 20%<br>" +
"The growth rate and maximum amount of money available on servers are significantly decreased<br>" +
"The amount of money gained from crimes and Infiltration is tripled<br>" +
"Certain Factions (Slum Snakes, Tetrads, The Syndicate, The Dark Army, Speakers for the Dead, " +
"NiteSec, The Black Hand) give the player the ability to form and manage their own gangs. These gangs " +
"will earn the player money and reputation with the corresponding Faction<br>" +
"Every Augmentation in the game will be available through the Factions listed above<br>" +
"For every Faction NOT listed above, reputation gains are halved<br>" +
"You will no longer gain passive reputation with Factions<br><br>" +
"Destroying this BitNode will give you Source-File 2, or if you already have this Source-File it will " +
"upgrade its level up to a maximum of 3. 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%");
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 " +
"the world into a corporatocracy, and it never looked back. Now, the privileged " +
"elite will happily bankrupt their own countrymen, decimate their own community, " +
"and evict their neighbors from houses in their desperate bid to increase their wealth.<br><br>" +
"In this BitNode you can create and manage your own corporation. Running a successful corporation " +
"has the potential of generating massive profits. All other forms of income are reduced by 75%. Furthermore: <br><br>" +
"The price and reputation cost of all Augmentations is tripled<br>" +
"The starting and maximum amount of money on servers is reduced by 75%<br>" +
"Server growth rate is reduced by 80%<br>" +
"You now only need 75 favour with a faction in order to donate to it, rather than 150<br><br>" +
"Destroying this BitNode will give you Source-File 3, or if you already have this Source-File it will " +
"upgrade its level up to a maximum of 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%");
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 " +
"for all stats are reduced. Most methods of earning money will now give significantly less.<br><br>" +
"In this BitNode you will gain access to a new set of Netscript Functions known as Singularity Functions. " +
"These functions allow you to control most aspects of the game through scripts, including working for factions/companies, " +
"purchasing/installing Augmentations, and creating programs.<br><br>" +
"Destroying this BitNode will give you Source-File 4, or if you already have this Source-File it will " +
"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",
"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 " +
"by 1's and 0's. They were wrong.<br><br>" +
"In this BitNode:<br><br>" +
"The base security level of servers is doubled<br>" +
"The starting money on servers is halved, but the maximum money remains the same<br>" +
"Most methods of earning money now give significantly less<br>" +
"Infiltration gives 50% more reputation and money<br>" +
"Corporations have 50% lower valuations and are therefore less profitable<br>" +
"Augmentations are more expensive<br>" +
"Hacking experience gain rates are reduced<br><br>" +
"Destroying this BitNode will give you Source-File 5, or if you already have this Source-File it will " +
"upgrade its level up to a maximum of 3. This Source-File grants you 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. <br><br>" +
"In addition, this Source-File will unlock the getBitNodeMultipliers() Netscript function, " +
"and will also raise all of your hacking-related multipliers by:<br><br>" +
"Level 1: 8%<br>" +
"Level 2: 12%<br>" +
"Level 3: 14%");
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 " +
"the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, and more intelligent " +
"than the humans that had created them.<br><br>" +
"In this BitNode you will be able to access the Bladeburner Division at the NSA, which provides a new mechanic " +
"for progression. Furthermore:<br><br>" +
"Hacking and Hacknet Nodes will be less profitable<br>" +
"Your hacking level is reduced by 65%<br>" +
"Hacking experience gain from scripts is reduced by 75%<br>" +
"Corporations have 80% lower valuations and are therefore less profitable<br>" +
"Working for companies is 50% less profitable<br>" +
"Crimes and Infiltration are 25% less profitable<br><br>" +
"Destroying this BitNode will give you Source-File 6, or if you already have this Source-File it will upgrade " +
"its level up to a maximum of 3. 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%");
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. " +
"Many argue that this was the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, " +
"and more intelligent than the humans that had created them.<br><br>" +
"In this BitNode you will be able to access the Bladeburner API, which allows you to access Bladeburner " +
"functionality through Netscript. Furthermore: <br><br>" +
"The rank you gain from Bladeburner contracts/operations is reduced by 40%<br>" +
"Bladeburner skills cost twice as many skill points<br>" +
"Augmentations are 3x more expensive<br>" +
"Hacking and Hacknet Nodes will be significantly less profitable<br>" +
"Your hacking level is reduced by 65%<br>" +
"Hacking experience gain from scripts is reduced by 75%<br>" +
"Corporations have 80% lower valuations and are therefore less profitable<br>" +
"Working for companies is 50% less profitable<br>" +
"Crimes and Infiltration are 25% less profitable<br><br>" +
"Destroying this BitNode will give you Source-File 7, or if you already have this Source-File it will upgrade " +
"its level up to a maximum of 3. 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%");
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>" +
"The only way to earn money is by trading on the stock market<br>" +
"You start with a WSE membership and access to the TIX API<br>" +
"You are able to short stocks and place different types of orders (limit/stop)<br>" +
"You can immediately donate to factions to gain reputation<br><br>" +
"Destroying this BitNode will give you Source-File 8, or if you already have this Source-File it will " +
"upgrade its level up to a maximum of 3. 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%");
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 " +
"abandoned the project and dissociated themselves from it.<br><br>" +
"This BitNode unlocks the Hacknet Server, an upgraded version of the Hacknet Node. Hacknet Servers generate " +
"hashes, which can be spent on a variety of different upgrades.<br><br>" +
"In this BitNode:<br><br>" +
"Your stats are significantly decreased<br>" +
"You cannnot purchase additional servers<br>" +
"Hacking is significantly less profitable<br><br>" +
"Destroying this BitNode will give you Source-File 9, or if you already have this Source-File it will " +
"upgrade its level up to a maximum of 3. 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)");
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 " +
"human consciousness. Mankind had finally achieved immortality - at least for those that could afford it.<br><br>" +
"This BitNode unlocks Sleeve technology. Sleeve technology allows you to:<br><br>" +
"1. Re-sleeve: Purchase and transfer your consciousness into a new body<br>" +
"2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks synchronously<br><br>" +
"In this BitNode:<br><br>" +
"Your stats are significantly decreased<br>" +
"All methods of gaining money are half as profitable (except Stock Market)<br>" +
"Purchased servers are more expensive, have less max RAM, and a lower maximum limit<br>" +
"Augmentations are 5x as expensive and require twice as much reputation<br><br>" +
"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.",
"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>" +
"In many countries, the high cost of trying to deal with the civil disorder bankrupted the governments. In all of this chaos and confusion, hackers " +
"were able to steal billions of dollars from the world's largest electronic banks, prompting an international banking crisis as " +
"governments were unable to bail out insolvent banks. Now, the world is slowly crumbling in the middle of the biggest economic crisis of all time.<br><br>" +
"In this BitNode:<br><br>" +
"Your hacking stat and experience gain are halved<br>" +
"The starting and maximum amount of money available on servers is significantly decreased<br>" +
"The growth rate of servers is significantly reduced<br>" +
"Weakening a server is twice as effective<br>" +
"Company wages are decreased by 50%<br>" +
"Corporation valuations are 99% lower and are therefore significantly less profitable<br>" +
"Hacknet Node production is significantly decreased<br>" +
"Crime and Infiltration are more lucrative<br>" +
"Augmentations are twice as expensive<br><br>" +
"Destroying this BitNode will give you Source-File 11, or if you already have this Source-File it will " +
"upgrade its level up to a maximum of 3. 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%");
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");
export function initBitNodeMultipliers(p: IPlayer) {
if (p.bitNodeN == null) {

File diff suppressed because it is too large Load Diff

View File

@@ -1,29 +1,35 @@
import {Engine} from "./engine";
import { setTimeoutRef } from "./utils/SetTimeoutRef";
import {removeChildrenFromElement} from "../utils/uiHelpers/removeChildrenFromElement";
import {createElement} from "../utils/uiHelpers/createElement";
import {exceptionAlert} from "../utils/helpers/exceptionAlert";
import {isString} from "../utils/helpers/isString";
import { Engine } from "./engine";
import { setTimeoutRef } from "./utils/SetTimeoutRef";
var cinematicTextFlag = false;
import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement";
import { createElement } from "../utils/uiHelpers/createElement";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { isString } from "../utils/helpers/isString";
//Lines must be an array of strings
function writeCinematicText(lines) {
export let cinematicTextFlag = false;
/**
* Print a message using a hacking-style "typing" effect.
* Note that this clears the UI so that the text from this is the only thing visible.
*
* @param lines {string[]} Array of strings to print, where each element is a separate line
*/
export function writeCinematicText(lines) {
cinematicTextFlag = true;
if (lines.constructor !== Array) {
throw new Error("Invalid non-array argument passed into writeCinematicText()");
}
//We'll reuse the 'Red Pill' content
// Reuse the 'Red Pill' content
Engine.loadCinematicTextContent();
var container = document.getElementById("cinematic-text-container");
const container = document.getElementById("cinematic-text-container");
container.style.width = "75%";
if (container == null) {throw new Error("Could not find cinematic-text-container for writeCinematicText()");}
removeChildrenFromElement(container);
for (var i = 0; i < lines.length; ++i) {
for (let i = 0; i < lines.length; ++i) {
if (!isString(lines[i])) {
throw new Error("Invalid non-string element in 'lines' argument. writeCinematicText() failed");
}
@@ -45,11 +51,11 @@ function writeCinematicTextRecurse(lines, lineNumber=0) {
function writeCinematicTextLine(line) {
return new Promise(function(resolve, reject) {
var container = document.getElementById("cinematic-text-container");
var pElem = document.createElement("p");
const container = document.getElementById("cinematic-text-container");
const pElem = document.createElement("p");
container.appendChild(pElem);
var promise = writeCinematicTextLetter(pElem, line, 0);
const promise = writeCinematicTextLetter(pElem, line, 0);
promise.then(function(res) {
resolve(res);
}, function(e) {
@@ -61,14 +67,15 @@ function writeCinematicTextLine(line) {
function writeCinematicTextLetter(pElem, line, i=0) {
return new Promise(function(resolve, reject) {
setTimeoutRef(function() {
const textToShow = line.substring(0, i);
if (i >= line.length) {
var textToShow = line.substring(0, i);
pElem.innerHTML = textToShow;
return resolve(true);
}
var textToShow = line.substring(0, i);
pElem.innerHTML = textToShow + "<span class='typed-cursor'> &#9608; </span>";
var promise = writeCinematicTextLetter(pElem, line, i+1);
const promise = writeCinematicTextLetter(pElem, line, i+1);
promise.then(function(res) {
resolve(res);
}, function(e) {
@@ -96,5 +103,3 @@ function cinematicTextEnd() {
}));
});
}
export {cinematicTextFlag, writeCinematicText};

View File

@@ -1,12 +1,16 @@
import { CodingContract,
CodingContractRewardType,
CodingContractTypes } from "./CodingContracts";
import { Factions } from "./Faction/Factions";
import { Player } from "./Player";
import { AllServers } from "./Server/AllServers";
import { GetServerByHostname } from "./Server/ServerHelpers";
import {
CodingContract,
CodingContractRewardType,
CodingContractTypes
} from "./CodingContracts";
import { Factions } from "./Faction/Factions";
import { Player } from "./Player";
import { AllServers } from "./Server/AllServers";
import { GetServerByHostname } from "./Server/ServerHelpers";
import { SpecialServerNames } from "./Server/SpecialServerIps";
import { getRandomInt } from "../utils/helpers/getRandomInt";
import { getRandomInt } from "../utils/helpers/getRandomInt";
export function generateRandomContract() {
// First select a random problem type
@@ -127,14 +131,15 @@ function getRandomReward() {
});
switch (reward.type) {
case CodingContractRewardType.FactionReputation:
case CodingContractRewardType.FactionReputation: {
// Get a random faction that player is a part of. That
// faction must allow hacking contracts
var numFactions = factionsThatAllowHacking.length;
var randFaction = factionsThatAllowHacking[getRandomInt(0, numFactions - 1)];
reward.name = randFaction;
break;
case CodingContractRewardType.CompanyReputation:
}
case CodingContractRewardType.CompanyReputation: {
const allJobs = Object.keys(Player.jobs);
if (allJobs.length > 0) {
reward.name = allJobs[getRandomInt(0, allJobs.length - 1)];
@@ -142,6 +147,7 @@ function getRandomReward() {
reward.type = CodingContractRewardType.Money;
}
break;
}
default:
break;
}
@@ -157,7 +163,9 @@ function getRandomServer() {
// An infinite loop shouldn't ever happen, but to be safe we'll use
// a for loop with a limited number of tries
for (let i = 0; i < 200; ++i) {
if (randServer.purchasedByPlayer === false) { break; }
if (!randServer.purchasedByPlayer && randServer.hostname !== SpecialServerNames.WorldDaemon) {
break;
}
randIndex = getRandomInt(0, servers.length - 1);
randServer = AllServers[servers[randIndex]];
}

View File

@@ -1,18 +1,22 @@
import { codingContractTypesMetadata,
DescriptionFunc,
GeneratorFunc,
SolverFunc } from "./data/codingcontracttypes";
import {
codingContractTypesMetadata,
DescriptionFunc,
GeneratorFunc,
SolverFunc
} from "./data/codingcontracttypes";
import { IMap } from "./types";
import { IMap } from "./types";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
import {
Generic_fromJSON,
Generic_toJSON,
Reviver
} from "../utils/JSONReviver";
import { KEY } from "../utils/helpers/keyCodes";
import { createElement } from "../utils/uiHelpers/createElement";
import { createPopup } from "../utils/uiHelpers/createPopup";
import { removeElementById } from "../utils/uiHelpers/removeElementById";
/* tslint:disable:no-magic-numbers completed-docs max-classes-per-file no-console */
/* Represents different types of problems that a Coding Contract can have */
@@ -198,6 +202,7 @@ export class CodingContract {
}
},
placeholder: "Enter Solution here",
width: "50%",
}) as HTMLInputElement;
solveBtn = createElement("a", {
class: "a-link-button",

View File

@@ -3,98 +3,44 @@
*
* Constants for specific mechanics or features will NOT be here.
*/
import {IMap} from "./types";
import { IMap } from "./types";
export let CONSTANTS: IMap<any> = {
Version: "0.46.1",
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
//the player will have this level assuming no multipliers. Multipliers can cause skills to go above this.
/** 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
* the player will have this level assuming no multipliers. Multipliers can cause skills to go above this.
*/
MaxSkillLevel: 975,
//Milliseconds per game cycle
// Milliseconds per game cycle
MilliPerCycle: 200,
//How much reputation is needed to join a megacorporation's faction
// How much reputation is needed to join a megacorporation's faction
CorpFactionRepRequirement: 200e3,
/* Base costs */
// Base RAM costs
BaseCostFor1GBOfRamHome: 32000,
BaseCostFor1GBOfRamServer: 55000, //1 GB of RAM
// Cost to travel to another city
TravelCost: 200e3,
/* Faction and Company favor */
BaseFavorToDonate: 150,
DonateMoneyToRepDivisor: 1e6,
// Faction and Company favor-related things
BaseFavorToDonate: 150,
DonateMoneyToRepDivisor: 1e6,
FactionReputationToFavorBase: 500,
FactionReputationToFavorMult: 1.02,
CompanyReputationToFavorBase: 500,
CompanyReputationToFavorMult: 1.02,
/* Augmentation */
//NeuroFlux Governor cost multiplier as you level up
// NeuroFlux Governor Augmentation cost multiplier
NeuroFluxGovernorLevelMult: 1.14,
/* Netscript Constants */
//RAM Costs for different commands
ScriptBaseRamCost: 1.6,
ScriptDomRamCost: 25,
ScriptWhileRamCost: 0,
ScriptForRamCost: 0,
ScriptIfRamCost: 0,
ScriptHackRamCost: 0.1,
ScriptHackAnalyzeRamCost: 1,
ScriptGrowRamCost: 0.15,
ScriptGrowthAnalyzeRamCost: 1,
ScriptWeakenRamCost: 0.15,
ScriptScanRamCost: 0.2,
ScriptPortProgramRamCost: 0.05,
ScriptRunRamCost: 1.0,
ScriptExecRamCost: 1.3,
ScriptSpawnRamCost: 2.0,
ScriptScpRamCost: 0.6,
ScriptKillRamCost: 0.5, //Kill and killall
ScriptHasRootAccessRamCost: 0.05,
ScriptGetHostnameRamCost: 0.05, //getHostname() and getIp()
ScriptGetHackingLevelRamCost: 0.05, //getHackingLevel()
ScriptGetMultipliersRamCost: 4.0, //getHackingMultipliers() and getBitNodeMultipliers()
ScriptGetServerRamCost: 0.1,
ScriptFileExistsRamCost: 0.1,
ScriptIsRunningRamCost: 0.1,
ScriptHacknetNodesRamCost: 4.0, //Base cost for accessing Hacknet Node API
ScriptHNUpgLevelRamCost: 0.4,
ScriptHNUpgRamRamCost: 0.6,
ScriptHNUpgCoreRamCost: 0.8,
ScriptGetStockRamCost: 2.0,
ScriptBuySellStockRamCost: 2.5,
ScriptGetPurchaseServerRamCost: 0.25,
ScriptPurchaseServerRamCost: 2.25,
ScriptGetPurchasedServerLimit: 0.05,
ScriptGetPurchasedServerMaxRam: 0.05,
ScriptRoundRamCost: 0.05,
ScriptReadWriteRamCost: 1.0,
ScriptArbScriptRamCost: 1.0, // Functions that apply to all scripts regardless of args
ScriptGetScriptRamCost: 0.1,
ScriptGetHackTimeRamCost: 0.05,
ScriptGetFavorToDonate: 0.10,
ScriptCodingContractBaseRamCost:10,
ScriptSleeveBaseRamCost: 4,
NumNetscriptPorts: 20,
ScriptSingularityFn1RamCost: 1,
ScriptSingularityFn2RamCost: 2,
ScriptSingularityFn3RamCost: 3,
ScriptSingularityFnRamMult: 2, // Multiplier for RAM cost outside of BN-4
ScriptGangApiBaseRamCost: 4,
ScriptBladeburnerApiBaseRamCost: 4,
NumNetscriptPorts: 20,
//Server constants
// Server-related constants
HomeComputerMaxRam: 1073741824, // 2 ^ 30
ServerBaseGrowthRate: 1.03, // Unadjusted Growth rate
ServerMaxGrowthRate: 1.0035, // Maximum possible growth rate (max rate accounting for server security)
@@ -102,49 +48,50 @@ export let CONSTANTS: IMap<any> = {
ServerWeakenAmount: 0.05, // Amount by which server's security decreases when weakened
PurchasedServerLimit: 25,
PurchasedServerMaxRam: 1048576, //2^20
PurchasedServerMaxRam: 1048576, // 2^20
//Augmentation Constants
AugmentationCostMultiplier: 5, //Used for balancing costs without having to readjust every Augmentation cost
AugmentationRepMultiplier: 2.5, //Used for balancing rep cost without having to readjust every value
MultipleAugMultiplier: 1.9,
// Augmentation Constants
AugmentationCostMultiplier: 5, // Used for balancing costs without having to readjust every Augmentation cost
AugmentationRepMultiplier: 2.5, // Used for balancing rep cost without having to readjust every value
MultipleAugMultiplier: 1.9,
//How much a TOR router costs
TorRouterCost: 200000,
// TOR Router
TorRouterCost: 200e3,
//Infiltration constants
// Infiltration
InfiltrationBribeBaseAmount: 100e3, //Amount per clearance level
InfiltrationMoneyValue: 5e3, //Convert "secret" value to money
InfiltrationMoneyValue: 5e3, //Convert "secret" value to money
InfiltrationRepValue: 1.4, //Convert "secret" value to faction reputation
InfiltrationExpPow: 0.8,
//Stock market constants
WSEAccountCost: 200e6,
TIXAPICost: 5e9,
MarketData4SCost: 1e9,
// Stock market
WSEAccountCost: 200e6,
TIXAPICost: 5e9,
MarketData4SCost: 1e9,
MarketDataTixApi4SCost: 25e9,
StockMarketCommission: 100e3,
StockMarketCommission: 100e3,
//Hospital/Health
// Hospital/Health
HospitalCostPerHp: 100e3,
//Intelligence-related constants
IntelligenceCrimeWeight: 0.05, //Weight for how much int affects crime success rates
IntelligenceInfiltrationWeight: 0.1, //Weight for how much int affects infiltration success rates
// Intelligence-related constants
IntelligenceCrimeWeight: 0.05, // Weight for how much int affects crime success rates
IntelligenceInfiltrationWeight: 0.1, // Weight for how much int affects infiltration success rates
IntelligenceCrimeBaseExpGain: 0.001,
IntelligenceProgramBaseExpGain: 500, //Program required hack level divided by this to determine int exp gain
IntelligenceTerminalHackBaseExpGain: 200, //Hacking exp divided by this to determine int exp gain
IntelligenceProgramBaseExpGain: 500, // Program required hack level divided by this to determine int exp gain
IntelligenceTerminalHackBaseExpGain: 200, // Hacking exp divided by this to determine int exp gain
IntelligenceSingFnBaseExpGain: 0.002,
IntelligenceClassBaseExpGain: 0.000001,
IntelligenceHackingMissionBaseExpGain: 0.03, //Hacking Mission difficulty multiplied by this to get exp gain
IntelligenceHackingMissionBaseExpGain: 0.03, // Hacking Mission difficulty multiplied by this to get exp gain
//Hacking Missions
HackingMissionRepToDiffConversion: 10000, //Faction rep is divided by this to get mission difficulty
HackingMissionRepToRewardConversion: 7, //Faction rep divided byt his to get mission rep reward
HackingMissionSpamTimeIncrease: 25000, //How much time limit increase is gained when conquering a Spam Node (ms)
HackingMissionTransferAttackIncrease: 1.05, //Multiplier by which the attack for all Core Nodes is increased when conquering a Transfer Node
HackingMissionMiscDefenseIncrease: 1.05, //The amount by which every misc node's defense is multiplied when one is conquered
HackingMissionDifficultyToHacking: 135, //Difficulty is multiplied by this to determine enemy's "hacking" level (to determine effects of scan/attack, etc)
// Hacking Missions
// TODO Move this into Hacking Mission implementation
HackingMissionRepToDiffConversion: 10000, // Faction rep is divided by this to get mission difficulty
HackingMissionRepToRewardConversion: 7, // Faction rep divided byt his to get mission rep reward
HackingMissionSpamTimeIncrease: 25000, // How much time limit increase is gained when conquering a Spam Node (ms)
HackingMissionTransferAttackIncrease: 1.05, // Multiplier by which the attack for all Core Nodes is increased when conquering a Transfer Node
HackingMissionMiscDefenseIncrease: 1.05, // The amount by which every misc node's defense is multiplied when one is conquered
HackingMissionDifficultyToHacking: 135, // Difficulty is multiplied by this to determine enemy's "hacking" level (to determine effects of scan/attack, etc)
HackingMissionHowToPlay: "Hacking missions are a minigame that, if won, will reward you with faction reputation.<br><br>" +
"In this game you control a set of Nodes and use them to try and defeat an enemy. Your Nodes " +
"are colored blue, while the enemy's are red. There are also other nodes on the map colored gray " +
@@ -192,7 +139,7 @@ export let CONSTANTS: IMap<any> = {
"-Miscellaneous Nodes slowly raise their defense over time<br><br>" +
"-Nodes slowly regenerate health over time.",
/* Time Constants */
// Time-related constants
MillisecondsPer20Hours: 72000000,
GameCyclesPer20Hours: 72000000 / 200,
@@ -220,7 +167,7 @@ export let CONSTANTS: IMap<any> = {
MillisecondsPerFiveMinutes: 300000,
GameCyclesPerFiveMinutes: 300000 / 200,
/* Player Work / Action related Constants */
// Player Work & Action
FactionWorkHacking: "Faction Hacking Work",
FactionWorkField: "Faction Field Work",
FactionWorkSecurity: "Faction Security Work",
@@ -263,26 +210,29 @@ export let CONSTANTS: IMap<any> = {
CrimeAssassination: "assassinate a high-profile target",
CrimeHeist: "pull off the ultimate heist",
/* Coding Contract Constants */
CodingContractBaseFactionRepGain: 2500,
CodingContractBaseCompanyRepGain: 4000,
CodingContractBaseMoneyGain: 75e6,
// Coding Contract
// TODO Move this into Coding contract impelmentation?
CodingContractBaseFactionRepGain: 2500,
CodingContractBaseCompanyRepGain: 4000,
CodingContractBaseMoneyGain: 75e6,
// BitNode/Source-File related stuff
TotalNumBitNodes: 24,
LatestUpdate:
`
v0.46.1
* Added a very rudimentary directory system to the Terminal
** Details here: https://bitburner.readthedocs.io/en/latest/basicgameplay/terminal.html#filesystem-directories
v0.47.1
* 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
* Added numHashes(), hashCost(), and spendHashes() functions to the Netscript Hacknet Node API
* 'Generate Coding Contract' hash upgrade is now more expensive
* 'Generate Coding Contract' hash upgrade now generates the contract randomly on the server, rather than on home computer
* The cost of selling hashes for money no longer increases each time
* Selling hashes for money now costs 4 hashes (in exchange for $1m)
* Bug Fix: Hacknet Node earnings should work properly when game is inactive/offline
* Bug Fix: Duplicate Sleeve augmentations are now properly reset when switching to a new BitNode
* 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)
`
}

View File

@@ -521,15 +521,18 @@ Industry.prototype.process = function(marketCycles=1, state, company) {
}
// Process production, purchase, and import/export of materials
var res = this.processMaterials(marketCycles, company);
this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]);
this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]);
let res = this.processMaterials(marketCycles, company);
if (Array.isArray(res)) {
this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]);
this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]);
}
// Process creation, production & sale of products
res = this.processProducts(marketCycles, company);
this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]);
this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]);
if (Array.isArray(res)) {
this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]);
this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]);
}
}
// Process change in demand and competition for this industry's materials
@@ -846,7 +849,7 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
sellAmt = (sellAmt * SecsPerMarketCycle * marketCycles);
sellAmt = Math.min(mat.qty, sellAmt);
if (sellAmt < 0) {
console.log("sellAmt calculated to be negative");
console.warn(`sellAmt calculated to be negative for ${matName} in ${city}`);
mat.sll = 0;
continue;
}
@@ -899,9 +902,11 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
break;
}
//Make sure theres enough space in warehouse
// Make sure theres enough space in warehouse
if (expWarehouse.sizeUsed >= expWarehouse.size) {
return; //Warehouse at capacity
// Warehouse at capacity. Exporting doesnt
// affect revenue so just return 0's
return [0, 0];
} else {
var maxAmt = Math.floor((expWarehouse.size - expWarehouse.sizeUsed) / MaterialSizes[matName]);
amt = Math.min(maxAmt, amt);
@@ -1463,7 +1468,6 @@ function Employee(params={}) {
this.hap = params.happiness ? params.happiness : getRandomInt(50, 100);
this.ene = params.energy ? params.energy : getRandomInt(50, 100);
this.age = params.age ? params.age : getRandomInt(20, 50);
this.int = params.intelligence ? params.intelligence : getRandomInt(10, 50);
this.cha = params.charisma ? params.charisma : getRandomInt(10, 50);
this.exp = params.experience ? params.experience : getRandomInt(10, 50);
@@ -1482,13 +1486,7 @@ function Employee(params={}) {
Employee.prototype.process = function(marketCycles=1, office) {
var gain = 0.003 * marketCycles,
det = gain * Math.random();
this.age += gain;
this.exp += gain;
if (this.age > 150) {
this.int -= det;
this.eff -= det;
this.cha -= det;
}
// Employee salaries slowly go up over time
this.cyclesUntilRaise -= marketCycles;
@@ -1577,7 +1575,6 @@ Employee.prototype.createUI = function(panel, corporation, industry) {
innerHTML:"Morale: " + formatNumber(this.mor, 3) + "<br>" +
"Happiness: " + formatNumber(this.hap, 3) + "<br>" +
"Energy: " + formatNumber(this.ene, 3) + "<br>" +
"Age: " + formatNumber(this.age, 3) + "<br>" +
"Intelligence: " + formatNumber(effInt, 3) + "<br>" +
"Charisma: " + formatNumber(effCha, 3) + "<br>" +
"Experience: " + formatNumber(this.exp, 3) + "<br>" +

View File

@@ -495,8 +495,6 @@ export class IndustryOffice extends BaseReactComponent {
<br />
Energy: {numeralWrapper.format(this.state.employee.ene, nf)}
<br />
Age: {numeralWrapper.format(this.state.employee.age, nf)}
<br />
Intelligence: {numeralWrapper.format(effInt, nf)}
<br />
Charisma: {numeralWrapper.format(effCha, nf)}

View File

@@ -1,38 +1,44 @@
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
import { CodingContractTypes } from "./CodingContracts";
import { generateContract,
generateRandomContract,
generateRandomContractOnHome } from "./CodingContractGenerator";
import { Companies } from "./Company/Companies";
import { Company } from "./Company/Company";
import { Programs } from "./Programs/Programs";
import { Factions } from "./Faction/Factions";
import { Player } from "./Player";
import { PlayerOwnedSourceFile } from "./SourceFile/PlayerOwnedSourceFile";
import { AllServers } from "./Server/AllServers";
import { GetServerByHostname } from "./Server/ServerHelpers";
import { hackWorldDaemon } from "./RedPill";
import { StockMarket,
SymbolToStockMap } from "./StockMarket/StockMarket";
import { Stock } from "./StockMarket/Stock";
import { Terminal } from "./Terminal";
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
import { CodingContractTypes } from "./CodingContracts";
import {
generateContract,
generateRandomContract,
generateRandomContractOnHome
} from "./CodingContractGenerator";
import { Companies } from "./Company/Companies";
import { Company } from "./Company/Company";
import { Programs } from "./Programs/Programs";
import { Factions } from "./Faction/Factions";
import { Player } from "./Player";
import { PlayerOwnedSourceFile } from "./SourceFile/PlayerOwnedSourceFile";
import { AllServers } from "./Server/AllServers";
import { GetServerByHostname } from "./Server/ServerHelpers";
import { hackWorldDaemon } from "./RedPill";
import { StockMarket, SymbolToStockMap } from "./StockMarket/StockMarket";
import { Stock } from "./StockMarket/Stock";
import { Terminal } from "./Terminal";
import { numeralWrapper } from "./ui/numeralFormat";
import { numeralWrapper } from "./ui/numeralFormat";
import { dialogBoxCreate } from "../utils/DialogBox";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { createElement } from "../utils/uiHelpers/createElement";
import { createOptionElement } from "../utils/uiHelpers/createOptionElement";
import { getSelectText } from "../utils/uiHelpers/getSelectData";
import { removeElementById } from "../utils/uiHelpers/removeElementById";
import { dialogBoxCreate } from "../utils/DialogBox";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { createElement } from "../utils/uiHelpers/createElement";
import { createOptionElement } from "../utils/uiHelpers/createOptionElement";
import { getSelectText } from "../utils/uiHelpers/getSelectData";
import { removeElementById } from "../utils/uiHelpers/removeElementById";
import React from "react";
import ReactDOM from "react-dom";
import ReactDOM from "react-dom";
const Component = React.Component;
const validSFN = [1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12];
// Update as additional BitNodes get implemented
const validSFN = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
// Some dev menu buttons just add a lot of something for convenience
const tonsPP = 1e27;
const tonsP = 1e12;
class ValueAdjusterComponent extends Component {
constructor(props) {
@@ -41,7 +47,7 @@ class ValueAdjusterComponent extends Component {
this.setValue = this.setValue.bind(this);
}
setValue(event) {
this.setState({ value: event.target.value });
this.setState({ value: parseFloat(event.target.value) });
}
render() {
const { title, add, subtract, reset } = this.props;
@@ -124,7 +130,6 @@ class DevMenuComponent extends Component {
this.setState({ codingcontract: event.target.value });
}
addMoney(n) {
return function() {
Player.gainMoney(n);
@@ -186,14 +191,20 @@ class DevMenuComponent extends Component {
}
}
modifyKarma(modifier) {
return function(amt) {
Player.karma += (amt * modifier);
}
}
tonsOfExp() {
Player.gainHackingExp(1e27);
Player.gainStrengthExp(1e27);
Player.gainDefenseExp(1e27);
Player.gainDexterityExp(1e27);
Player.gainAgilityExp(1e27);
Player.gainCharismaExp(1e27);
Player.gainIntelligenceExp(1e27);
Player.gainHackingExp(tonsPP);
Player.gainStrengthExp(tonsPP);
Player.gainDefenseExp(tonsPP);
Player.gainDexterityExp(tonsPP);
Player.gainAgilityExp(tonsPP);
Player.gainCharismaExp(tonsPP);
Player.gainIntelligenceExp(tonsPP);
Player.updateSkillLevels();
}
@@ -237,6 +248,12 @@ class DevMenuComponent extends Component {
}
}
resetKarma() {
return function() {
Player.karma = 0;
}
}
enableIntelligence() {
if(Player.intelligence === 0) {
Player.intelligence = 1;
@@ -296,7 +313,7 @@ class DevMenuComponent extends Component {
tonsOfRep() {
for (const i in Factions) {
Factions[i].playerReputation = 1e27;
Factions[i].playerReputation = tonsPP;
}
}
@@ -308,7 +325,7 @@ class DevMenuComponent extends Component {
tonsOfFactionFavor() {
for (const i in Factions) {
Factions[i].favor = 1e27;
Factions[i].favor = tonsPP;
}
}
@@ -454,7 +471,7 @@ class DevMenuComponent extends Component {
tonsOfRepCompanies() {
for (const c in Companies) {
Companies[c].playerReputation = 1e12;
Companies[c].playerReputation = tonsP;
}
}
@@ -466,7 +483,7 @@ class DevMenuComponent extends Component {
tonsOfFavorCompanies() {
for (const c in Companies) {
Companies[c].favor = 1e12;
Companies[c].favor = tonsP;
}
}
@@ -491,7 +508,7 @@ class DevMenuComponent extends Component {
addTonsBladeburnerRank() {
if (!!Player.bladeburner) {
Player.bladeburner.changeRank(1e12);
Player.bladeburner.changeRank(tonsP);
}
}
@@ -511,13 +528,13 @@ class DevMenuComponent extends Component {
addTonsBladeburnerCycles() {
if (!!Player.bladeburner) {
Player.bladeburner.storedCycles += 1e12;
Player.bladeburner.storedCycles += tonsP;
}
}
addTonsGangCycles() {
if (!!Player.gang) {
Player.gang.storedCycles = 1e12;
Player.gang.storedCycles = tonsP;
}
}
@@ -537,7 +554,7 @@ class DevMenuComponent extends Component {
addTonsCorporationCycles() {
if (!!Player.corporation) {
Player.corporation.storedCycles = 1e12;
Player.corporation.storedCycles = tonsP;
}
}
@@ -644,16 +661,16 @@ class DevMenuComponent extends Component {
let sourceFiles = [];
validSFN.forEach( i => sourceFiles.push(
<tr key={'sf-'+i}>
<td><span className="text">SF-{i}:</span></td>
<td>
<button className="std-button touch-right" onClick={this.setSF(i, 0)}>0</button>
<button className="std-button touch-sides" onClick={this.setSF(i, 1)}>1</button>
<button className="std-button touch-sides" onClick={this.setSF(i, 2)}>2</button>
<button className="std-button touch-left" onClick={this.setSF(i, 3)}>3</button>
</td>
</tr>
));
<tr key={'sf-'+i}>
<td><span className="text">SF-{i}:</span></td>
<td>
<button className="std-button touch-right" onClick={this.setSF(i, 0)}>0</button>
<button className="std-button touch-sides" onClick={this.setSF(i, 1)}>1</button>
<button className="std-button touch-sides" onClick={this.setSF(i, 2)}>2</button>
<button className="std-button touch-left" onClick={this.setSF(i, 3)}>3</button>
</td>
</tr>
));
@@ -713,11 +730,11 @@ class DevMenuComponent extends Component {
<span className="text text-center">Hacking:</span>
</td>
<td>
<ValueAdjusterComponent
title="hacking exp"
add={this.modifyExp('hacking', 1)}
subtract={this.modifyExp('hacking', -1)}
reset={this.resetExperience('hacking')}
<ValueAdjusterComponent
title="hacking exp"
add={this.modifyExp('hacking', 1)}
subtract={this.modifyExp('hacking', -1)}
reset={this.resetExperience('hacking')}
/>
</td>
</tr>
@@ -726,7 +743,7 @@ class DevMenuComponent extends Component {
<span className="text text-center">Strength:</span>
</td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="strength exp"
add={this.modifyExp('strength', 1)}
subtract={this.modifyExp('strength', -1)}
@@ -739,7 +756,7 @@ class DevMenuComponent extends Component {
<span className="text text-center">Defense:</span>
</td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="defense exp"
add={this.modifyExp('defense', 1)}
subtract={this.modifyExp('defense', -1)}
@@ -752,7 +769,7 @@ class DevMenuComponent extends Component {
<span className="text text-center">Dexterity:</span>
</td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="dexterity exp"
add={this.modifyExp('dexterity', 1)}
subtract={this.modifyExp('dexterity', -1)}
@@ -765,7 +782,7 @@ class DevMenuComponent extends Component {
<span className="text text-center">Agility:</span>
</td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="agility exp"
add={this.modifyExp('agility', 1)}
subtract={this.modifyExp('agility', -1)}
@@ -778,7 +795,7 @@ class DevMenuComponent extends Component {
<span className="text text-center">Charisma:</span>
</td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="charisma exp"
add={this.modifyExp('charisma', 1)}
subtract={this.modifyExp('charisma', -1)}
@@ -791,7 +808,7 @@ class DevMenuComponent extends Component {
<span className="text text-center">Intelligence:</span>
</td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="intelligence exp"
add={this.modifyExp('intelligence', 1)}
subtract={this.modifyExp('intelligence', -1)}
@@ -805,6 +822,19 @@ class DevMenuComponent extends Component {
<button className="std-button" onClick={this.disableIntelligence}>Disable</button>
</td>
</tr>
<tr>
<td>
<span className="text text-center">Karma:</span>
</td>
<td>
<ValueAdjusterComponent
title="karma"
add={this.modifyKarma(1)}
subtract={this.modifyKarma(-1)}
reset={this.resetKarma()}
/>
</td>
</tr>
</tbody>
</table>
</div>
@@ -832,7 +862,7 @@ class DevMenuComponent extends Component {
<span className="text">Reputation:</span>
</td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="reputation"
add={this.modifyFactionRep(1)}
subtract={this.modifyFactionRep(-1)}
@@ -845,7 +875,7 @@ class DevMenuComponent extends Component {
<span className="text">Favor:</span>
</td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="favor"
add={this.modifyFactionFavor(1)}
subtract={this.modifyFactionFavor(-1)}
@@ -979,7 +1009,7 @@ class DevMenuComponent extends Component {
<tr>
<td><span className="text">Reputation:</span></td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="reputation"
add={this.modifyCompanyRep(1)}
subtract={this.modifyCompanyRep(-1)}
@@ -990,7 +1020,7 @@ class DevMenuComponent extends Component {
<tr>
<td><span className="text">Favor:</span></td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="favor"
add={this.modifyCompanyFavor(1)}
subtract={this.modifyCompanyFavor(-1)}
@@ -1028,7 +1058,7 @@ class DevMenuComponent extends Component {
<td><span className="text">Rank:</span></td>
<td><button className="std-button" onClick={this.addTonsBladeburnerRank}>Tons</button></td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="rank"
add={this.modifyBladeburnerRank(1)}
subtract={this.modifyBladeburnerRank(-1)}
@@ -1040,7 +1070,7 @@ class DevMenuComponent extends Component {
<td><span className="text">Cycles:</span></td>
<td><button className="std-button" onClick={this.addTonsBladeburnerCycles}>Tons</button></td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="cycles"
add={this.modifyBladeburnerCycles(1)}
subtract={this.modifyBladeburnerCycles(-1)}
@@ -1064,7 +1094,7 @@ class DevMenuComponent extends Component {
<td><span className="text">Cycles:</span></td>
<td><button className="std-button" onClick={this.addTonsGangCycles}>Tons</button></td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="cycles"
add={this.modifyGangCycles(1)}
subtract={this.modifyGangCycles(-1)}
@@ -1088,7 +1118,7 @@ class DevMenuComponent extends Component {
<td><span className="text">Cycles:</span></td>
<td><button className="std-button" onClick={this.addTonsCorporationCycles}>Tons</button></td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="cycles"
add={this.modifyCorporationCycles(1)}
subtract={this.modifyCorporationCycles(-1)}
@@ -1121,7 +1151,7 @@ class DevMenuComponent extends Component {
{contractTypes}
</select>
<button className="std-button" onClick={this.specificContract}>Generate Specified Contract Type on Home Comp</button>
</td>
</tr>
</tbody>
@@ -1185,7 +1215,6 @@ class DevMenuComponent extends Component {
}
}
const devMenuContainerId = "dev-menu-container";
export function createDevMenu() {
@@ -1199,11 +1228,11 @@ export function createDevMenu() {
id: devMenuContainerId,
});
const entireGameContainer = document.getElementById("entire-game-container");
if (entireGameContainer == null) {
throw new Error("Could not find entire-game-container DOM element");
}
entireGameContainer.appendChild(devMenuContainer);
const entireGameContainer = document.getElementById("entire-game-container");
if (entireGameContainer == null) {
throw new Error("Could not find entire-game-container DOM element");
}
entireGameContainer.appendChild(devMenuContainer);
ReactDOM.render(<DevMenuComponent />, devMenuContainer);
}

View File

@@ -97,16 +97,6 @@ export class Faction {
return [favorGain, rep];
}
//Adds all Augmentations to this faction.
addAllAugmentations(augs: object): void {
this.augmentations.length = 0;
for (const name in augs) {
if (augs.hasOwnProperty(name)) {
this.augmentations.push(name);
}
}
}
/**
* Serialize the current object to a JSON save state.
*/

7
src/Faction/FactionHelpers.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
import { Augmentation } from "../Augmentation/Augmentation";
import { Faction } from "../Faction/Faction";
export declare function getNextNeurofluxLevel(): number;
export declare function hasAugmentationPrereqs(aug: Augmentation): boolean;
export declare function purchaseAugmentationBoxCreate(aug: Augmentation, fac: Faction): void;
export declare function purchaseAugmentation(aug: Augmentation, fac: Faction, sing?: boolean): void;

View File

@@ -1,694 +0,0 @@
import { Augmentations } from "../Augmentation/Augmentations";
import { PlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../Constants";
import { Engine } from "../engine";
import { Faction } from "./Faction";
import { Factions } from "./Factions";
import { FactionInfos } from "./FactionInfo";
import { HackingMission, setInMission } from "../Missions";
import { Player } from "../Player";
import { PurchaseAugmentationsOrderSetting } from "../Settings/SettingEnums";
import { Settings } from "../Settings/Settings";
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
import { createSleevePurchasesFromCovenantPopup } from "../PersonObjects/Sleeve/SleeveCovenantPurchases";
import {Page, routing} from "../ui/navigationTracking";
import {numeralWrapper} from "../ui/numeralFormat";
import {dialogBoxCreate} from "../../utils/DialogBox";
import {factionInvitationBoxCreate} from "../../utils/FactionInvitationBox";
import {removeChildrenFromElement} from "../../utils/uiHelpers/removeChildrenFromElement";
import {createElement} from "../../utils/uiHelpers/createElement";
import {Reviver, Generic_toJSON,
Generic_fromJSON} from "../../utils/JSONReviver";
import {formatNumber} from "../../utils/StringHelperFunctions";
import {yesNoBoxCreate, yesNoBoxGetYesButton,
yesNoBoxGetNoButton, yesNoBoxClose} from "../../utils/YesNoBox";
function inviteToFaction(faction) {
if (Settings.SuppressFactionInvites) {
faction.alreadyInvited = true;
Player.factionInvitations.push(faction.name);
if (routing.isOn(Page.Factions)) {
Engine.loadFactionsContent();
}
} else {
factionInvitationBoxCreate(faction);
}
}
function joinFaction(faction) {
faction.isMember = true;
Player.factions.push(faction.name);
const factionInfo = faction.getInfo();
//Determine what factions you are banned from now that you have joined this faction
for(const i in factionInfo.enemies) {
const enemy = factionInfo.enemies[i];
if (Factions[enemy] instanceof Faction) {
Factions[enemy].isBanned = true;
}
}
}
//Displays the HTML content for a specific faction
function displayFactionContent(factionName) {
var faction = Factions[factionName];
if (faction == null) {
throw new Error("Invalid factionName passed into displayFactionContent: " + factionName);
}
if (!faction.isMember) {
throw new Error("Not a member of this faction, cannot display faction information");
}
var factionInfo = faction.getInfo();
removeChildrenFromElement(Engine.Display.factionContent);
var elements = [];
//Header and faction info
elements.push(createElement("h1", {
innerText:factionName
}));
elements.push(createElement("pre", {
innerHTML:"<i>" + factionInfo.infoText + "</i>"
}));
elements.push(createElement("p", {
innerText:"---------------",
}));
//Faction reputation and favor
var favorGain = faction.getFavorGain();
if (favorGain.length != 2) {favorGain = 0;}
favorGain = favorGain[0];
elements.push(createElement("p", {
innerText: "Reputation: " + formatNumber(faction.playerReputation, 4),
tooltip:"You will earn " + formatNumber(favorGain, 0) +
" faction favor upon resetting after installing an Augmentation"
}))
elements.push(createElement("p", {
innerText:"---------------",
}));
elements.push(createElement("p", {
innerText:"Faction Favor: " + formatNumber(faction.favor, 0),
tooltip:"Faction favor increases the rate at which " +
"you earn reputation for this faction by 1% per favor. Faction favor " +
"is gained whenever you reset after installing an Augmentation. The amount of " +
"favor you gain depends on how much reputation you have with the faction"
}));
elements.push(createElement("p", {
innerText:"---------------",
}));
//Faction Work Description Text
elements.push(createElement("pre", {
id:"faction-work-description-text",
innerText:"Perform work/carry out assignments for your faction to help further its cause! By doing so " +
"you will earn reputation for your faction. You will also gain reputation passively over time, " +
"although at a very slow rate. Earning reputation will allow you to purchase Augmentations " +
"through this faction, which are powerful upgrades that enhance your abilities. Note that you cannot " +
"use your terminal or create scripts when you are performing a task!"
}));
elements.push(createElement("br"));
//Hacking Mission Option
var hackMissionDiv = createElement("div", { class:"faction-work-div" });
var hackMissionDivWrapper = createElement("div", {class:"faction-work-div-wrapper"});
hackMissionDiv.appendChild(hackMissionDivWrapper);
hackMissionDivWrapper.appendChild(createElement("a", {
class:"a-link-button", innerText:"Hacking Mission",
clickListener:()=>{
Engine.loadMissionContent();
var mission = new HackingMission(faction.playerReputation, faction);
setInMission(true, mission); //Sets inMission flag to true
mission.init();
return false;
}
}));
hackMissionDivWrapper.appendChild(createElement("p", {
innerText:"Attempt a hacking mission for your faction. " +
"A mission is a mini game that, if won, earns you " +
"significant reputation with this faction. (Recommended hacking level: 200+)"
}));
elements.push(hackMissionDiv);
//Hacking Contracts Option
var hackDiv = createElement("div", { class:"faction-work-div", });
var hackDivWrapper = createElement("div", {class:"faction-work-div-wrapper"});
hackDiv.appendChild(hackDivWrapper);
hackDivWrapper.appendChild(createElement("a", {
class:"std-button", innerText:"Hacking Contracts",
clickListener:()=>{
Player.startFactionHackWork(faction);
return false;
}
}));
hackDivWrapper.appendChild(createElement("p", {
innerText:"Complete hacking contracts for your faction. " +
"Your effectiveness, which determines how much " +
"reputation you gain for this faction, is based on your hacking skill. " +
"You will gain hacking exp."
}));
elements.push(hackDiv);
//Field Work Option
var fieldWorkDiv = createElement("div", { class:"faction-work-div" });
var fieldWorkDivWrapper = createElement("div", {class:"faction-work-div-wrapper"});
fieldWorkDiv.appendChild(fieldWorkDivWrapper);
fieldWorkDivWrapper.appendChild(createElement("a", {
class:"std-button", innerText:"Field Work",
clickListener:()=>{
Player.startFactionFieldWork(faction);
return false;
}
}));
fieldWorkDivWrapper.appendChild(createElement("p", {
innerText:"Carry out field missions for your faction. " +
"Your effectiveness, which determines how much " +
"reputation you gain for this faction, is based on all of your stats. " +
"You will gain exp for all stats."
}));
elements.push(fieldWorkDiv);
//Security Work Option
var securityWorkDiv = createElement("div", { class:"faction-work-div" });
var securityWorkDivWrapper = createElement("div", {class:"faction-work-div-wrapper"});
securityWorkDiv.appendChild(securityWorkDivWrapper);
securityWorkDivWrapper.appendChild(createElement("a", {
class:"std-button", innerText:"Security Work",
clickListener:()=>{
Player.startFactionSecurityWork(faction);
return false;
}
}));
securityWorkDivWrapper.appendChild(createElement("p", {
innerText:"Serve in a security detail for your faction. " +
"Your effectiveness, which determines how much " +
"reputation you gain for this faction, is based on your combat stats. " +
"You will gain exp for all combat stats."
}));
elements.push(securityWorkDiv);
//Donate for reputation
var donateDiv = createElement("div", { class:"faction-work-div" });
var donateDivWrapper = createElement("div", {class:"faction-work-div-wrapper"});
donateDiv.appendChild(donateDivWrapper);
var donateRepGain = createElement("p", {
innerText:"This donation will result in 0.000 reputation gain"
});
var donateAmountInput = createElement("input", {
class: "text-input", placeholder:"Donation amount",
inputListener:()=>{
let amt = 0;
if(donateAmountInput.value !== "") {
amt = parseFloat(donateAmountInput.value);
}
if (isNaN(amt)) {
donateRepGain.innerText = "Invalid donate amount entered!";
} else {
var repGain = amt / CONSTANTS.DonateMoneyToRepDivisor * Player.faction_rep_mult;
donateRepGain.innerText = "This donation will result in " +
formatNumber(repGain, 3) + " reputation gain";
}
},
});
donateDivWrapper.appendChild(createElement("a", {
class:"std-button", innerText:"Donate Money",
clickListener:()=>{
var amt = parseFloat(donateAmountInput.value);
if (isNaN(amt) || amt < 0) {
dialogBoxCreate("Invalid amount entered!");
} else if (Player.money.lt(amt)) {
dialogBoxCreate("You cannot afford to donate this much money!");
} else {
Player.loseMoney(amt);
var repGain = amt / CONSTANTS.DonateMoneyToRepDivisor * Player.faction_rep_mult;
faction.playerReputation += repGain;
dialogBoxCreate("You just donated " + numeralWrapper.format(amt, "$0.000a") + " to " +
faction.name + " to gain " + formatNumber(repGain, 3) + " reputation");
displayFactionContent(factionName);
}
}
}));
donateDivWrapper.appendChild(donateAmountInput);
donateDivWrapper.appendChild(donateRepGain);
elements.push(donateDiv);
//Purchase Augmentations
const purchaseAugmentationsDiv = createElement("div", { class: "faction-work-div", display: "inline" });
const purchaseAugmentationsDivWrapper = createElement("div", { class: "faction-work-div-wrapper" });
purchaseAugmentationsDiv.appendChild(purchaseAugmentationsDivWrapper);
purchaseAugmentationsDivWrapper.appendChild(createElement("a", {
class:"std-button",
innerText:"Purchase Augmentations",
margin: "5px",
clickListener:()=>{
Engine.hideAllContent();
Engine.Display.factionAugmentationsContent.style.display = "block";
displayFactionAugmentations(factionName);
return false;
}
}));
purchaseAugmentationsDivWrapper.appendChild(createElement("pre", {
innerHTML: "<br>As your reputation with this faction rises, you will " +
"unlock Augmentations, which you can purchase to enhance " +
"your abilities.<br><br>"
}));
elements.push(purchaseAugmentationsDiv);
//Gang (BitNode-2)
if (Player.bitNodeN == 2 && (factionName == "Slum Snakes" || factionName == "Tetrads" ||
factionName == "The Syndicate" || factionName == "The Dark Army" || factionName == "Speakers for the Dead" ||
factionName == "NiteSec" || factionName == "The Black Hand")) {
//Set everything else to invisible
hackMissionDiv.style.display = "none";
hackDiv.style.display = "none";
fieldWorkDiv.style.display = "none";
securityWorkDiv.style.display = "none";
donateDiv.style.display = "none";
//Create the 'Manage Gang' button
var gangDiv = createElement("div", {
id:"faction-gang-div", class:"faction-work-div", display:"inline"
});
var gangDivWrapper = createElement("div", {class:"faction-work-div-wrapper"});
gangDiv.appendChild(gangDivWrapper);
gangDivWrapper.appendChild(createElement("a", {
class:"a-link-button", innerText:"Manage Gang",
clickListener: () => {
if (!Player.inGang()) {
// Determine whether this is a hacking gang
let hacking = false;
if (factionName === "NiteSec" || factionName === "The Black Hand") { hacking = true; }
// Configure Yes/No buttons for the pop-up
var yesBtn = yesNoBoxGetYesButton(), noBtn = yesNoBoxGetNoButton();
yesBtn.innerHTML = "Create Gang";
noBtn.innerHTML = "Cancel";
yesBtn.addEventListener("click", () => {
Player.startGang(factionName, hacking);
document.getElementById("world-menu-header").click();
document.getElementById("world-menu-header").click();
Engine.loadGangContent();
yesNoBoxClose();
});
noBtn.addEventListener("click", () => {
yesNoBoxClose();
});
// Pop-up text
let gangTypeText = "";
if (hacking) {
gangTypeText = "This is a HACKING gang. Members in this gang will have different tasks than COMBAT gangs. " +
"Compared to combat gangs, progression with hacking gangs is more straightforward as territory warfare " +
"is not as important.<br><br>";
} else {
gangTypeText = "This is a COMBAT gang. Members in this gang will have different tasks than HACKING gangs. " +
"Compared to hacking gangs, progression with combat gangs can be more difficult as territory management " +
"is more important. However, well-managed combat gangs can progress faster than hacking ones.<br><br>";
}
yesNoBoxCreate(`Would you like to create a new Gang with ${factionName}?<br><br>` +
"Note that this will prevent you from creating a Gang with any other Faction until " +
"this BitNode is destroyed.<br><br>" +
gangTypeText +
"Other than hacking vs combat, there are NO differences between the Factions you can " +
"create a Gang with, and each of these Factions have all Augmentations available.");
} else {
Engine.loadGangContent();
}
}
}));
gangDivWrapper.appendChild(createElement("p", {
innerText:"Create and manage a gang for this Faction. " +
"Gangs will earn you money and faction reputation."
}));
//Manage Gang button goes before Faction work stuff
elements.splice(7, 1, gangDiv);
if (Player.inGang() && Player.gang.facName != factionName) {
//If the player has a gang but its not for this faction
gangDiv.style.display = "none";
}
//Display all elements
for (var i = 0; i < elements.length; ++i) {
Engine.Display.factionContent.appendChild(elements[i]);
}
return;
}
// Purchase Sleeves from Covenant
if (factionName === "The Covenant" && Player.bitNodeN >= 10 && SourceFileFlags[10]) {
const covenantPurchaseSleevesDiv = createElement("div", { class: "faction-work-div", display: "inline" });
const covenantPurchaseSleevesDivWrapper = createElement("div", { class: "faction-work-div-wrapper" });
covenantPurchaseSleevesDiv.appendChild(covenantPurchaseSleevesDivWrapper);
covenantPurchaseSleevesDivWrapper.appendChild(createElement("a", {
class: "std-button",
innerText: "Purchase Duplicate Sleeves",
clickListener: () => {
createSleevePurchasesFromCovenantPopup(Player);
}
}));
covenantPurchaseSleevesDivWrapper.appendChild(createElement("p", {
innerText: "Purchase Duplicate Sleeves. These are permanent! You can purchase up to 5 total.",
}));
elements.push(covenantPurchaseSleevesDiv);
}
// Determine if actions should be possible
donateDiv.style.display = faction.favor >= Math.floor(CONSTANTS.BaseFavorToDonate * BitNodeMultipliers.RepToDonateToFaction) ? "inline" : "none";
hackMissionDiv.style.display = factionInfo.offerHackingMission ? "inline": "none";
hackDiv.style.display = factionInfo.offerHackingWork ? "inline" : "none";
fieldWorkDiv.style.display = factionInfo.offerFieldWork ? "inline" : "none";
securityWorkDiv.style.display = factionInfo.offerSecurityWork ? "inline" : "none";
//Display all elements
for (var i = 0; i < elements.length; ++i) {
Engine.Display.factionContent.appendChild(elements[i]);
}
}
function displayFactionAugmentations(factionName) {
var faction = Factions[factionName];
if (faction == null) {
throw new Error("Could not find faction " + factionName + " in displayFactionAugmentations");
}
removeChildrenFromElement(Engine.Display.factionAugmentationsContent);
var elements = [];
//Back button
elements.push(createElement("a", {
innerText:"Back", class:"a-link-button",
clickListener:()=>{
Engine.loadFactionContent();
displayFactionContent(factionName);
return false;
}
}));
//Header text
elements.push(createElement("h1", {innerText:"Faction Augmentations"}));
elements.push(createElement("p", {
id:"faction-augmentations-page-desc",
innerHTML:"Lists all Augmentations that are available to purchase from " + factionName + "<br><br>" +
"Augmentations are powerful upgrades that will enhance your abilities."
}));
elements.push(createElement("br"));
elements.push(createElement("br"));
//Augmentations List
var augmentationsList = createElement("ul");
//Sort buttons
const sortByCostBtn = createElement("a", {
innerText:"Sort by Cost", class:"a-link-button",
clickListener:()=>{
Settings.PurchaseAugmentationsOrder = PurchaseAugmentationsOrderSetting.Cost;
var augs = faction.augmentations.slice();
augs.sort((augName1, augName2)=>{
var aug1 = Augmentations[augName1], aug2 = Augmentations[augName2];
if (aug1 == null || aug2 == null) {
throw new Error("Invalid Augmentation Names");
}
return aug1.baseCost - aug2.baseCost;
});
removeChildrenFromElement(augmentationsList);
createFactionAugmentationDisplayElements(augmentationsList, augs, faction);
}
});
const sortByRepBtn = createElement("a", {
innerText:"Sort by Reputation", class:"a-link-button",
clickListener:()=>{
Settings.PurchaseAugmentationsOrder = PurchaseAugmentationsOrderSetting.Reputation;
var augs = faction.augmentations.slice();
augs.sort((augName1, augName2)=>{
var aug1 = Augmentations[augName1], aug2 = Augmentations[augName2];
if (aug1 == null || aug2 == null) {
throw new Error("Invalid Augmentation Names");
}
return aug1.baseRepRequirement - aug2.baseRepRequirement;
});
removeChildrenFromElement(augmentationsList);
createFactionAugmentationDisplayElements(augmentationsList, augs, faction);
}
});
const defaultSortBtn = createElement("a", {
innerText:"Sort by Default Order", class:"a-link-button",
clickListener:()=>{
Settings.PurchaseAugmentationsOrder = PurchaseAugmentationsOrderSetting.Default;
removeChildrenFromElement(augmentationsList);
createFactionAugmentationDisplayElements(augmentationsList, faction.augmentations, faction);
}
});
elements.push(sortByCostBtn);
elements.push(sortByRepBtn);
elements.push(defaultSortBtn);
switch(Settings.PurchaseAugmentationsOrder) {
case PurchaseAugmentationsOrderSetting.Cost:
sortByCostBtn.click();
break;
case PurchaseAugmentationsOrderSetting.Reputation:
sortByRepBtn.click();
break;
default:
defaultSortBtn.click();
break;
}
elements.push(augmentationsList);
for (var i = 0; i < elements.length; ++i) {
Engine.Display.factionAugmentationsContent.appendChild(elements[i]);
}
}
//Takes in an array of Augmentation Names, constructs DOM elements
//to list them on the faction page, and appends them to the given
//DOM element
// @augmentationsList DOM List to append Aug DOM elements to
// @augs Array of Aug names
// @faction Faction for which to display Augmentations
function createFactionAugmentationDisplayElements(augmentationsList, augs, faction) {
const factionInfo = faction.getInfo();
for (var i = 0; i < augs.length; ++i) {
(function () {
var aug = Augmentations[augs[i]];
if (aug == null) {
throw new Error("Invalid Augmentation when trying to create Augmentation display Elements");
}
var owned = false;
for (var j = 0; j < Player.queuedAugmentations.length; ++j) {
if (Player.queuedAugmentations[j].name == aug.name) {
owned = true;
break;
}
}
for (var j = 0; j < Player.augmentations.length; ++j) {
if (Player.augmentations[j].name == aug.name) {
owned = true;
break;
}
}
var item = createElement("li");
var span = createElement("span", { display:"inline-block", margin: "4px", padding: "4px" });
var aDiv = createElement("div", {tooltip:aug.info});
var aElem = createElement("a", {
innerText:aug.name, display:"inline",
clickListener:()=>{
if (!Settings.SuppressBuyAugmentationConfirmation) {
purchaseAugmentationBoxCreate(aug, faction);
} else {
purchaseAugmentation(aug, faction);
}
return false;
}
});
if (aug.name == AugmentationNames.NeuroFluxGovernor) {
aElem.innerText += " - Level " + (getNextNeurofluxLevel());
}
var pElem = createElement("p", {
display:"inline",
})
var req = aug.baseRepRequirement * factionInfo.augmentationRepRequirementMult;
var hasPrereqs = hasAugmentationPrereqs(aug);
if (!hasPrereqs) {
aElem.setAttribute("class", "a-link-button-inactive");
pElem.innerHTML = "LOCKED (Requires " + aug.prereqs.join(",") + " as prerequisite(s))";
pElem.style.color = "red";
} else if (aug.name != AugmentationNames.NeuroFluxGovernor && (aug.owned || owned)) {
aElem.setAttribute("class", "a-link-button-inactive");
pElem.innerHTML = "ALREADY OWNED";
} else if (faction.playerReputation >= req) {
aElem.setAttribute("class", "a-link-button");
pElem.innerHTML = "UNLOCKED - " + numeralWrapper.format(aug.baseCost * factionInfo.augmentationPriceMult, "$0.000a");
} else {
aElem.setAttribute("class", "a-link-button-inactive");
pElem.innerHTML = "LOCKED (Requires " + formatNumber(req, 1) + " faction reputation) - " + numeralWrapper.format(aug.baseCost * factionInfo.augmentationPriceMult, "$0.000a");
pElem.style.color = "red";
}
aDiv.appendChild(aElem);
span.appendChild(aDiv);
span.appendChild(pElem);
item.appendChild(span);
augmentationsList.appendChild(item);
}()); //Immediate invocation closure
}
}
function purchaseAugmentationBoxCreate(aug, fac) {
const factionInfo = fac.getInfo();
var yesBtn = yesNoBoxGetYesButton(), noBtn = yesNoBoxGetNoButton();
yesBtn.innerHTML = "Purchase";
noBtn.innerHTML = "Cancel";
yesBtn.addEventListener("click", function() {
purchaseAugmentation(aug, fac);
});
noBtn.addEventListener("click", function() {
yesNoBoxClose();
});
yesNoBoxCreate("<h2>" + aug.name + "</h2><br>" +
aug.info + "<br><br>" +
"<br>Would you like to purchase the " + aug.name + " Augmentation for $" +
formatNumber(aug.baseCost * factionInfo.augmentationPriceMult, 2) + "?");
}
//Returns a boolean indicating whether the player has the prerequisites for the
//specified Augmentation
function hasAugmentationPrereqs(aug) {
var hasPrereqs = true;
if (aug.prereqs && aug.prereqs.length > 0) {
for (var i = 0; i < aug.prereqs.length; ++i) {
var prereqAug = Augmentations[aug.prereqs[i]];
if (prereqAug == null) {
console.log("ERROR: Invalid prereq Augmentation: " + aug.prereqs[i]);
continue;
}
if (prereqAug.owned === false) {
hasPrereqs = false;
//Check if the aug is purchased
for (var j = 0; j < Player.queuedAugmentations.length; ++j) {
if (Player.queuedAugmentations[j].name === prereqAug.name) {
hasPrereqs = true;
break;
}
}
}
}
}
return hasPrereqs;
}
function purchaseAugmentation(aug, fac, sing=false) {
const factionInfo = fac.getInfo();
var hasPrereqs = hasAugmentationPrereqs(aug);
if (!hasPrereqs) {
var txt = "You must first purchase or install " + aug.prereqs.join(",") + " before you can " +
"purchase this one.";
if (sing) {return txt;} else {dialogBoxCreate(txt);}
} else if (aug.baseCost !== 0 && Player.money.lt(aug.baseCost * factionInfo.augmentationPriceMult)) {
let txt = "You don't have enough money to purchase " + aug.name;
if (sing) {return txt;}
dialogBoxCreate(txt);
} else if (fac.playerReputation < aug.baseRepRequirement) {
let txt = "You don't have enough faction reputation to purchase " + aug.name;
if (sing) {return txt;}
dialogBoxCreate(txt);
} else if (aug.baseCost === 0 || Player.money.gte(aug.baseCost * factionInfo.augmentationPriceMult)) {
if (Player.firstAugPurchased === false) {
Player.firstAugPurchased = true;
document.getElementById("augmentations-tab").style.display = "list-item";
document.getElementById("character-menu-header").click();
document.getElementById("character-menu-header").click();
}
var queuedAugmentation = new PlayerOwnedAugmentation(aug.name);
if (aug.name == AugmentationNames.NeuroFluxGovernor) {
queuedAugmentation.level = getNextNeurofluxLevel();
}
Player.queuedAugmentations.push(queuedAugmentation);
Player.loseMoney((aug.baseCost * factionInfo.augmentationPriceMult));
//If you just purchased Neuroflux Governor, recalculate the cost
if (aug.name == AugmentationNames.NeuroFluxGovernor) {
var nextLevel = getNextNeurofluxLevel();
--nextLevel;
var mult = Math.pow(CONSTANTS.NeuroFluxGovernorLevelMult, nextLevel);
aug.baseRepRequirement = 500 * mult * CONSTANTS.AugmentationRepMultiplier * BitNodeMultipliers.AugmentationRepCost;
aug.baseCost = 750e3 * mult * CONSTANTS.AugmentationCostMultiplier * BitNodeMultipliers.AugmentationMoneyCost;
for (var i = 0; i < Player.queuedAugmentations.length-1; ++i) {
aug.baseCost *= CONSTANTS.MultipleAugMultiplier;
}
}
for (var name in Augmentations) {
if (Augmentations.hasOwnProperty(name)) {
Augmentations[name].baseCost *= CONSTANTS.MultipleAugMultiplier;
}
}
if (sing) {
return "You purchased " + aug.name;
} else {
if(!Settings.SuppressBuyAugmentationConfirmation){
dialogBoxCreate("You purchased " + aug.name + ". It's enhancements will not take " +
"effect until they are installed. To install your augmentations, go to the " +
"'Augmentations' tab on the left-hand navigation menu. Purchasing additional " +
"augmentations will now be more expensive.");
}
}
displayFactionAugmentations(fac.name);
} else {
dialogBoxCreate("Hmm, something went wrong when trying to purchase an Augmentation. " +
"Please report this to the game developer with an explanation of how to " +
"reproduce this.");
}
yesNoBoxClose();
}
function getNextNeurofluxLevel() {
// Get current Neuroflux level based on Player's augmentations
let currLevel = 0;
for (var i = 0; i < Player.augmentations.length; ++i) {
if (Player.augmentations[i].name === AugmentationNames.NeuroFluxGovernor) {
currLevel = Player.augmentations[i].level;
}
}
// Account for purchased but uninstalled Augmentations
for (var i = 0; i < Player.queuedAugmentations.length; ++i) {
if (Player.queuedAugmentations[i].name == AugmentationNames.NeuroFluxGovernor) {
++currLevel;
}
}
return currLevel + 1;
}
function processPassiveFactionRepGain(numCycles) {
var numTimesGain = (numCycles / 600) * Player.faction_rep_mult;
for (var name in Factions) {
if (Factions.hasOwnProperty(name)) {
var faction = Factions[name];
//TODO Get hard value of 1 rep per "rep gain cycle"" for now..
//maybe later make this based on
//a player's 'status' like how powerful they are and how much money they have
if (faction.isMember) {faction.playerReputation += (numTimesGain * BitNodeMultipliers.FactionPassiveRepGain);}
}
}
}
export {getNextNeurofluxLevel, inviteToFaction,
joinFaction, displayFactionContent, processPassiveFactionRepGain,
purchaseAugmentation};

View File

@@ -0,0 +1,245 @@
import React from "react";
import ReactDOM from "react-dom";
import { FactionRoot } from "./ui/Root";
import { Augmentations } from "../Augmentation/Augmentations";
import { isRepeatableAug } from "../Augmentation/AugmentationHelpers";
import { PlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../Constants";
import { Engine } from "../engine";
import { Faction } from "./Faction";
import { Factions } from "./Factions";
import { HackingMission, setInMission } from "../Missions";
import { Player } from "../Player";
import { Settings } from "../Settings/Settings";
import { Page, routing } from "../ui/navigationTracking";
import { dialogBoxCreate } from "../../utils/DialogBox";
import { factionInvitationBoxCreate } from "../../utils/FactionInvitationBox";
import {
Reviver,
Generic_toJSON,
Generic_fromJSON
} from "../../utils/JSONReviver";
import { formatNumber } from "../../utils/StringHelperFunctions";
import {
yesNoBoxCreate,
yesNoBoxGetYesButton,
yesNoBoxGetNoButton,
yesNoBoxClose
} from "../../utils/YesNoBox";
export function inviteToFaction(faction) {
if (Settings.SuppressFactionInvites) {
faction.alreadyInvited = true;
Player.factionInvitations.push(faction.name);
if (routing.isOn(Page.Factions)) {
Engine.loadFactionsContent();
}
} else {
factionInvitationBoxCreate(faction);
}
}
export function joinFaction(faction) {
faction.isMember = true;
Player.factions.push(faction.name);
const factionInfo = faction.getInfo();
//Determine what factions you are banned from now that you have joined this faction
for(const i in factionInfo.enemies) {
const enemy = factionInfo.enemies[i];
if (Factions[enemy] instanceof Faction) {
Factions[enemy].isBanned = true;
}
}
}
export function startHackingMission(faction) {
const mission = new HackingMission(faction.playerReputation, faction);
setInMission(true, mission); //Sets inMission flag to true
mission.init();
}
//Displays the HTML content for a specific faction
export function displayFactionContent(factionName, initiallyOnAugmentationsPage=false) {
const faction = Factions[factionName];
if (faction == null) {
throw new Error(`Invalid factionName passed into displayFactionContent(): ${factionName}`);
}
if (!faction.isMember) {
throw new Error(`Not a member of this faction. Cannot display faction information`);
}
ReactDOM.render(
<FactionRoot
engine={Engine}
initiallyOnAugmentationsPage={initiallyOnAugmentationsPage}
faction={faction}
p={Player}
startHackingMissionFn={startHackingMission}
/>,
Engine.Display.factionContent
)
}
export function purchaseAugmentationBoxCreate(aug, fac) {
const factionInfo = fac.getInfo();
const yesBtn = yesNoBoxGetYesButton();
yesBtn.innerHTML = "Purchase";
yesBtn.addEventListener("click", function() {
if (!isRepeatableAug(aug) && Player.hasAugmentation(aug)) {
return;
}
purchaseAugmentation(aug, fac);
yesNoBoxClose();
});
const noBtn = yesNoBoxGetNoButton();
noBtn.innerHTML = "Cancel";
noBtn.addEventListener("click", function() {
yesNoBoxClose();
});
yesNoBoxCreate("<h2>" + aug.name + "</h2><br>" +
aug.info + "<br><br>" +
"<br>Would you like to purchase the " + aug.name + " Augmentation for $" +
formatNumber(aug.baseCost * factionInfo.augmentationPriceMult, 2) + "?");
}
//Returns a boolean indicating whether the player has the prerequisites for the
//specified Augmentation
export function hasAugmentationPrereqs(aug) {
let hasPrereqs = true;
if (aug.prereqs && aug.prereqs.length > 0) {
for (let i = 0; i < aug.prereqs.length; ++i) {
const prereqAug = Augmentations[aug.prereqs[i]];
if (prereqAug == null) {
console.error(`Invalid prereq Augmentation ${aug.prereqs[i]}`);
continue;
}
if (prereqAug.owned === false) {
hasPrereqs = false;
// Check if the aug is purchased
for (let j = 0; j < Player.queuedAugmentations.length; ++j) {
if (Player.queuedAugmentations[j].name === prereqAug.name) {
hasPrereqs = true;
break;
}
}
}
}
}
return hasPrereqs;
}
export function purchaseAugmentation(aug, fac, sing=false) {
const factionInfo = fac.getInfo();
var hasPrereqs = hasAugmentationPrereqs(aug);
if (!hasPrereqs) {
var txt = "You must first purchase or install " + aug.prereqs.join(",") + " before you can " +
"purchase this one.";
if (sing) {return txt;} else {dialogBoxCreate(txt);}
} else if (aug.baseCost !== 0 && Player.money.lt(aug.baseCost * factionInfo.augmentationPriceMult)) {
let txt = "You don't have enough money to purchase " + aug.name;
if (sing) {return txt;}
dialogBoxCreate(txt);
} else if (fac.playerReputation < aug.baseRepRequirement) {
let txt = "You don't have enough faction reputation to purchase " + aug.name;
if (sing) {return txt;}
dialogBoxCreate(txt);
} else if (aug.baseCost === 0 || Player.money.gte(aug.baseCost * factionInfo.augmentationPriceMult)) {
if (Player.firstAugPurchased === false) {
Player.firstAugPurchased = true;
document.getElementById("augmentations-tab").style.display = "list-item";
document.getElementById("character-menu-header").click();
document.getElementById("character-menu-header").click();
}
var queuedAugmentation = new PlayerOwnedAugmentation(aug.name);
if (aug.name == AugmentationNames.NeuroFluxGovernor) {
queuedAugmentation.level = getNextNeurofluxLevel();
}
Player.queuedAugmentations.push(queuedAugmentation);
Player.loseMoney((aug.baseCost * factionInfo.augmentationPriceMult));
// If you just purchased Neuroflux Governor, recalculate the cost
if (aug.name == AugmentationNames.NeuroFluxGovernor) {
var nextLevel = getNextNeurofluxLevel();
--nextLevel;
var mult = Math.pow(CONSTANTS.NeuroFluxGovernorLevelMult, nextLevel);
aug.baseRepRequirement = 500 * mult * CONSTANTS.AugmentationRepMultiplier * BitNodeMultipliers.AugmentationRepCost;
aug.baseCost = 750e3 * mult * CONSTANTS.AugmentationCostMultiplier * BitNodeMultipliers.AugmentationMoneyCost;
for (var i = 0; i < Player.queuedAugmentations.length-1; ++i) {
aug.baseCost *= CONSTANTS.MultipleAugMultiplier;
}
}
for (var name in Augmentations) {
if (Augmentations.hasOwnProperty(name)) {
Augmentations[name].baseCost *= CONSTANTS.MultipleAugMultiplier;
}
}
if (sing) {
return "You purchased " + aug.name;
} else {
if(!Settings.SuppressBuyAugmentationConfirmation){
dialogBoxCreate("You purchased " + aug.name + ". It's enhancements will not take " +
"effect until they are installed. To install your augmentations, go to the " +
"'Augmentations' tab on the left-hand navigation menu. Purchasing additional " +
"augmentations will now be more expensive.");
}
}
// Force a rerender of the Augmentations page
displayFactionContent(fac.name, true);
} else {
dialogBoxCreate("Hmm, something went wrong when trying to purchase an Augmentation. " +
"Please report this to the game developer with an explanation of how to " +
"reproduce this.");
}
}
export function getNextNeurofluxLevel() {
// Get current Neuroflux level based on Player's augmentations
let currLevel = 0;
for (var i = 0; i < Player.augmentations.length; ++i) {
if (Player.augmentations[i].name === AugmentationNames.NeuroFluxGovernor) {
currLevel = Player.augmentations[i].level;
}
}
// Account for purchased but uninstalled Augmentations
for (var i = 0; i < Player.queuedAugmentations.length; ++i) {
if (Player.queuedAugmentations[i].name == AugmentationNames.NeuroFluxGovernor) {
++currLevel;
}
}
return currLevel + 1;
}
export function processPassiveFactionRepGain(numCycles) {
var numTimesGain = (numCycles / 600) * Player.faction_rep_mult;
for (var name in Factions) {
if (Factions.hasOwnProperty(name)) {
var faction = Factions[name];
//TODO Get hard value of 1 rep per "rep gain cycle"" for now..
//maybe later make this based on
//a player's 'status' like how powerful they are and how much money they have
if (faction.isMember) {faction.playerReputation += (numTimesGain * BitNodeMultipliers.FactionPassiveRepGain);}
}
}
}

View File

@@ -0,0 +1,163 @@
/**
* Root React Component for displaying a faction's "Purchase Augmentations" page
*/
import * as React from "react";
import { PurchaseableAugmentation } from "./PurchaseableAugmentation";
import { Augmentations } from "../../Augmentation/Augmentations";
import { Faction } from "../../Faction/Faction";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { PurchaseAugmentationsOrderSetting } from "../../Settings/SettingEnums";
import { Settings } from "../../Settings/Settings";
import { StdButton } from "../../ui/React/StdButton";
type IProps = {
faction: Faction;
p: IPlayer;
routeToMainPage: () => void;
}
type IState = {
rerenderFlag: boolean;
sortOrder: PurchaseAugmentationsOrderSetting;
}
const infoStyleMarkup = {
width: "70%",
}
export class AugmentationsPage extends React.Component<IProps, IState> {
// Flag for whether the player has a gang with this faction
isPlayersGang: boolean;
constructor(props: IProps) {
super(props);
this.isPlayersGang = props.p.inGang() && (props.p.getGangName() === props.faction.name);
this.state = {
rerenderFlag: false,
sortOrder: PurchaseAugmentationsOrderSetting.Default,
}
this.rerender = this.rerender.bind(this);
}
getAugs() {
if (this.isPlayersGang) {
const augs: string[] = [];
for (const augName in Augmentations) {
const aug = Augmentations[augName];
if (!aug.isSpecial) {
augs.push(augName);
}
}
return augs;
} else {
return this.props.faction.augmentations.slice();
}
}
getAugsSorted() {
switch (Settings.PurchaseAugmentationsOrder) {
case PurchaseAugmentationsOrderSetting.Cost: {
return this.getAugsSortedByCost();
}
case PurchaseAugmentationsOrderSetting.Reputation: {
return this.getAugsSortedByReputation();
}
default:
return this.getAugsSortedByDefault();
}
}
getAugsSortedByCost() {
const augs = this.getAugs();
augs.sort((augName1, augName2)=>{
var aug1 = Augmentations[augName1], aug2 = Augmentations[augName2];
if (aug1 == null || aug2 == null) {
throw new Error("Invalid Augmentation Names");
}
return aug1.baseCost - aug2.baseCost;
});
return augs;
}
getAugsSortedByReputation() {
const augs = this.getAugs();
augs.sort((augName1, augName2)=>{
var aug1 = Augmentations[augName1], aug2 = Augmentations[augName2];
if (aug1 == null || aug2 == null) {
throw new Error("Invalid Augmentation Names");
}
return aug1.baseRepRequirement - aug2.baseRepRequirement;
});
return augs;
}
getAugsSortedByDefault() {
return this.getAugs();
}
switchSortOrder(newOrder: PurchaseAugmentationsOrderSetting) {
Settings.PurchaseAugmentationsOrder = newOrder;
this.rerender();
}
rerender() {
this.setState((prevState) => {
return {
rerenderFlag: !prevState.rerenderFlag,
}
});
}
render() {
const augs = this.getAugsSorted();
const augList = augs.map((aug) => {
return (
<PurchaseableAugmentation
augName={aug}
faction={this.props.faction}
key={aug}
p={this.props.p}
rerender={this.rerender}
/>
)
});
return (
<div>
<StdButton
onClick={this.props.routeToMainPage}
text={"Back"}
/>
<h1>Faction Augmentations</h1>
<p style={infoStyleMarkup}>
These are all of the Augmentations that are available to purchase
from {this.props.faction.name}. Augmentations are powerful upgrades
that will enhance your abilities.
</p>
<StdButton
onClick={() => this.switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)}
text={"Sort by Cost"}
/>
<StdButton
onClick={() => this.switchSortOrder(PurchaseAugmentationsOrderSetting.Reputation)}
text={"Sort by Reputation"}
/>
<StdButton
onClick={() => this.switchSortOrder(PurchaseAugmentationsOrderSetting.Default)}
text={"Sort by Default Order"}
/>
<br />
{augList}
</div>
)
}
}

View File

@@ -0,0 +1,101 @@
/**
* React component for a donate option on the Faction UI
*/
import * as React from "react";
import { CONSTANTS } from "../../Constants";
import { Faction } from "../../Faction/Faction";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { numeralWrapper } from "../../ui/numeralFormat";
import { StdButton } from "../../ui/React/StdButton";
import { dialogBoxCreate } from "../../../utils/DialogBox";
type IProps = {
faction: Faction;
p: IPlayer;
rerender: () => void;
}
type IState = {
donateAmt: number;
statusTxt: string;
}
const inputStyleMarkup = {
margin: "5px",
}
export class DonateOption extends React.Component<IProps, IState> {
// Style markup for block elements. Stored as property
blockStyle: object = { display: "block" };
constructor(props: IProps) {
super(props);
this.state = {
donateAmt: 0,
statusTxt: "",
}
this.calculateRepGain = this.calculateRepGain.bind(this);
this.donate = this.donate.bind(this);
this.handleChange = this.handleChange.bind(this);
}
// Returns rep gain for a given donation amount
calculateRepGain(amt: number): number {
return amt / CONSTANTS.DonateMoneyToRepDivisor * this.props.p.faction_rep_mult;
}
donate(): void {
const fac = this.props.faction;
const amt = this.state.donateAmt;
if (isNaN(amt) || amt <= 0) {
dialogBoxCreate(`Invalid amount entered!`);
} else if (!this.props.p.canAfford(amt)) {
dialogBoxCreate(`You cannot afford to donate this much money!`);
} else {
this.props.p.loseMoney(amt);
const repGain = this.calculateRepGain(amt);
this.props.faction.playerReputation += repGain;
dialogBoxCreate(`You just donated ${numeralWrapper.formatMoney(amt)} to ${fac.name} to gain ` +
`${numeralWrapper.format(repGain, "0,0.000")} reputation`);
this.props.rerender();
}
}
handleChange(e: React.ChangeEvent<HTMLInputElement>): void {
const amt = parseFloat(e.target.value);
if (isNaN(amt)) {
this.setState({
donateAmt: 0,
statusTxt: "Invalid donate amount entered!",
});
} else {
const repGain = this.calculateRepGain(amt);
this.setState({
donateAmt: amt,
statusTxt: `This donation will result in ${numeralWrapper.format(repGain, "0,0.000")} reputation gain`,
});
}
}
render() {
return (
<div className={"faction-work-div"}>
<div className={"faction-work-div-wrapper"}>
<input onChange={this.handleChange} placeholder={"Donation amount"} style={inputStyleMarkup} />
<StdButton
onClick={this.donate}
text={"Donate Money"}
/>
<p style={this.blockStyle}>{this.state.statusTxt}</p>
</div>
</div>
)
}
}

88
src/Faction/ui/Info.tsx Normal file
View File

@@ -0,0 +1,88 @@
/**
* React component for general information about the faction. This includes the
* factions "motto", reputation, favor, and gameplay instructions
*/
import * as React from "react";
import { Faction } from "../../Faction/Faction";
import { FactionInfo } from "../../Faction/FactionInfo";
import { numeralWrapper } from "../../ui/numeralFormat";
import { AutoupdatingParagraph } from "../../ui/React/AutoupdatingParagraph";
import { ParagraphWithTooltip } from "../../ui/React/ParagraphWithTooltip";
type IProps = {
faction: Faction;
factionInfo: FactionInfo;
}
type IInnerHTMLMarkup = {
__html: string;
}
const blockStyleMarkup = {
display: "block",
}
const infoStyleMarkup = {
display: "block",
width: "70%",
}
export class Info extends React.Component<IProps, any> {
constructor(props: IProps) {
super(props);
this.getFavorGainText = this.getFavorGainText.bind(this);
this.getReputationText = this.getReputationText.bind(this);
}
getFavorGainText(): string {
const favorGain = this.props.faction.getFavorGain()[0];
return `You will earn ${numeralWrapper.format(favorGain, "0,0")} faction favor upon resetting after installing an Augmentation`
}
getReputationText(): string {
const formattedRep = numeralWrapper.format(this.props.faction.playerReputation, "0.000a");
return `Reputation: ${formattedRep}`
}
render() {
const favorTooltip = "Faction favor increases the rate at which you earn reputation for " +
"this faction by 1% per favor. Faction favor is gained whenever you " +
"reset after installing an Augmentation. The amount of " +
"favor you gain depends on how much reputation you have with the faction"
const infoText: IInnerHTMLMarkup = {
__html: this.props.factionInfo.infoText,
}
return (
<div>
<pre>
<i className={"text"} dangerouslySetInnerHTML={infoText}></i>
</pre>
<p style={blockStyleMarkup}>-------------------------</p>
<AutoupdatingParagraph
intervalTime={5e3}
getText={this.getReputationText}
getTooltip={this.getFavorGainText}
/>
<p style={blockStyleMarkup}>-------------------------</p>
<ParagraphWithTooltip
text={`Faction Favor: ${numeralWrapper.format(this.props.faction.favor, "0,0")}`}
tooltip={favorTooltip}
/>
<p style={blockStyleMarkup}>-------------------------</p>
<p style={infoStyleMarkup}>
Perform work/carry out assignments for your faction to help further its cause!
By doing so you will earn reputation for your faction. You will also gain
reputation passively over time, although at a very slow rate. Earning
reputation will allow you to purchase Augmentations through this faction, which
are powerful upgrades that enhance your abilities. Note that you cannot use your
terminal or create scripts when you are performing a task!
</p>
</div>
)
}
}

30
src/Faction/ui/Option.tsx Normal file
View File

@@ -0,0 +1,30 @@
/**
* React component for a selectable option on the Faction UI. These
* options including working for the faction, hacking missions, purchasing
* augmentations, etc.
*/
import * as React from "react";
import { StdButton } from "../../ui/React/StdButton";
type IProps = {
buttonText: string;
infoText: string;
onClick: (e: React.MouseEvent<HTMLElement>) => void;
}
export class Option extends React.Component<IProps, any> {
render() {
return (
<div className={"faction-work-div"}>
<div className={"faction-work-div-wrapper"}>
<StdButton
onClick={this.props.onClick}
text={this.props.buttonText}
/>
<p>{this.props.infoText}</p>
</div>
</div>
)
}
}

View File

@@ -0,0 +1,152 @@
/**
* React component for displaying a single augmentation for purchase through
* the faction UI
*/
import * as React from "react";
import {
getNextNeurofluxLevel,
hasAugmentationPrereqs,
purchaseAugmentation,
purchaseAugmentationBoxCreate,
} from "../FactionHelpers";
import { Augmentation } from "../../Augmentation/Augmentation";
import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Faction } from "../../Faction/Faction";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Settings } from "../../Settings/Settings";
import { numeralWrapper } from "../../ui/numeralFormat";
import { IMap } from "../../types";
import { StdButton } from "../../ui/React/StdButton";
type IProps = {
augName: string;
faction: Faction;
p: IPlayer;
rerender: () => void;
}
const spanStyleMarkup = {
margin: "4px",
padding: "4px",
}
const inlineStyleMarkup = {
display: "inline-block",
}
export class PurchaseableAugmentation extends React.Component<IProps, any> {
aug: Augmentation | null;
constructor(props: IProps) {
super(props);
this.aug = Augmentations[this.props.augName];
this.handleClick = this.handleClick.bind(this);
}
getMoneyCost(): number {
return this.aug!.baseCost * this.props.faction.getInfo().augmentationPriceMult;
}
getRepCost(): number {
return this.aug!.baseRepRequirement * this.props.faction.getInfo().augmentationRepRequirementMult;
}
handleClick() {
if (!Settings.SuppressBuyAugmentationConfirmation) {
purchaseAugmentationBoxCreate(this.aug!, this.props.faction);
} else {
purchaseAugmentation(this.aug!, this.props.faction);
}
}
// Whether the player has the prerequisite Augmentations
hasPrereqs(): boolean {
return hasAugmentationPrereqs(this.aug!);
}
// Whether the player has enough rep for this Augmentation
hasReputation(): boolean {
return this.props.faction.playerReputation >= this.getRepCost();
}
// Whether the player has this augmentations (purchased OR installed)
owned(): boolean {
let owned = false;
for (const queuedAug of this.props.p.queuedAugmentations) {
if (queuedAug.name === this.props.augName) {
owned = true;
break;
}
}
for (const installedAug of this.props.p.augmentations) {
if (installedAug.name === this.props.augName) {
owned = true;
break;
}
}
return owned;
}
render() {
if (this.aug == null) {
console.error(`Invalid Augmentation when trying to create PurchaseableAugmentation display element: ${this.props.augName}`);
return null;
}
const moneyCost = this.getMoneyCost();
const repCost = this.getRepCost();
// Determine UI properties
let disabled: boolean = false;
let statusTxt: string = "";
let color: string = "";
if (!this.hasPrereqs()) {
disabled = true;
statusTxt = `LOCKED (Requires ${this.aug.prereqs.join(",")} as prerequisite(s))`;
color = "red";
} else if (this.aug.name !== AugmentationNames.NeuroFluxGovernor && (this.aug.owned || this.owned())) {
disabled = true;
statusTxt = "ALREADY OWNED";
} else if (this.hasReputation()) {
statusTxt = `UNLOCKED - ${numeralWrapper.formatMoney(moneyCost)}`;
} else {
disabled = true;
statusTxt = `LOCKED (Requires ${numeralWrapper.format(repCost, "0,0.0")} faction reputation - ${numeralWrapper.formatMoney(moneyCost)})`;
color = "red";
}
const txtStyle: IMap<string> = {
display: "inline-block",
}
if (color !== "") { txtStyle.color = color; }
// Determine button txt
let btnTxt = this.aug.name;
if (this.aug.name === AugmentationNames.NeuroFluxGovernor) {
btnTxt += ` - Level ${getNextNeurofluxLevel()}`;
}
return (
<li>
<span style={spanStyleMarkup}>
<StdButton
disabled={disabled}
onClick={this.handleClick}
style={inlineStyleMarkup}
text={btnTxt}
tooltip={this.aug.info}
/>
<p style={txtStyle}>{statusTxt}</p>
</span>
</li>
)
}
}

302
src/Faction/ui/Root.tsx Normal file
View File

@@ -0,0 +1,302 @@
/**
* Root React Component for displaying a Faction's UI.
* This is the component for displaying a single faction's UI, not the list of all
* accessible factions
*/
import * as React from "react";
import { AugmentationsPage } from "./AugmentationsPage";
import { DonateOption } from "./DonateOption";
import { Info } from "./Info";
import { Option } from "./Option";
import { CONSTANTS } from "../../Constants";
import { IEngine } from "../../IEngine";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { Faction } from "../../Faction/Faction";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { createSleevePurchasesFromCovenantPopup } from "../../PersonObjects/Sleeve/SleeveCovenantPurchases";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import {
yesNoBoxClose,
yesNoBoxCreate,
yesNoBoxGetNoButton,
yesNoBoxGetYesButton,
} from "../../../utils/YesNoBox";
type IProps = {
engine: IEngine;
initiallyOnAugmentationsPage?: boolean;
faction: Faction;
p: IPlayer;
startHackingMissionFn: (faction: Faction) => void;
}
type IState = {
rerenderFlag: boolean;
purchasingAugs: boolean;
}
// Info text for all options on the UI
const gangInfo = "Create and manage a gang for this Faction. Gangs will earn you money and " +
"faction reputation";
const hackingMissionInfo = "Attempt a hacking mission for your faction. " +
"A mission is a mini game that, if won, earns you " +
"significant reputation with this faction. (Recommended hacking level: 200+)";
const hackingContractsInfo = "Complete hacking contracts for your faction. " +
"Your effectiveness, which determines how much " +
"reputation you gain for this faction, is based on your hacking skill. " +
"You will gain hacking exp.";
const fieldWorkInfo = "Carry out field missions for your faction. " +
"Your effectiveness, which determines how much " +
"reputation you gain for this faction, is based on all of your stats. " +
"You will gain exp for all stats.";
const securityWorkInfo = "Serve in a security detail for your faction. " +
"Your effectiveness, which determines how much " +
"reputation you gain for this faction, is based on your combat stats. " +
"You will gain exp for all combat stats.";
const augmentationsInfo = "As your reputation with this faction rises, you will " +
"unlock Augmentations, which you can purchase to enhance " +
"your abilities.";
const sleevePurchasesInfo = "Purchase Duplicate Sleeves and upgrades. These are permanent!";
const GangNames = [
"Slum Snakes",
"Tetrads",
"The Syndicate",
"The Dark Army",
"Speakers for the Dead",
"NiteSec",
"The Black Hand"
];
export class FactionRoot extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
rerenderFlag: false,
purchasingAugs: props.initiallyOnAugmentationsPage ? props.initiallyOnAugmentationsPage : false,
}
this.manageGang = this.manageGang.bind(this);
this.rerender = this.rerender.bind(this);
this.routeToMain = this.routeToMain.bind(this);
this.routeToPurchaseAugs = this.routeToPurchaseAugs.bind(this);
this.sleevePurchases = this.sleevePurchases.bind(this);
this.startFieldWork = this.startFieldWork.bind(this);
this.startHackingContracts = this.startHackingContracts.bind(this);
this.startHackingMission = this.startHackingMission.bind(this);
this.startSecurityWork = this.startSecurityWork.bind(this);
}
manageGang() {
// If player already has a gang, just go to the gang UI
if (this.props.p.inGang()) {
return this.props.engine.loadGangContent();
}
// Otherwise, we have to create the gang
const facName = this.props.faction.name;
let isHacking = false;
if (facName === "NiteSec" || facName === "The Black Hand") {
isHacking = true;
}
// A Yes/No popup box will allow the player to confirm gang creation
const yesBtn = yesNoBoxGetYesButton();
const noBtn = yesNoBoxGetNoButton();
if (yesBtn == null || noBtn == null) { return; }
yesBtn.innerHTML = "Create Gang";
yesBtn.addEventListener("click", () => {
this.props.p.startGang(facName, isHacking);
const worldMenuHeader = document.getElementById("world-menu-header");
if (worldMenuHeader instanceof HTMLElement) {
worldMenuHeader.click(); worldMenuHeader.click();
}
this.props.engine.loadGangContent();
yesNoBoxClose();
});
noBtn.innerHTML = "Cancel";
noBtn.addEventListener("click", () => {
yesNoBoxClose();
});
// Pop-up text
let gangTypeText = "";
if (isHacking) {
gangTypeText = "This is a HACKING gang. Members in this gang will have different tasks than COMBAT gangs. " +
"Compared to combat gangs, progression with hacking gangs is more straightforward as territory warfare " +
"is not as important.<br><br>";
} else {
gangTypeText = "This is a COMBAT gang. Members in this gang will have different tasks than HACKING gangs. " +
"Compared to hacking gangs, progression with combat gangs can be more difficult as territory management " +
"is more important. However, well-managed combat gangs can progress faster than hacking ones.<br><br>";
}
yesNoBoxCreate(`Would you like to create a new Gang with ${facName}?<br><br>` +
"Note that this will prevent you from creating a Gang with any other Faction until " +
"this BitNode is destroyed. It also resets your reputation with this faction.<br><br>" +
gangTypeText +
"Other than hacking vs combat, there are NO differences between the Factions you can " +
"create a Gang with, and each of these Factions have all Augmentations available.");
}
rerender() {
this.setState((prevState) => {
return {
rerenderFlag: !prevState.rerenderFlag,
}
});
}
// Route to the main faction page
routeToMain() {
this.setState({ purchasingAugs: false });
}
// Route to the purchase augmentation UI for this faction
routeToPurchaseAugs() {
this.setState({ purchasingAugs: true });
}
sleevePurchases() {
createSleevePurchasesFromCovenantPopup(this.props.p);
}
startFieldWork() {
this.props.p.startFactionFieldWork(this.props.faction);
}
startHackingContracts() {
this.props.p.startFactionHackWork(this.props.faction);
}
startHackingMission() {
const fac = this.props.faction;
this.props.engine.loadMissionContent();
this.props.startHackingMissionFn(fac);
}
startSecurityWork() {
this.props.p.startFactionSecurityWork(this.props.faction);
}
render() {
return this.state.purchasingAugs ? this.renderAugmentationsPage() : this.renderMainPage();
}
renderMainPage() {
const p = this.props.p;
const faction = this.props.faction;
const factionInfo = faction.getInfo();
// We have a special flag for whether the player this faction is the player's
// gang faction because if the player has a gang, they cannot do any other action
const isPlayersGang = p.inGang() && (p.getGangName() === faction.name);
// Flags for whether special options (gang, sleeve purchases, donate, etc.)
// should be shown
const favorToDonate = Math.floor(CONSTANTS.BaseFavorToDonate * BitNodeMultipliers.RepToDonateToFaction);
const canDonate = faction.favor >= favorToDonate;
const canPurchaseSleeves = (faction.name === "The Covenant" && p.bitNodeN >= 10 && SourceFileFlags[10]);
let canAccessGang = (p.canAccessGang() && GangNames.includes(faction.name));
if (p.inGang()) {
if (p.getGangName() !== faction.name) {
canAccessGang = false;
} else if (p.getGangName() === faction.name) {
canAccessGang = true;
}
}
return (
<div>
<h1>{faction.name}</h1>
<Info
faction={faction}
factionInfo={factionInfo}
/>
{
canAccessGang &&
<Option
buttonText={"Manage Gang"}
infoText={gangInfo}
onClick={this.manageGang}
/>
}
{
(!isPlayersGang && factionInfo.offerHackingMission) &&
<Option
buttonText={"Hacking Mission"}
infoText={hackingMissionInfo}
onClick={this.startHackingMission}
/>
}
{
(!isPlayersGang && factionInfo.offerHackingWork) &&
<Option
buttonText={"Hacking Contracts"}
infoText={hackingContractsInfo}
onClick={this.startHackingContracts}
/>
}
{
(!isPlayersGang && factionInfo.offerFieldWork) &&
<Option
buttonText={"Field Work"}
infoText={fieldWorkInfo}
onClick={this.startFieldWork}
/>
}
{
(!isPlayersGang && factionInfo.offerSecurityWork) &&
<Option
buttonText={"Security Work"}
infoText={securityWorkInfo}
onClick={this.startSecurityWork}
/>
}
{
(!isPlayersGang && canDonate) &&
<DonateOption
faction={this.props.faction}
p={this.props.p}
rerender={this.rerender}
/>
}
<Option
buttonText={"Purchase Augmentations"}
infoText={augmentationsInfo}
onClick={this.routeToPurchaseAugs}
/>
{
canPurchaseSleeves &&
<Option
buttonText={"Purchase & Upgrade Duplicate Sleeves"}
infoText={sleevePurchasesInfo}
onClick={this.sleevePurchases}
/>
}
</div>
)
}
renderAugmentationsPage() {
return (
<div>
<AugmentationsPage
faction={this.props.faction}
p={this.props.p}
routeToMainPage={this.routeToMain}
/>
</div>
)
}
}

View File

@@ -1,31 +1,39 @@
/*
Also add police clashes
balance point to keep them from running out of control
/**
* TODO
* Add police clashes
* balance point to keep them from running out of control
*/
import { gangMemberTasksMetadata } from "./data/gangmembertasks";
import { gangMemberUpgradesMetadata } from "./data/gangmemberupgrades";
import { gangMemberTasksMetadata } from "./data/gangmembertasks";
import { gangMemberUpgradesMetadata } from "./data/gangmemberupgrades";
import { Engine } from "./engine";
import { Faction } from "./Faction/Faction";
import { Factions } from "./Faction/Factions";
import { displayFactionContent } from "./Faction/FactionHelpers";
import { Page, routing } from "./ui/navigationTracking";
import { numeralWrapper } from "./ui/numeralFormat";
import { dialogBoxCreate } from "../utils/DialogBox";
import {
Reviver,
Generic_toJSON,
Generic_fromJSON
} from "../utils/JSONReviver";
import { formatNumber } from "../utils/StringHelperFunctions";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { getRandomInt } from "../utils/helpers/getRandomInt";
import { KEY } from "../utils/helpers/keyCodes";
import { createAccordionElement } from "../utils/uiHelpers/createAccordionElement";
import { createElement } from "../utils/uiHelpers/createElement";
import { createPopup } from "../utils/uiHelpers/createPopup";
import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement";
import { removeElement } from "../utils/uiHelpers/removeElement";
import { removeElementById } from "../utils/uiHelpers/removeElementById";
import { Engine } from "./engine";
import { Faction } from "./Faction/Faction";
import { Factions } from "./Faction/Factions";
import { displayFactionContent } from "./Faction/FactionHelpers";
import { numeralWrapper } from "./ui/numeralFormat";
import { dialogBoxCreate } from "../utils/DialogBox";
import { Reviver, Generic_toJSON,
Generic_fromJSON } from "../utils/JSONReviver";
import { KEY } from "../utils/helpers/keyCodes";
import { createAccordionElement } from "../utils/uiHelpers/createAccordionElement";
import { createElement } from "../utils/uiHelpers/createElement";
import { createPopup } from "../utils/uiHelpers/createPopup";
import { Page,
routing } from "./ui/navigationTracking";
import { formatNumber } from "../utils/StringHelperFunctions";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { getRandomInt } from "../utils/helpers/getRandomInt";
import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement";
import { removeElement } from "../utils/uiHelpers/removeElement";
import { removeElementById } from "../utils/uiHelpers/removeElementById";
// Constants
const GangRespectToReputationRatio = 5; // Respect is divided by this to get rep gain
@@ -50,7 +58,7 @@ $(document).keydown(function(event) {
}
});
//Delete upgrade box when clicking outside
// Delete upgrade box when clicking outside
$(document).mousedown(function(event) {
var boxId = "gang-member-upgrade-popup-box";
var contentId = "gang-member-upgrade-popup-box-content";
@@ -66,8 +74,16 @@ $(document).mousedown(function(event) {
}
});
let GangNames = ["Slum Snakes", "Tetrads", "The Syndicate", "The Dark Army", "Speakers for the Dead",
"NiteSec", "The Black Hand"];
const GangNames = [
"Slum Snakes",
"Tetrads",
"The Syndicate",
"The Dark Army",
"Speakers for the Dead",
"NiteSec",
"The Black Hand"
];
export let AllGangs = {
"Slum Snakes" : {
power: 1,
@@ -137,12 +153,12 @@ export function loadAllGangs(saveString) {
}
/**
* @param facName - Name of corresponding faction
* @param hacking - Boolean indicating whether or not its a hacking gang
* @param facName {string} Name of corresponding faction
* @param hacking {bollean} Whether or not its a hacking gang
*/
export function Gang(facName, hacking=false) {
this.facName = facName;
this.members = []; //Array of GangMembers
this.members = [];
this.wanted = 1;
this.respect = 1;
@@ -197,7 +213,7 @@ Gang.prototype.process = function(numCycles=1, player) {
}
Gang.prototype.processGains = function(numCycles=1, player) {
//Get gains per cycle
// Get gains per cycle
var moneyGains = 0, respectGains = 0, wantedLevelGains = 0;
for (var i = 0; i < this.members.length; ++i) {
respectGains += (this.members[i].calculateRespectGain(this));
@@ -291,9 +307,9 @@ Gang.prototype.processTerritoryAndPowerGains = function(numCycles=1) {
}
// Then process territory
for (var i = 0; i < GangNames.length; ++i) {
for (let i = 0; i < GangNames.length; ++i) {
const others = GangNames.filter((e) => {
return e !== i;
return e !== GangNames[i];
});
const other = getRandomInt(0, others.length - 1);
@@ -318,6 +334,7 @@ Gang.prototype.processTerritoryAndPowerGains = function(numCycles=1) {
AllGangs[otherGang].territory -= 0.0001;
if (thisGang === gangName) {
this.clash(true); // Player won
AllGangs[otherGang].power *= (1 / 1.01);
} else if (otherGang === gangName) {
this.clash(false); // Player lost
} else {
@@ -333,6 +350,7 @@ Gang.prototype.processTerritoryAndPowerGains = function(numCycles=1) {
this.clash(false); // Player lost
} else if (otherGang === gangName) {
this.clash(true); // Player won
AllGangs[thisGang].power *= (1 / 1.01);
} else {
AllGangs[thisGang].power *= (1 / 1.01);
}
@@ -562,10 +580,9 @@ Gang.fromJSON = function(value) {
Reviver.constructors.Gang = Gang;
/*** Gang Member object ***/
function GangMember(name) {
this.name = name;
this.task = "Unassigned"; //GangMemberTask object
this.task = "Unassigned";
this.earnedRespect = 0;
@@ -597,11 +614,11 @@ function GangMember(name) {
this.agi_asc_mult = 1;
this.cha_asc_mult = 1;
this.upgrades = []; //Names of upgrades
this.augmentations = []; //Names only
this.upgrades = []; // Names of upgrades
this.augmentations = []; // Names of augmentations only
}
//Same formula for Player
// Same skill calculation formula as Player
GangMember.prototype.calculateSkill = function(exp, mult=1) {
return Math.max(Math.floor(mult * (32 * Math.log(exp + 534.5) - 200)), 1);
}
@@ -645,7 +662,7 @@ GangMember.prototype.getTask = function() {
return GangMemberTasks["Unassigned"];
}
//Gains are per cycle
// Gains are per cycle
GangMember.prototype.calculateRespectGain = function(gang) {
const task = this.getTask();
if (task == null || !(task instanceof GangMemberTask) || task.baseRespect === 0) {return 0;}
@@ -665,21 +682,25 @@ GangMember.prototype.calculateRespectGain = function(gang) {
GangMember.prototype.calculateWantedLevelGain = function(gang) {
const task = this.getTask();
if (task == null || !(task instanceof GangMemberTask) || task.baseWanted === 0) {return 0;}
var statWeight = (task.hackWeight/100) * this.hack +
(task.strWeight/100) * this.str +
(task.defWeight/100) * this.def +
(task.dexWeight/100) * this.dex +
(task.agiWeight/100) * this.agi +
(task.chaWeight/100) * this.cha;
if (task == null || !(task instanceof GangMemberTask) || task.baseWanted === 0) { return 0; }
let statWeight = (task.hackWeight / 100) * this.hack +
(task.strWeight / 100) * this.str +
(task.defWeight / 100) * this.def +
(task.dexWeight / 100) * this.dex +
(task.agiWeight / 100) * this.agi +
(task.chaWeight / 100) * this.cha;
statWeight -= (3.5 * task.difficulty);
if (statWeight <= 0) { return 0; }
const territoryMult = Math.pow(AllGangs[gang.facName].territory * 100, task.territory.wanted) / 100;
if (isNaN(territoryMult) || territoryMult <= 0) { return 0; }
if (task.baseWanted < 0) {
return 0.5 * task.baseWanted * statWeight * territoryMult;
return 0.4 * task.baseWanted * statWeight * territoryMult;
} else {
return 7 * task.baseWanted / (Math.pow(3 * statWeight * territoryMult, 0.8));
const calc = 7 * task.baseWanted / (Math.pow(3 * statWeight * territoryMult, 0.8));
// Put an arbitrary cap on this to prevent wanted level from rising too fast if the
// denominator is very small. Might want to rethink formula later
return Math.min(100, calc);
}
}
@@ -770,9 +791,11 @@ GangMember.prototype.ascend = function() {
// Returns the multipliers that would be gained from ascension
GangMember.prototype.getAscensionResults = function() {
// Calculate ascension bonus to stat multipliers.
// This is based on the current number of multipliers from Non-Augmentation upgrades
// + Ascension Bonus = N% of current bonus from Augmentations
/**
* Calculate ascension bonus to stat multipliers.
* This is based on the current number of multipliers from Non-Augmentation upgrades
* + Ascension Bonus = N% of current bonus from Augmentations
*/
let hack = 1;
let str = 1;
let def = 1;
@@ -837,7 +860,7 @@ GangMember.fromJSON = function(value) {
Reviver.constructors.GangMember = GangMember;
//Defines tasks that Gang Members can work on
// Defines tasks that Gang Members can work on
function GangMemberTask(name="", desc="", isHacking=false, isCombat=false,
params={baseRespect: 0, baseWanted: 0, baseMoney: 0,
hackWeight: 0, strWeight: 0, defWeight: 0,
@@ -935,7 +958,7 @@ GangMemberUpgrade.prototype.createDescription = function() {
this.desc = lines.join("<br>");
}
//Passes in a GangMember object
// Passes in a GangMember object
GangMemberUpgrade.prototype.apply = function(member) {
if (this.mults.str != null) { member.str_mult *= this.mults.str; }
if (this.mults.def != null) { member.def_mult *= this.mults.def; }
@@ -971,7 +994,7 @@ gangMemberUpgradesMetadata.forEach((e) => {
Gang.prototype.createGangMemberUpgradeBox = function(player, initialFilter="") {
const boxId = "gang-member-upgrade-popup-box";
if (UIElems.gangMemberUpgradeBoxOpened) {
//Already opened, refreshing
// Already opened, refreshing
if (UIElems.gangMemberUpgradeBoxElements == null || UIElems.gangMemberUpgradeBox == null || UIElems.gangMemberUpgradeBoxContent == null) {
console.error("Refreshing Gang member upgrade box throws error because required elements are null");
return;
@@ -991,7 +1014,7 @@ Gang.prototype.createGangMemberUpgradeBox = function(player, initialFilter="") {
}
}
} else {
//New popup
// New popup
UIElems.gangMemberUpgradeBoxFilter = createElement("input", {
type:"text", placeholder:"Filter gang members",
value:initialFilter,
@@ -1023,7 +1046,7 @@ Gang.prototype.createGangMemberUpgradeBox = function(player, initialFilter="") {
}
}
//Create upgrade panels for each individual Gang Member
// Create upgrade panels for each individual Gang Member
GangMember.prototype.createGangMemberUpgradePanel = function(gangObj, player) {
var container = createElement("div", {
border:"1px solid white",
@@ -1045,7 +1068,7 @@ GangMember.prototype.createGangMemberUpgradePanel = function(gangObj, player) {
"Cha: " + this.cha + " (x" + formatNumber(this.cha_mult * this.cha_asc_mult, 2) + ")\n",
});
//Already purchased upgrades
// Already purchased upgrades
const ownedUpgradesElements = [];
function pushOwnedUpgrade(upgName) {
const upg = GangMemberUpgrades[upgName];
@@ -1071,7 +1094,7 @@ GangMember.prototype.createGangMemberUpgradePanel = function(gangObj, player) {
container.appendChild(ownedUpgrades);
container.appendChild(createElement("br", {}));
//Upgrade buttons. Only show upgrades that can be afforded
// Upgrade buttons. Only show upgrades that can be afforded
const weaponUpgrades = [];
const armorUpgrades = [];
const vehicleUpgrades = [];
@@ -1203,18 +1226,18 @@ Gang.prototype.displayGangContent = function(player) {
if (!UIElems.gangContentCreated || UIElems.gangContainer == null) {
UIElems.gangContentCreated = true;
//Create gang container
// Create gang container
UIElems.gangContainer = createElement("div", {
id:"gang-container", class:"generic-menupage-container",
});
//Get variables
// Get variables
var facName = this.facName,
members = this.members,
wanted = this.wanted,
respect = this.respect;
//Back button
// Back button
UIElems.gangContainer.appendChild(createElement("a", {
class:"a-link-button", display:"inline-block", innerText:"Back",
clickListener:()=>{
@@ -1224,7 +1247,7 @@ Gang.prototype.displayGangContent = function(player) {
}
}));
//Buttons to switch between panels
// Buttons to switch between panels
UIElems.managementButton = createElement("a", {
id:"gang-management-subpage-button", class:"a-link-button-inactive",
display:"inline-block", innerHTML: "Gang Management (Alt+1)",
@@ -1256,7 +1279,7 @@ Gang.prototype.displayGangContent = function(player) {
UIElems.gangContainer.appendChild(UIElems.managementButton);
UIElems.gangContainer.appendChild(UIElems.territoryButton);
//Subpage for managing gang members
// Subpage for managing gang members
UIElems.gangManagementSubpage = createElement("div", {
display:"block", id:"gang-management-subpage",
});
@@ -1340,6 +1363,7 @@ Gang.prototype.displayGangContent = function(player) {
innerText: "Cancel",
});
createPopup(popupId, [txt, br, nameInput, yesBtn, noBtn]);
nameInput.focus();
}
});
UIElems.gangManagementSubpage.appendChild(UIElems.gangRecruitMemberButton);
@@ -1352,7 +1376,7 @@ Gang.prototype.displayGangContent = function(player) {
});
UIElems.gangManagementSubpage.appendChild(UIElems.gangRecruitRequirementText);
//Gang Member List management buttons (Expand/Collapse All, select a single member)
// Gang Member List management buttons (Expand/Collapse All, select a single member)
UIElems.gangManagementSubpage.appendChild(createElement("br", {}));
UIElems.gangExpandAllButton = createElement("a", {
class:"a-link-button", display:"inline-block",
@@ -1400,17 +1424,17 @@ Gang.prototype.displayGangContent = function(player) {
UIElems.gangManagementSubpage.appendChild(UIElems.gangMemberFilter);
UIElems.gangManagementSubpage.appendChild(UIElems.gangManageEquipmentButton);
//Gang Member list
// Gang Member list
UIElems.gangMemberList = createElement("ul", {id:"gang-member-list"});
this.displayGangMemberList();
UIElems.gangManagementSubpage.appendChild(UIElems.gangMemberList);
//Subpage for seeing gang territory information
// Subpage for seeing gang territory information
UIElems.gangTerritorySubpage = createElement("div", {
id:"gang-territory-subpage", display:"none"
});
//Info text for territory page
// Info text for territory page
UIElems.gangTerritoryDescText = createElement("p", {
width:"70%",
innerHTML:
@@ -1575,7 +1599,7 @@ Gang.prototype.updateGangContent = function() {
}
}
} else {
//Update information for overall gang
// Update information for overall gang
if (UIElems.gangInfo instanceof Element) {
var faction = Factions[this.facName];
var rep;
@@ -1585,7 +1609,7 @@ Gang.prototype.updateGangContent = function() {
rep = faction.playerReputation;
}
removeChildrenFromElement(UIElems.gangInfo);
UIElems.gangInfo.appendChild(createElement("p", { // Respect
UIElems.gangInfo.appendChild(createElement("p", { // Respect
display: "inline-block",
innerText: "Respect: " + formatNumber(this.respect, 6) +
" (" + formatNumber(5*this.respectGainRate, 6) + " / sec)",
@@ -1596,7 +1620,7 @@ Gang.prototype.updateGangContent = function() {
}));
UIElems.gangInfo.appendChild(createElement("br"));
UIElems.gangInfo.appendChild(createElement("p", { // Wanted level
UIElems.gangInfo.appendChild(createElement("p", { // Wanted level
display: "inline-block",
innerText: "Wanted Level: " + formatNumber(this.wanted, 6) +
" (" + formatNumber(5*this.wantedGainRate, 6) + " / sec)",
@@ -1608,20 +1632,20 @@ Gang.prototype.updateGangContent = function() {
var wantedPenalty = this.getWantedPenalty();
wantedPenalty = (1 - wantedPenalty) * 100;
UIElems.gangInfo.appendChild(createElement("p", { // Wanted Level multiplier
UIElems.gangInfo.appendChild(createElement("p", { // Wanted Level multiplier
display: "inline-block",
innerText: `Wanted Level Penalty: -${formatNumber(wantedPenalty, 2)}%`,
tooltip: "Penalty for respect and money gain rates due to Wanted Level"
}));
UIElems.gangInfo.appendChild(createElement("br"));
UIElems.gangInfo.appendChild(createElement("p", { // Money gain rate
UIElems.gangInfo.appendChild(createElement("p", { // Money gain rate
display: "inline-block",
innerText: `Money gain rate: ${numeralWrapper.format(5 * this.moneyGainRate, "$0.000a")} / sec`,
}));
UIElems.gangInfo.appendChild(createElement("br"));
//Fix some rounding issues graphically
// Fix some rounding issues graphically
var territoryMult = AllGangs[this.facName].territory * 100;
let displayNumber;
if (territoryMult <= 0) {
@@ -1656,7 +1680,7 @@ Gang.prototype.updateGangContent = function() {
console.error("gang-info DOM element DNE");
}
//Toggle the 'Recruit member button' if valid
// Toggle the 'Recruit member button' if valid
const numMembers = this.members.length;
const respectCost = this.getRespectNeededToRecruitMember();
@@ -1674,14 +1698,14 @@ Gang.prototype.updateGangContent = function() {
UIElems.gangRecruitRequirementText.innerHTML = `${formatNumber(respectCost, 2)} respect needed to recruit next member`;
}
//Update information for each gang member
// Update information for each gang member
for (let i = 0; i < this.members.length; ++i) {
this.updateGangMemberDisplayElement(this.members[i]);
}
}
}
//Takes in a GangMember object
// Takes in a GangMember object
Gang.prototype.createGangMemberDisplayElement = function(memberObj) {
if (!UIElems.gangContentCreated) { return; }
const name = memberObj.name;
@@ -1822,7 +1846,7 @@ Gang.prototype.createGangMemberDisplayElement = function(memberObj) {
taskDiv.appendChild(taskSelector);
taskDiv.appendChild(gainInfo);
//Panel for Description of task
// Panel for Description of task
var taskDescDiv = createElement("div", {
class:"gang-member-info-div",
id: name + "gang-member-task-desc",

View File

@@ -6,34 +6,34 @@
*/
import { IReturnStatus } from "../types";
import { HacknetServer } from "../Hacknet/HacknetServer";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Server } from "../Server/Server";
function baseCheck(server: Server | HacknetServer, fnName: string): IReturnStatus {
if (server instanceof HacknetServer) {
function baseCheck(server: Server, fnName: string): IReturnStatus {
const hostname = server.hostname;
if (!("requiredHackingSkill" in server)) {
return {
res: false,
msg: `Cannot ${fnName} ${server.hostname} server because it is a Hacknet Node`
msg: `Cannot ${fnName} ${hostname} server because it is a Hacknet Node`
}
}
if (server.hasAdminRights === false) {
return {
res: false,
msg: `Cannot ${fnName} ${server.hostname} server because you do not have root access`,
msg: `Cannot ${fnName} ${hostname} server because you do not have root access`,
}
}
return { res: true }
}
export function netscriptCanHack(server: Server | HacknetServer, p: IPlayer): IReturnStatus {
export function netscriptCanHack(server: Server, p: IPlayer): IReturnStatus {
const initialCheck = baseCheck(server, "hack");
if (!initialCheck.res) { return initialCheck; }
let s = <Server>server;
let s = server;
if (s.requiredHackingSkill > p.hacking_skill) {
return {
res: false,
@@ -44,10 +44,10 @@ export function netscriptCanHack(server: Server | HacknetServer, p: IPlayer): IR
return { res: true }
}
export function netscriptCanGrow(server: Server | HacknetServer): IReturnStatus {
export function netscriptCanGrow(server: Server): IReturnStatus {
return baseCheck(server, "grow");
}
export function netscriptCanWeaken(server: Server | HacknetServer): IReturnStatus {
export function netscriptCanWeaken(server: Server): IReturnStatus {
return baseCheck(server, "weaken");
}

View 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
*/

View File

@@ -1,35 +1,51 @@
import { HacknetNode,
BaseCostForHacknetNode,
HacknetNodePurchaseNextMult,
HacknetNodeMaxLevel,
HacknetNodeMaxRam,
HacknetNodeMaxCores } from "./HacknetNode";
import { HacknetServer,
BaseCostForHacknetServer,
HacknetServerPurchaseMult,
HacknetServerMaxLevel,
HacknetServerMaxRam,
HacknetServerMaxCores,
HacknetServerMaxCache,
MaxNumberHacknetServers } from "./HacknetServer";
import { HashManager } from "./HashManager";
import { HashUpgrades } from "./HashUpgrades";
/**
* 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,
HacknetNodePurchaseNextMult,
HacknetNodeMaxLevel,
HacknetNodeMaxRam,
HacknetNodeMaxCores
} from "./HacknetNode";
import {
HacknetServer,
BaseCostForHacknetServer,
HacknetServerPurchaseMult,
HacknetServerMaxLevel,
HacknetServerMaxRam,
HacknetServerMaxCores,
HacknetServerMaxCache,
MaxNumberHacknetServers
} from "./HacknetServer";
import { HashManager } from "./HashManager";
import { HashUpgrades } from "./HashUpgrades";
import { generateRandomContract } from "../CodingContractGenerator";
import { iTutorialSteps, iTutorialNextStep,
ITutorial} from "../InteractiveTutorial";
import { Player } from "../Player";
import { AddToAllServers,
AllServers } from "../Server/AllServers";
import { GetServerByHostname } from "../Server/ServerHelpers";
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
import { Page, routing } from "../ui/navigationTracking";
import { generateRandomContract } from "../CodingContractGenerator";
import {
iTutorialSteps,
iTutorialNextStep,
ITutorial
} from "../InteractiveTutorial";
import { Player } from "../Player";
import { AllServers } from "../Server/AllServers";
import { GetServerByHostname } from "../Server/ServerHelpers";
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
import { Page, routing } from "../ui/navigationTracking";
import {getElementById} from "../../utils/uiHelpers/getElementById";
import { getElementById } from "../../utils/uiHelpers/getElementById";
import React from "react";
import ReactDOM from "react-dom";
import { HacknetRoot } from "./ui/Root";
import React from "react";
import ReactDOM from "react-dom";
import { HacknetRoot } from "./ui/Root";
let hacknetNodesDiv;
function hacknetNodesInit() {
@@ -66,7 +82,7 @@ export function purchaseHacknet() {
if (!Player.canAfford(cost)) { return -1; }
Player.loseMoney(cost);
const server = Player.createHacknetServer();
Player.hashManager.updateCapacity(Player);
updateHashManagerCapacity();
return numOwned;
} else {
@@ -79,8 +95,7 @@ export function purchaseHacknet() {
// Auto generate a name for the Node
const name = "hacknet-node-" + numOwned;
const node = new HacknetNode(name);
node.updateMoneyGainRate(Player);
const node = new HacknetNode(name, Player.hacknet_node_money_mult);
Player.loseMoney(cost);
Player.hacknetNodes.push(node);
@@ -110,32 +125,32 @@ 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`);
}
if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(1, Player))) {
if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(1, Player.hacknet_node_level_cost_mult))) {
return 0;
}
let min = 1;
let max = maxLevel - 1;
let levelsToMax = maxLevel - nodeObj.level;
if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(levelsToMax, Player))) {
if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(levelsToMax, Player.hacknet_node_level_cost_mult))) {
return levelsToMax;
}
while (min <= max) {
var curr = (min + max) / 2 | 0;
if (curr !== maxLevel &&
Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, Player)) &&
Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr + 1, Player))) {
Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, Player.hacknet_node_level_cost_mult)) &&
Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr + 1, Player.hacknet_node_level_cost_mult))) {
return Math.min(levelsToMax, curr);
} else if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr, Player))) {
} else if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr, Player.hacknet_node_level_cost_mult))) {
max = curr - 1;
} else if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, Player))) {
} else if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, Player.hacknet_node_level_cost_mult))) {
min = curr + 1;
} else {
return Math.min(levelsToMax, curr);
@@ -144,12 +159,13 @@ 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`);
}
if (Player.money.lt(nodeObj.calculateRamUpgradeCost(1, Player))) {
if (Player.money.lt(nodeObj.calculateRamUpgradeCost(1, Player.hacknet_node_ram_cost_mult))) {
return 0;
}
@@ -159,45 +175,46 @@ export function getMaxNumberRamUpgrades(nodeObj, maxLevel) {
} else {
levelsToMax = Math.round(Math.log2(maxLevel / nodeObj.ram));
}
if (Player.money.gt(nodeObj.calculateRamUpgradeCost(levelsToMax, Player))) {
if (Player.money.gt(nodeObj.calculateRamUpgradeCost(levelsToMax, Player.hacknet_node_ram_cost_mult))) {
return levelsToMax;
}
//We'll just loop until we find the max
for (let i = levelsToMax-1; i >= 0; --i) {
if (Player.money.gt(nodeObj.calculateRamUpgradeCost(i, Player))) {
if (Player.money.gt(nodeObj.calculateRamUpgradeCost(i, Player.hacknet_node_ram_cost_mult))) {
return i;
}
}
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`);
}
if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(1, Player))) {
if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(1, Player.hacknet_node_core_cost_mult))) {
return 0;
}
let min = 1;
let max = maxLevel - 1;
const levelsToMax = maxLevel - nodeObj.cores;
if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(levelsToMax, Player))) {
if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(levelsToMax, Player.hacknet_node_core_cost_mult))) {
return levelsToMax;
}
//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 &&
Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, Player)) &&
Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr + 1, Player))) {
Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, Player.hacknet_node_core_cost_mult)) &&
Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr + 1, Player.hacknet_node_core_cost_mult))) {
return Math.min(levelsToMax, curr);
} else if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr, Player))) {
} else if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr, Player.hacknet_node_core_cost_mult))) {
max = curr - 1;
} else if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, Player))) {
} else if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, Player.hacknet_node_core_cost_mult))) {
min = curr + 1;
} else {
return Math.min(levelsToMax, curr);
@@ -207,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`);
@@ -242,7 +260,136 @@ export function getMaxNumberCacheUpgrades(nodeObj, maxLevel) {
return 0;
}
// Initial construction of Hacknet Nodes UI
export function purchaseLevelUpgrade(node, levels=1) {
const sanitizedLevels = Math.round(levels);
const cost = node.calculateLevelUpgradeCost(sanitizedLevels, Player.hacknet_node_level_cost_mult);
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
return false;
}
const isServer = (node instanceof HacknetServer);
// If we're at max level, return false
if (node.level >= (isServer ? HacknetServerMaxLevel : HacknetNodeMaxLevel)) {
return false;
}
// If the number of specified upgrades would exceed the max level, calculate
// the maximum number of upgrades and use that
if (node.level + sanitizedLevels > (isServer ? HacknetServerMaxLevel : HacknetNodeMaxLevel)) {
const diff = Math.max(0, (isServer ? HacknetServerMaxLevel : HacknetNodeMaxLevel) - node.level);
return purchaseLevelUpgrade(node, diff);
}
if (!Player.canAfford(cost)) {
return false;
}
Player.loseMoney(cost);
node.upgradeLevel(sanitizedLevels, Player.hacknet_node_money_mult);
return true;
}
export function purchaseRamUpgrade(node, levels=1) {
const sanitizedLevels = Math.round(levels);
const cost = node.calculateRamUpgradeCost(sanitizedLevels, Player.hacknet_node_ram_cost_mult);
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
return false;
}
const isServer = (node instanceof HacknetServer);
// Fail if we're already at max
if (node.ram >= (isServer ? HacknetServerMaxRam : HacknetNodeMaxRam)) {
return false;
}
// If the number of specified upgrades would exceed the max RAM, calculate the
// max possible number of upgrades and use that
if (isServer) {
if (node.maxRam * Math.pow(2, sanitizedLevels) > HacknetServerMaxRam) {
const diff = Math.max(0, Math.log2(Math.round(HacknetServerMaxRam / node.maxRam)));
return purchaseRamUpgrade(node, diff);
}
} else {
if (node.ram * Math.pow(2, sanitizedLevels) > HacknetNodeMaxRam) {
const diff = Math.max(0, Math.log2(Math.round(HacknetNodeMaxRam / node.ram)));
return purchaseRamUpgrade(node, diff);
}
}
if (!Player.canAfford(cost)) {
return false;
}
Player.loseMoney(cost);
node.upgradeRam(sanitizedLevels, Player.hacknet_node_money_mult);
return true;
}
export function purchaseCoreUpgrade(node, levels=1) {
const sanitizedLevels = Math.round(levels);
const cost = node.calculateCoreUpgradeCost(sanitizedLevels, Player.hacknet_node_core_cost_mult);
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
return false;
}
const isServer = (node instanceof HacknetServer);
// Fail if we're already at max
if (node.cores >= (isServer ? HacknetServerMaxCores : HacknetNodeMaxCores)) {
return false;
}
// If the specified number of upgrades would exceed the max Cores, calculate
// the max possible number of upgrades and use that
if (node.cores + sanitizedLevels > (isServer ? HacknetServerMaxCores : HacknetNodeMaxCores)) {
const diff = Math.max(0, (isServer ? HacknetServerMaxCores : HacknetNodeMaxCores) - node.cores);
return purchaseCoreUpgrade(node, diff);
}
if (!Player.canAfford(cost)) {
return false;
}
Player.loseMoney(cost);
node.upgradeCore(sanitizedLevels, Player.hacknet_node_money_mult);
return true;
}
export function purchaseCacheUpgrade(node, levels=1) {
const sanitizedLevels = Math.round(levels);
const cost = node.calculateCacheUpgradeCost(sanitizedLevels);
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
return false;
}
if (!(node instanceof HacknetServer)) {
console.warn(`purchaseCacheUpgrade() called for a non-HacknetNode`);
return false;
}
// Fail if we're already at max
if (node.cache + sanitizedLevels > HacknetServerMaxCache) {
const diff = Math.max(0, HacknetServerMaxCache - node.cache);
return purchaseCacheUpgrade(node, diff);
}
if (!Player.canAfford(cost)) {
return false;
}
Player.loseMoney(cost);
node.upgradeCache(sanitizedLevels);
return true;
}
// Create/Refresh Hacknet Nodes UI
export function renderHacknetNodesUI() {
if (!routing.isOn(Page.HacknetNodes)) { return; }
@@ -294,7 +441,10 @@ function processAllHacknetServerEarnings(numCycles) {
let hashes = 0;
for (let i = 0; i < Player.hacknetNodes.length; ++i) {
const hserver = AllServers[Player.hacknetNodes[i]]; // hacknetNodes array only contains the IP addresses
// hacknetNodes array only contains the IP addresses of the servers.
// Also, update the hash rate before processing
const hserver = AllServers[Player.hacknetNodes[i]];
hserver.updateHashRate(Player.hacknet_node_money_mult);
hashes += hserver.process(numCycles);
}
@@ -303,6 +453,37 @@ function processAllHacknetServerEarnings(numCycles) {
return hashes;
}
export function updateHashManagerCapacity() {
if (!(Player.hashManager instanceof HashManager)) {
console.error(`Player does not have a HashManager`);
return;
}
const nodes = Player.hacknetNodes;
if (nodes.length === 0) {
Player.hashManager.updateCapacity(0);
return;
}
let total = 0;
for (let i = 0; i < nodes.length; ++i) {
if (typeof nodes[i] !== "string") {
Player.hashManager.updateCapacity(0);
return;
}
const h = AllServers[nodes[i]];
if (!(h instanceof HacknetServer)) {
Player.hashManager.updateCapacity(0);
return;
}
total += h.hashCapacity;
}
Player.hashManager.updateCapacity(total);
}
export function purchaseHashUpgrade(upgName, upgTarget) {
if (!(Player.hashManager instanceof HashManager)) {
console.error(`Player does not have a HashManager`);
@@ -317,6 +498,7 @@ export function purchaseHashUpgrade(upgName, upgTarget) {
switch (upgName) {
case "Sell for Money": {
Player.gainMoney(upg.value);
Player.recordMoneySource(upg.value, "hacknetnode");
break;
}
case "Sell for Corporation Funds": {

View File

@@ -9,7 +9,6 @@ import { IHacknetNode } from "./IHacknetNode";
import { CONSTANTS } from "../Constants";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { IPlayer } from "../PersonObjects/IPlayer";
import { dialogBoxCreate } from "../../utils/DialogBox";
import { Generic_fromJSON,
@@ -62,12 +61,14 @@ export class HacknetNode implements IHacknetNode {
// Total money earned by this Node
totalMoneyGenerated: number = 0;
constructor(name: string="") {
constructor(name: string="", prodMult: number=1) {
this.name = name;
this.updateMoneyGainRate(prodMult);
}
// Get the cost to upgrade this Node's number of cores
calculateCoreUpgradeCost(levels: number=1, p: IPlayer): number {
calculateCoreUpgradeCost(levels: number=1, costMult: number): number {
const sanitizedLevels = Math.round(levels);
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
return 0;
@@ -86,13 +87,13 @@ export class HacknetNode implements IHacknetNode {
++currentCores;
}
totalCost *= p.hacknet_node_core_cost_mult;
totalCost *= costMult;
return totalCost;
}
// Get the cost to upgrade this Node's level
calculateLevelUpgradeCost(levels: number=1, p: IPlayer): number {
calculateLevelUpgradeCost(levels: number=1, costMult: number): number {
const sanitizedLevels = Math.round(levels);
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
return 0;
@@ -110,11 +111,11 @@ export class HacknetNode implements IHacknetNode {
++currLevel;
}
return BaseCostForHacknetNode / 2 * totalMultiplier * p.hacknet_node_level_cost_mult;
return BaseCostForHacknetNode / 2 * totalMultiplier * costMult;
}
// Get the cost to upgrade this Node's RAM
calculateRamUpgradeCost(levels: number=1, p: IPlayer): number {
calculateRamUpgradeCost(levels: number=1, costMult: number): number {
const sanitizedLevels = Math.round(levels);
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
return 0;
@@ -138,7 +139,7 @@ export class HacknetNode implements IHacknetNode {
++numUpgrades;
}
totalCost *= p.hacknet_node_ram_cost_mult;
totalCost *= costMult;
return totalCost;
}
@@ -161,112 +162,37 @@ export class HacknetNode implements IHacknetNode {
// Upgrade this Node's number of cores, if possible
// Returns a boolean indicating whether new cores were successfully bought
purchaseCoreUpgrade(levels: number=1, p: IPlayer): boolean {
const sanitizedLevels = Math.round(levels);
const cost = this.calculateCoreUpgradeCost(sanitizedLevels, p);
if (isNaN(cost) || sanitizedLevels < 0) {
return false;
}
// Fail if we're already at max
if (this.cores >= HacknetNodeMaxCores) {
return false;
}
// If the specified number of upgrades would exceed the max Cores, calculate
// the max possible number of upgrades and use that
if (this.cores + sanitizedLevels > HacknetNodeMaxCores) {
const diff = Math.max(0, HacknetNodeMaxCores - this.cores);
return this.purchaseCoreUpgrade(diff, p);
}
if (!p.canAfford(cost)) {
return false;
}
p.loseMoney(cost);
this.cores = Math.round(this.cores + sanitizedLevels); // Just in case of floating point imprecision
this.updateMoneyGainRate(p);
return true;
upgradeCore(levels: number=1, prodMult: number): void {
this.cores = Math.min(HacknetNodeMaxCores, Math.round(this.cores + levels));
this.updateMoneyGainRate(prodMult);
}
// Upgrade this Node's level, if possible
// Returns a boolean indicating whether the level was successfully updated
purchaseLevelUpgrade(levels: number=1, p: IPlayer): boolean {
const sanitizedLevels = Math.round(levels);
const cost = this.calculateLevelUpgradeCost(sanitizedLevels, p);
if (isNaN(cost) || sanitizedLevels < 0) {
return false;
}
// If we're at max level, return false
if (this.level >= HacknetNodeMaxLevel) {
return false;
}
// If the number of specified upgrades would exceed the max level, calculate
// the maximum number of upgrades and use that
if (this.level + sanitizedLevels > HacknetNodeMaxLevel) {
var diff = Math.max(0, HacknetNodeMaxLevel - this.level);
return this.purchaseLevelUpgrade(diff, p);
}
if (!p.canAfford(cost)) {
return false;
}
p.loseMoney(cost);
this.level = Math.round(this.level + sanitizedLevels); // Just in case of floating point imprecision
this.updateMoneyGainRate(p);
return true;
upgradeLevel(levels: number=1, prodMult: number): void {
this.level = Math.min(HacknetNodeMaxLevel, Math.round(this.level + levels));
this.updateMoneyGainRate(prodMult);
}
// Upgrade this Node's RAM, if possible
// Returns a boolean indicating whether the RAM was successfully upgraded
purchaseRamUpgrade(levels: number=1, p: IPlayer): boolean {
const sanitizedLevels = Math.round(levels);
const cost = this.calculateRamUpgradeCost(sanitizedLevels, p);
if (isNaN(cost) || sanitizedLevels < 0) {
return false;
}
// Fail if we're already at max
if (this.ram >= HacknetNodeMaxRam) {
return false;
}
// If the number of specified upgrades would exceed the max RAM, calculate the
// max possible number of upgrades and use that
if (this.ram * Math.pow(2, sanitizedLevels) > HacknetNodeMaxRam) {
var diff = Math.max(0, Math.log2(Math.round(HacknetNodeMaxRam / this.ram)));
return this.purchaseRamUpgrade(diff, p);
}
if (!p.canAfford(cost)) {
return false;
}
p.loseMoney(cost);
for (let i = 0; i < sanitizedLevels; ++i) {
upgradeRam(levels: number=1, prodMult: number): void {
for (let i = 0; i < levels; ++i) {
this.ram *= 2; // Ram is always doubled
}
this.ram = Math.round(this.ram); // Handle any floating point precision issues
this.updateMoneyGainRate(p);
return true;
this.updateMoneyGainRate(prodMult);
}
// Re-calculate this Node's production and update the moneyGainRatePerSecond prop
updateMoneyGainRate(p: IPlayer): void {
updateMoneyGainRate(prodMult: number): void {
//How much extra $/s is gained per level
var gainPerLevel = HacknetNodeMoneyGainPerLevel;
this.moneyGainRatePerSecond = (this.level * gainPerLevel) *
Math.pow(1.035, this.ram - 1) *
((this.cores + 5) / 6) *
p.hacknet_node_money_mult *
prodMult *
BitNodeMultipliers.HacknetNodeMoney;
if (isNaN(this.moneyGainRatePerSecond)) {
this.moneyGainRatePerSecond = 0;

View File

@@ -6,15 +6,16 @@ import { CONSTANTS } from "../Constants";
import { IHacknetNode } from "./IHacknetNode";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { IPlayer } from "../PersonObjects/IPlayer";
import { BaseServer } from "../Server/BaseServer";
import { RunningScript } from "../Script/RunningScript";
import { createRandomIp } from "../../utils/IPAddress";
import { Generic_fromJSON,
Generic_toJSON,
Reviver } from "../../utils/JSONReviver";
import {
Generic_fromJSON,
Generic_toJSON,
Reviver
} from "../../utils/JSONReviver";
// Constants for Hacknet Server stats/production
export const HacknetServerHashesPerLevel: number = 0.001;
@@ -31,7 +32,7 @@ export const HacknetServerUpgradeRamMult: number = 1.4; // Multiplier for co
export const HacknetServerUpgradeCoreMult: number = 1.55; // Multiplier for cost when buying another core
export const HacknetServerUpgradeCacheMult: number = 1.85; // Multiplier for cost when upgrading cache
export const MaxNumberHacknetServers: number = 25; // Max number of Hacknet Servers you can own
export const MaxNumberHacknetServers: number = 20; // Max number of Hacknet Servers you can own
// Constants for max upgrade levels for Hacknet Server
export const HacknetServerMaxLevel: number = 300;
@@ -46,7 +47,6 @@ interface IConstructorParams {
isConnectedTo?: boolean;
maxRam?: number;
organizationName?: string;
player?: IPlayer;
}
export class HacknetServer extends BaseServer implements IHacknetNode {
@@ -81,10 +81,6 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
this.maxRam = 1;
this.updateHashCapacity();
if (params.player) {
this.updateHashRate(params.player);
}
}
calculateCacheUpgradeCost(levels: number): number {
@@ -109,7 +105,7 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
return totalCost;
}
calculateCoreUpgradeCost(levels: number, p: IPlayer): number {
calculateCoreUpgradeCost(levels: number, costMult: number): number {
const sanitizedLevels = Math.round(levels);
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
return 0;
@@ -127,12 +123,12 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
++currentCores;
}
totalCost *= BaseCostForHacknetServerCore;
totalCost *= p.hacknet_node_core_cost_mult;
totalCost *= costMult;
return totalCost;
}
calculateLevelUpgradeCost(levels: number, p: IPlayer): number {
calculateLevelUpgradeCost(levels: number, costMult: number): number {
const sanitizedLevels = Math.round(levels);
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
return 0;
@@ -150,10 +146,10 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
++currLevel;
}
return 10 * BaseCostForHacknetServer * totalMultiplier * p.hacknet_node_level_cost_mult;
return 10 * BaseCostForHacknetServer * totalMultiplier * costMult;
}
calculateRamUpgradeCost(levels: number, p: IPlayer): number {
calculateRamUpgradeCost(levels: number, costMult: number): number {
const sanitizedLevels = Math.round(levels);
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
return 0;
@@ -175,149 +171,48 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
currentRam *= 2;
++numUpgrades;
}
totalCost *= p.hacknet_node_ram_cost_mult;
totalCost *= costMult;
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
purchaseCacheUpgrade(levels: number, p: IPlayer): boolean {
const sanitizedLevels = Math.round(levels);
const cost = this.calculateCacheUpgradeCost(levels);
if (isNaN(cost) || cost <= 0 || sanitizedLevels <= 0) {
return false;
}
if (this.cache >= HacknetServerMaxCache) {
return false;
}
// If the specified number of upgrades would exceed the max, try to purchase
// the maximum possible
if (this.cache + levels > HacknetServerMaxCache) {
const diff = Math.max(0, HacknetServerMaxCache - this.cache);
return this.purchaseCacheUpgrade(diff, p);
}
if (!p.canAfford(cost)) {
return false;
}
p.loseMoney(cost);
this.cache = Math.round(this.cache + sanitizedLevels);
upgradeCache(levels: number): void {
this.cache = Math.min(HacknetServerMaxCache, Math.round(this.cache + levels));
this.updateHashCapacity();
return true;
}
// Returns a boolean indicating whether the number of cores was successfully upgraded
purchaseCoreUpgrade(levels: number, p: IPlayer): boolean {
const sanitizedLevels = Math.round(levels);
const cost = this.calculateCoreUpgradeCost(sanitizedLevels, p);
if (isNaN(cost) || cost <= 0 || sanitizedLevels <= 0) {
return false;
}
if (this.cores >= HacknetServerMaxCores) {
return false;
}
// If the specified number of upgrades would exceed the max, try to purchase
// the maximum possible
if (this.cores + sanitizedLevels > HacknetServerMaxCores) {
const diff = Math.max(0, HacknetServerMaxCores - this.cores);
return this.purchaseCoreUpgrade(diff, p);
}
if (!p.canAfford(cost)) {
return false;
}
p.loseMoney(cost);
this.cores = Math.round(this.cores + sanitizedLevels);
this.updateHashRate(p);
return true;
upgradeCore(levels: number, prodMult: number): void {
this.cores = Math.min(HacknetServerMaxCores, Math.round(this.cores + levels));
this.updateHashRate(prodMult);
}
// Returns a boolean indicating whether the level was successfully upgraded
purchaseLevelUpgrade(levels: number, p: IPlayer): boolean {
const sanitizedLevels = Math.round(levels);
const cost = this.calculateLevelUpgradeCost(sanitizedLevels, p);
if (isNaN(cost) || cost <= 0 || sanitizedLevels <= 0) {
return false;
}
if (this.level >= HacknetServerMaxLevel) {
return false;
}
// If the specified number of upgrades would exceed the max, try to purchase the
// maximum possible
if (this.level + sanitizedLevels > HacknetServerMaxLevel) {
const diff = Math.max(0, HacknetServerMaxLevel - this.level);
return this.purchaseLevelUpgrade(diff, p);
}
if (!p.canAfford(cost)) {
return false;
}
p.loseMoney(cost);
this.level = Math.round(this.level + sanitizedLevels);
this.updateHashRate(p);
return true;
upgradeLevel(levels: number, prodMult: number): void {
this.level = Math.min(HacknetServerMaxLevel, Math.round(this.level + levels));
this.updateHashRate(prodMult);
}
// Returns a boolean indicating whether the RAM was successfully upgraded
purchaseRamUpgrade(levels: number, p: IPlayer): boolean {
const sanitizedLevels = Math.round(levels);
const cost = this.calculateRamUpgradeCost(sanitizedLevels, p);
if(isNaN(cost) || cost <= 0 || sanitizedLevels <= 0) {
return false;
}
if (this.maxRam >= HacknetServerMaxRam) {
return false;
}
// If the specified number of upgrades would exceed the max, try to purchase
// just the maximum possible
if (this.maxRam * Math.pow(2, sanitizedLevels) > HacknetServerMaxRam) {
const diff = Math.max(0, Math.log2(Math.round(HacknetServerMaxRam / this.maxRam)));
return this.purchaseRamUpgrade(diff, p);
}
if (!p.canAfford(cost)) {
return false;
}
p.loseMoney(cost);
for (let i = 0; i < sanitizedLevels; ++i) {
upgradeRam(levels: number, prodMult: number): boolean {
for (let i = 0; i < levels; ++i) {
this.maxRam *= 2;
}
this.maxRam = Math.round(this.maxRam);
this.updateHashRate(p);
this.maxRam = Math.min(HacknetServerMaxRam, Math.round(this.maxRam));
this.updateHashRate(prodMult);
return true;
}
/**
* Whenever a script is run, we must update this server's hash rate
*/
runScript(script: RunningScript, p?: IPlayer): void {
// Whenever a script is run, we must update this server's hash rate
runScript(script: RunningScript, prodMult?: number): void {
super.runScript(script);
if (p) {
this.updateHashRate(p);
if (prodMult != null && typeof prodMult === "number") {
this.updateHashRate(prodMult);
}
}
@@ -325,7 +220,7 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
this.hashCapacity = 32 * Math.pow(2, this.cache);
}
updateHashRate(p: IPlayer): void {
updateHashRate(prodMult: number): void {
const baseGain = HacknetServerHashesPerLevel * this.level;
const ramMultiplier = Math.pow(1.07, Math.log2(this.maxRam));
const coreMultiplier = 1 + (this.cores - 1) / 5;
@@ -333,7 +228,7 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
const hashRate = baseGain * ramMultiplier * coreMultiplier * ramRatio;
this.hashRate = hashRate * p.hacknet_node_money_mult * BitNodeMultipliers.HacknetNodeMoney;
this.hashRate = hashRate * prodMult * BitNodeMultipliers.HacknetNodeMoney;
if (isNaN(this.hashRate)) {
this.hashRate = 0;

View File

@@ -6,12 +6,9 @@
* his hashes, and contains method for grabbing the data/multipliers from
* those upgrades
*/
import { HacknetServer } from "./HacknetServer";
import { HashUpgrades } from "./HashUpgrades";
import { IMap } from "../types";
import { IPlayer } from "../PersonObjects/IPlayer";
import { AllServers } from "../Server/AllServers";
import { Generic_fromJSON,
Generic_toJSON,
Reviver } from "../../utils/JSONReviver";
@@ -84,14 +81,14 @@ export class HashManager {
return upg.getCost(currLevel);
}
prestige(p: IPlayer): void {
prestige(): void {
for (const name in HashUpgrades) {
this.upgrades[name] = 0;
}
this.hashes = 0;
if (p != null) {
this.updateCapacity(p);
}
// When prestiging, player's hacknet nodes are always reset. So capacity = 0
this.updateCapacity(0);
}
/**
@@ -99,14 +96,16 @@ export class HashManager {
*/
refundUpgrade(upgName: string): void {
const upg = HashUpgrades[upgName];
// Reduce the level first, so we get the right cost
--this.upgrades[upgName];
const currLevel = this.upgrades[upgName];
if (upg == null || currLevel == null || currLevel === 0) {
if (upg == null || currLevel == null || currLevel < 0) {
console.error(`Invalid Upgrade Name given to HashManager.upgrade(): ${upgName}`);
return;
}
// Reduce the level first, so we get the right cost
--this.upgrades[upgName];
const cost = upg.getCost(currLevel);
this.hashes += cost;
}
@@ -116,33 +115,11 @@ export class HashManager {
this.hashes = Math.min(this.hashes, this.capacity);
}
updateCapacity(p: IPlayer): void {
if (p.hacknetNodes.length <= 0) {
updateCapacity(newCap: number): void {
if (newCap < 0) {
this.capacity = 0;
return;
}
// Make sure the Player's `hacknetNodes` property actually holds Hacknet Servers
const ip: string = <string>p.hacknetNodes[0];
if (typeof ip !== "string") {
this.capacity = 0;
return;
}
const hserver = <HacknetServer>AllServers[ip];
if (!(hserver instanceof HacknetServer)) {
this.capacity = 0;
return;
}
let total: number = 0;
for (let i = 0; i < p.hacknetNodes.length; ++i) {
const h = <HacknetServer>AllServers[<string>p.hacknetNodes[i]];
total += h.hashCapacity;
}
this.capacity = total;
this.capacity = Math.max(newCap, 0);
}
/**

View File

@@ -1,16 +1,14 @@
// Interface for a Hacknet Node. Implemented by both a basic Hacknet Node,
// and the upgraded Hacknet Server in BitNode-9
import { IPlayer } from "../PersonObjects/IPlayer";
export interface IHacknetNode {
cores: number;
level: number;
onlineTimeSeconds: number;
calculateCoreUpgradeCost: (levels: number, p: IPlayer) => number;
calculateLevelUpgradeCost: (levels: number, p: IPlayer) => number;
calculateRamUpgradeCost: (levels: number, p: IPlayer) => number;
purchaseCoreUpgrade: (levels: number, p: IPlayer) => boolean;
purchaseLevelUpgrade: (levels: number, p: IPlayer) => boolean;
purchaseRamUpgrade: (levels: number, p: IPlayer) => boolean;
calculateCoreUpgradeCost: (levels: number, costMult: number) => number;
calculateLevelUpgradeCost: (levels: number, costMult: number) => number;
calculateRamUpgradeCost: (levels: number, costMult: number) => number;
upgradeCore: (levels: number, prodMult: number) => void;
upgradeLevel: (levels: number, prodMult: number) => void;
upgradeRam: (levels: number, prodMult: number) => void;
}

View File

@@ -15,7 +15,7 @@ export class GeneralInfo extends React.Component {
`you hashes. Hashes can be spent on a variety of different upgrades.`;
} else {
return `Here, you can purchase a Hacknet Node, a specialized machine that can connect ` +
`and contribute its resources to the Hacknet networ. This allows you to take ` +
`and contribute its resources to the Hacknet network. This allows you to take ` +
`a small percentage of profits from hacks performed on the network. Essentially, ` +
`you are renting out your Node's computing power.`;
}

View File

@@ -4,12 +4,19 @@
*/
import React from "react";
import { HacknetNodeMaxLevel,
HacknetNodeMaxRam,
HacknetNodeMaxCores } from "../HacknetNode";
import { getMaxNumberLevelUpgrades,
getMaxNumberRamUpgrades,
getMaxNumberCoreUpgrades } from "../HacknetHelpers";
import {
HacknetNodeMaxLevel,
HacknetNodeMaxRam,
HacknetNodeMaxCores
} from "../HacknetNode";
import {
getMaxNumberLevelUpgrades,
getMaxNumberRamUpgrades,
getMaxNumberCoreUpgrades,
purchaseLevelUpgrade,
purchaseRamUpgrade,
purchaseCoreUpgrade,
} from "../HacknetHelpers";
import { Player } from "../../Player";
@@ -35,7 +42,7 @@ export class HacknetNode extends React.Component {
multiplier = Math.min(levelsToMax, purchaseMult);
}
const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, Player);
const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, Player.hacknet_node_level_cost_mult);
upgradeLevelText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeLevelCost)}`;
if (Player.money.lt(upgradeLevelCost)) {
upgradeLevelClass = "std-button-disabled";
@@ -48,7 +55,7 @@ export class HacknetNode extends React.Component {
if (purchaseMult === "MAX") {
numUpgrades = getMaxNumberLevelUpgrades(node, HacknetNodeMaxLevel);
}
node.purchaseLevelUpgrade(numUpgrades, Player);
purchaseLevelUpgrade(node, numUpgrades);
recalculate();
return false;
}
@@ -66,7 +73,7 @@ export class HacknetNode extends React.Component {
multiplier = Math.min(levelsToMax, purchaseMult);
}
const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, Player);
const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, Player.hacknet_node_ram_cost_mult);
upgradeRamText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeRamCost)}`;
if (Player.money.lt(upgradeRamCost)) {
upgradeRamClass = "std-button-disabled";
@@ -79,7 +86,7 @@ export class HacknetNode extends React.Component {
if (purchaseMult === "MAX") {
numUpgrades = getMaxNumberRamUpgrades(node, HacknetNodeMaxRam);
}
node.purchaseRamUpgrade(numUpgrades, Player);
purchaseRamUpgrade(node, numUpgrades);
recalculate();
return false;
}
@@ -97,7 +104,7 @@ export class HacknetNode extends React.Component {
multiplier = Math.min(levelsToMax, purchaseMult);
}
const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, Player);
const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, Player.hacknet_node_core_cost_mult);
upgradeCoresText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeCoreCost)}`;
if (Player.money.lt(upgradeCoreCost)) {
upgradeCoresClass = "std-button-disabled";
@@ -110,7 +117,7 @@ export class HacknetNode extends React.Component {
if (purchaseMult === "MAX") {
numUpgrades = getMaxNumberCoreUpgrades(node, HacknetNodeMaxCores);
}
node.purchaseCoreUpgrade(numUpgrades, Player);
purchaseCoreUpgrade(node, numUpgrades);
recalculate();
return false;
}

View File

@@ -4,14 +4,23 @@
*/
import React from "react";
import { HacknetServerMaxLevel,
HacknetServerMaxRam,
HacknetServerMaxCores,
HacknetServerMaxCache } from "../HacknetServer";
import { getMaxNumberLevelUpgrades,
getMaxNumberRamUpgrades,
getMaxNumberCoreUpgrades,
getMaxNumberCacheUpgrades } from "../HacknetHelpers";
import {
HacknetServerMaxLevel,
HacknetServerMaxRam,
HacknetServerMaxCores,
HacknetServerMaxCache
} from "../HacknetServer";
import {
getMaxNumberLevelUpgrades,
getMaxNumberRamUpgrades,
getMaxNumberCoreUpgrades,
getMaxNumberCacheUpgrades,
purchaseLevelUpgrade,
purchaseRamUpgrade,
purchaseCoreUpgrade,
purchaseCacheUpgrade,
updateHashManagerCapacity,
} from "../HacknetHelpers";
import { Player } from "../../Player";
@@ -37,7 +46,7 @@ export class HacknetServer extends React.Component {
multiplier = Math.min(levelsToMax, purchaseMult);
}
const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, Player);
const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, Player.hacknet_node_level_cost_mult);
upgradeLevelText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeLevelCost)}`;
if (Player.money.lt(upgradeLevelCost)) {
upgradeLevelClass = "std-button-disabled";
@@ -50,7 +59,7 @@ export class HacknetServer extends React.Component {
if (purchaseMult === "MAX") {
numUpgrades = getMaxNumberLevelUpgrades(node, HacknetServerMaxLevel);
}
node.purchaseLevelUpgrade(numUpgrades, Player);
purchaseLevelUpgrade(node, numUpgrades);
recalculate();
return false;
}
@@ -69,7 +78,7 @@ export class HacknetServer extends React.Component {
multiplier = Math.min(levelsToMax, purchaseMult);
}
const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, Player);
const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, Player.hacknet_node_ram_cost_mult);
upgradeRamText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeRamCost)}`;
if (Player.money.lt(upgradeRamCost)) {
upgradeRamClass = "std-button-disabled";
@@ -82,7 +91,7 @@ export class HacknetServer extends React.Component {
if (purchaseMult === "MAX") {
numUpgrades = getMaxNumberRamUpgrades(node, HacknetServerMaxRam);
}
node.purchaseRamUpgrade(numUpgrades, Player);
purchaseRamUpgrade(node, numUpgrades);
recalculate();
return false;
}
@@ -101,7 +110,7 @@ export class HacknetServer extends React.Component {
multiplier = Math.min(levelsToMax, purchaseMult);
}
const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, Player);
const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, Player.hacknet_node_core_cost_mult);
upgradeCoresText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeCoreCost)}`;
if (Player.money.lt(upgradeCoreCost)) {
upgradeCoresClass = "std-button-disabled";
@@ -114,7 +123,7 @@ export class HacknetServer extends React.Component {
if (purchaseMult === "MAX") {
numUpgrades = getMaxNumberCoreUpgrades(node, HacknetServerMaxCores);
}
node.purchaseCoreUpgrade(numUpgrades, Player);
purchaseCoreUpgrade(node, numUpgrades);
recalculate();
return false;
}
@@ -146,9 +155,9 @@ export class HacknetServer extends React.Component {
if (purchaseMult === "MAX") {
numUpgrades = getMaxNumberCacheUpgrades(node, HacknetServerMaxCache);
}
node.purchaseCacheUpgrade(numUpgrades, Player);
purchaseCacheUpgrade(node, numUpgrades);
recalculate();
Player.hashManager.updateCapacity(Player);
updateHashManagerCapacity();
return false;
}

View File

@@ -39,6 +39,8 @@ export class HacknetRoot extends React.Component {
}
this.createHashUpgradesPopup = this.createHashUpgradesPopup.bind(this);
this.handlePurchaseButtonClick = this.handlePurchaseButtonClick.bind(this);
this.recalculateTotalProduction = this.recalculateTotalProduction.bind(this);
}
componentDidMount() {
@@ -50,6 +52,12 @@ export class HacknetRoot extends React.Component {
createPopup(id, HashUpgradePopup, { popupId: id, rerender: this.createHashUpgradesPopup });
}
handlePurchaseButtonClick() {
if (purchaseHacknet() >= 0) {
this.recalculateTotalProduction();
}
}
recalculateTotalProduction() {
let total = 0;
for (let i = 0; i < Player.hacknetNodes.length; ++i) {
@@ -85,13 +93,6 @@ export class HacknetRoot extends React.Component {
purchaseCost = getCostOfNextHacknetNode();
}
// onClick event handler for purchase button
const purchaseOnClick = () => {
if (purchaseHacknet() >= 0) {
this.recalculateTotalProduction();
}
}
// onClick event handlers for purchase multiplier buttons
const purchaseMultiplierOnClicks = [
this.setPurchaseMultiplier.bind(this, PurchaseMultipliers.x1),
@@ -112,7 +113,7 @@ export class HacknetRoot extends React.Component {
key={hserver.hostname}
node={hserver}
purchaseMultiplier={this.state.purchaseMultiplier}
recalculate={this.recalculateTotalProduction.bind(this)}
recalculate={this.recalculateTotalProduction}
/>
)
} else {
@@ -121,7 +122,7 @@ export class HacknetRoot extends React.Component {
key={node.name}
node={node}
purchaseMultiplier={this.state.purchaseMultiplier}
recalculate={this.recalculateTotalProduction.bind(this)}
recalculate={this.recalculateTotalProduction}
/>
)
}
@@ -132,7 +133,7 @@ export class HacknetRoot extends React.Component {
<h1>Hacknet Nodes</h1>
<GeneralInfo />
<PurchaseButton cost={purchaseCost} multiplier={this.state.purchaseMultiplier} onClick={purchaseOnClick} />
<PurchaseButton cost={purchaseCost} multiplier={this.state.purchaseMultiplier} onClick={this.handlePurchaseButtonClick} />
<br />
<div id={"hacknet-nodes-money-multipliers-div"}>

View File

@@ -3,8 +3,13 @@
* to TypeScript at the moment
*/
export interface IEngine {
hideAllContent: () => void;
loadBladeburnerContent: () => void;
loadFactionContent: () => void;
loadFactionsContent: () => void;
loadGangContent: () => void;
loadInfiltrationContent: () => void;
loadMissionContent: () => void;
loadResleevingContent: () => void;
loadStockMarketContent: () => void;
}

View File

@@ -1,30 +1,12 @@
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
import { CONSTANTS } from "./Constants";
import { Engine } from "./engine";
import { Player } from "./Player";
import { dialogBoxCreate } from "../utils/DialogBox";
import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners";
import { getRandomInt } from "../utils/helpers/getRandomInt";
import { infiltrationBoxCreate } from "../utils/InfiltrationBox";
import { formatNumber } from "../utils/StringHelperFunctions";
/* Infiltration.js
*
* Kill
* Knockout (nonlethal)
* Stealth Knockout (nonlethal)
* Assassinate
*
* Hack Security
* Destroy Security
* Sneak past Security
*
* Pick the locked door
*
* Bribe security
*
* Escape
*/
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
import { CONSTANTS } from "./Constants";
import { Engine } from "./engine";
import { Player } from "./Player";
import { dialogBoxCreate } from "../utils/DialogBox";
import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners";
import { getRandomInt } from "../utils/helpers/getRandomInt";
import { infiltrationBoxCreate } from "../utils/InfiltrationBox";
import { formatNumber } from "../utils/StringHelperFunctions";
let InfiltrationScenarios = {
Guards: "You see an armed security guard patrolling the area.",
@@ -35,21 +17,21 @@ let InfiltrationScenarios = {
}
function InfiltrationInstance(companyName, startLevel, val, maxClearance, diff) {
this.companyName = companyName;
this.clearanceLevel = 0;
this.maxClearanceLevel = maxClearance;
this.securityLevel = startLevel;
this.difficulty = diff; //Affects how much security level increases. Represents a percentage
this.baseValue = val; //Base value of company secrets
this.secretsStolen = []; //Numbers representing value of stolen secrets
this.companyName = companyName;
this.clearanceLevel = 0;
this.maxClearanceLevel = maxClearance;
this.securityLevel = startLevel;
this.difficulty = diff; // Affects how much security level increases. Represents a percentage
this.baseValue = val; // Base value of company secrets
this.secretsStolen = []; // Numbers representing value of stolen secrets
this.hackingExpGained = 0;
this.strExpGained = 0;
this.defExpGained = 0;
this.dexExpGained = 0;
this.agiExpGained = 0;
this.chaExpGained = 0;
this.intExpGained = 0;
this.hackingExpGained = 0;
this.strExpGained = 0;
this.defExpGained = 0;
this.dexExpGained = 0;
this.agiExpGained = 0;
this.chaExpGained = 0;
this.intExpGained = 0;
}
InfiltrationInstance.prototype.expMultiplier = function() {
@@ -124,7 +106,7 @@ InfiltrationInstance.prototype.gainIntelligenceExp = function(amt) {
InfiltrationInstance.prototype.calcGainedIntelligenceExp = function() {
if(!this.intExpGained || isNaN(this.intExpGained)) return 0;
return Math.pow(this.intExpGained*this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
return Math.pow(this.intExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
}
function beginInfiltration(companyName, startLevel, val, maxClearance, diff) {
@@ -177,7 +159,7 @@ function nextInfiltrationLevel(inst) {
bribeButton.style.display = "none";
escapeButton.style.display = "none";
var rand = getRandomInt(0, 5); //This needs to change if more scenarios are added
var rand = getRandomInt(0, 5); // This needs to change if more scenarios are added
var scenario = null;
switch (rand) {
case 1:
@@ -460,7 +442,7 @@ function nextInfiltrationLevel(inst) {
function endInfiltrationLevel(inst) {
//Check if you gained any secrets
// Check if you gained any secrets
if (inst.clearanceLevel % 5 == 0) {
var baseSecretValue = inst.baseValue * inst.clearanceLevel / 2;
var secretValue = baseSecretValue * Player.faction_rep_mult *
@@ -474,12 +456,12 @@ function endInfiltrationLevel(inst) {
"could be given to factions for reputation (<span class='light-yellow'>" + formatNumber(secretValue, 3) + " rep</span>)");
}
//Increase security level based on difficulty
// Increase security level based on difficulty
inst.securityLevel *= (1 + (inst.difficulty / 100));
writeInfiltrationStatusText("You move on to the facility's next clearance level. This " +
"clearance level has " + inst.difficulty + "% higher security");
//If this is max level, force endInfiltration
// If this is max level, force endInfiltration
if (inst.clearanceLevel >= inst.maxClearanceLevel) {
endInfiltration(inst, true);
} else {
@@ -647,8 +629,8 @@ function updateInfiltrationButtons(inst, scenario) {
let intWgt = CONSTANTS.IntelligenceInfiltrationWeight;
//Kill
//Success: 5%, Failure 10%, -Karma
// Kill
// Success: 5%, Failure 10%, -Karma
function attemptInfiltrationKill(inst) {
var chance = getInfiltrationKillChance(inst);
inst.gainStrengthExp(inst.securityLevel / 75) * Player.strength_exp_mult;
@@ -673,8 +655,8 @@ function getInfiltrationKillChance(inst) {
}
//Knockout
//Success: 3%, Failure: 10%
// Knockout
// Success: 3%, Failure: 10%
function attemptInfiltrationKnockout(inst) {
var chance = getInfiltrationKnockoutChance(inst);
inst.gainStrengthExp(inst.securityLevel / 70) * Player.strength_exp_mult;
@@ -698,8 +680,8 @@ function getInfiltrationKnockoutChance(inst) {
Player.agility) / (1.7 * lvl));
}
//Stealth knockout
//Success: 0%, Failure: 10%
// Stealth knockout
// Success: 0%, Failure: 10%
function attemptInfiltrationStealthKnockout(inst) {
var chance = getInfiltrationStealthKnockoutChance(inst);
inst.gainStrengthExp(inst.securityLevel / 75) * Player.strength_exp_mult;
@@ -722,8 +704,8 @@ function getInfiltrationStealthKnockoutChance(inst) {
intWgt * Player.intelligence) / (3 * lvl));
}
//Assassination
//Success: 0%, Failure: 5%, -Karma
// Assassination
// Success: 0%, Failure: 5%, -Karma
function attemptInfiltrationAssassinate(inst) {
var chance = getInfiltrationAssassinateChance(inst);
inst.gainStrengthExp(inst.securityLevel / 75) * Player.strength_exp_mult;
@@ -746,8 +728,8 @@ function getInfiltrationAssassinateChance(inst) {
}
//Destroy security
//Success: 5%, Failure: 10%
// Destroy security
// Success: 5%, Failure: 10%
function attemptInfiltrationDestroySecurity(inst) {
var chance = getInfiltrationDestroySecurityChance(inst);
inst.gainStrengthExp(inst.securityLevel / 75) * Player.strength_exp_mult;
@@ -773,8 +755,8 @@ function getInfiltrationDestroySecurityChance(inst) {
}
//Hack security
//Success: 3%, Failure: 5%
// Hack security
// Success: 3%, Failure: 5%
function attemptInfiltrationHack(inst) {
var chance = getInfiltrationHackChance(inst);
inst.gainHackingExp(inst.securityLevel / 30) * Player.hacking_exp_mult;
@@ -796,8 +778,8 @@ function getInfiltrationHackChance(inst) {
(intWgt * Player.intelligence)) / lvl);
}
//Sneak past security
//Success: 0%, Failure: 8%
// Sneak past security
// Success: 0%, Failure: 8%
function attemptInfiltrationSneak(inst) {
var chance = getInfiltrationSneakChance(inst);
inst.gainAgilityExp(inst.securityLevel / 30) * Player.agility_exp_mult;
@@ -817,8 +799,8 @@ function getInfiltrationSneakChance(inst) {
intWgt * Player.intelligence) / (2 * lvl));
}
//Pick locked door
//Success: 1%, Failure: 3%
// Pick locked door
// Success: 1%, Failure: 3%
function attemptInfiltrationPickLockedDoor(inst) {
var chance = getInfiltrationPickLockedDoorChance(inst);
inst.gainDexterityExp(inst.securityLevel / 25) * Player.dexterity_exp_mult;
@@ -838,8 +820,8 @@ function getInfiltrationPickLockedDoorChance(inst) {
intWgt * Player.intelligence) / lvl);
}
//Bribe
//Success: 0%, Failure: 15%,
// Bribe
// Success: 0%, Failure: 15%,
function attemptInfiltrationBribe(inst) {
var chance = getInfiltrationBribeChance(inst);
inst.gainCharismaExp(inst.securityLevel / 8) * Player.charisma_exp_mult;
@@ -857,8 +839,8 @@ function getInfiltrationBribeChance(inst) {
(Player.charisma) / lvl);
}
//Escape
//Failure: 5%
// Escape
// Failure: 5%
function attemptInfiltrationEscape(inst) {
var chance = getInfiltrationEscapeChance(inst);
inst.gainAgilityExp(inst.securityLevel / 30) * Player.agility_exp_mult;

View File

@@ -1,34 +1,37 @@
import { Engine } from "./engine";
import { Player } from "./Player";
import { Settings } from "./Settings/Settings";
import { initializeMainMenuLinks } from "./ui/MainMenu/Links";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners";
import { createElement } from "../utils/uiHelpers/createElement";
import { createPopup } from "../utils/uiHelpers/createPopup";
import { removeElementById } from "../utils/uiHelpers/removeElementById";
import { Engine } from "./engine";
import { Player } from "./Player";
import { Settings } from "./Settings/Settings";
//Ordered array of keys to Interactive Tutorial Steps
import { initializeMainMenuLinks } from "./ui/MainMenu/Links";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners";
import { createElement } from "../utils/uiHelpers/createElement";
import { createPopup } from "../utils/uiHelpers/createPopup";
import { removeElementById } from "../utils/uiHelpers/removeElementById";
// Ordered array of keys to Interactive Tutorial Steps
const orderedITutorialSteps = [
"Start",
"GoToCharacterPage", //Click on 'Stats' page
"CharacterPage", //Introduction to 'Stats' page
"CharacterGoToTerminalPage", //Go back to Terminal
"TerminalIntro", //Introduction to Terminal
"TerminalHelp", //Using 'help' Terminal command
"TerminalLs", //Using 'ls' Terminal command
"TerminalScan", //Using 'scan' Terminal command
"TerminalScanAnalyze1", //Using 'scan-analyze' Terminal command
"TerminalScanAnalyze2", //Using 'scan-analyze 3' Terminal command
"TerminalConnect", //Connecting to foodnstuff
"TerminalAnalyze", //Analyzing foodnstuff
"TerminalNuke", //NUKE foodnstuff
"TerminalManualHack", //Hack foodnstuff
"TerminalHackingMechanics", //Explanation of hacking mechanics
"TerminalCreateScript", //Create a script using 'nano'
"TerminalTypeScript", //Script Editor page - Type script and then save & close
"TerminalFree", //Using 'Free' Terminal command
"TerminalRunScript", //Running script using 'run' Terminal command
"GoToCharacterPage", // Click on 'Stats' page
"CharacterPage", // Introduction to 'Stats' page
"CharacterGoToTerminalPage", // Go back to Terminal
"TerminalIntro", // Introduction to Terminal
"TerminalHelp", // Using 'help' Terminal command
"TerminalLs", // Using 'ls' Terminal command
"TerminalScan", // Using 'scan' Terminal command
"TerminalScanAnalyze1", // Using 'scan-analyze' Terminal command
"TerminalScanAnalyze2", // Using 'scan-analyze 3' Terminal command
"TerminalConnect", // Connecting to foodnstuff
"TerminalAnalyze", // Analyzing foodnstuff
"TerminalNuke", // NUKE foodnstuff
"TerminalManualHack", // Hack foodnstuff
"TerminalHackingMechanics", // Explanation of hacking mechanics
"TerminalCreateScript", // Create a script using 'nano'
"TerminalTypeScript", // Script Editor page - Type script and then save & close
"TerminalFree", // Using 'Free' Terminal command
"TerminalRunScript", // Running script using 'run' Terminal command
"TerminalGoToActiveScriptsPage",
"ActiveScriptsPage",
"ActiveScriptsToTerminal",
@@ -41,22 +44,22 @@ const orderedITutorialSteps = [
"End"
]
//Create an 'enum' for the Steps
// Create an 'enum' for the Steps
const iTutorialSteps = {};
for (let i = 0; i < orderedITutorialSteps.length; ++i) {
iTutorialSteps[orderedITutorialSteps[i]] = i;
}
var ITutorial = {
currStep: 0, //iTutorialSteps.Start
currStep: 0, // iTutorialSteps.Start
isRunning: false,
//Keeps track of whether each step has been done
// Keeps track of whether each step has been done
stepIsDone: {},
}
function iTutorialStart() {
//Initialize Interactive Tutorial state by settings 'done' for each state to false
// Initialize Interactive Tutorial state by settings 'done' for each state to false
ITutorial.stepIsDone = {};
for (let i = 0; i < orderedITutorialSteps.length; ++i) {
ITutorial.stepIsDone[i] = false;
@@ -64,7 +67,7 @@ function iTutorialStart() {
Engine.loadTerminalContent();
//Don't autosave during this interactive tutorial
// Don't autosave during this interactive tutorial
Engine.Counters.autoSaveCounter = Infinity;
console.log("Interactive Tutorial started");
ITutorial.currStep = 0;
@@ -72,21 +75,21 @@ function iTutorialStart() {
document.getElementById("interactive-tutorial-container").style.display = "block";
//Exit tutorial button
// Exit tutorial button
var exitButton = clearEventListeners("interactive-tutorial-exit");
exitButton.addEventListener("click", function() {
iTutorialEnd();
return false;
});
//Back button
// Back button
var backButton = clearEventListeners("interactive-tutorial-back");
backButton.addEventListener("click", function() {
iTutorialPrevStep();
return false;
});
//Next button
// Next button
var nextButton = clearEventListeners("interactive-tutorial-next");
nextButton.addEventListener("click", function() {
iTutorialNextStep();
@@ -99,7 +102,7 @@ function iTutorialStart() {
function iTutorialEvaluateStep() {
if (!ITutorial.isRunning) {console.log("Interactive Tutorial not running"); return;}
//Disable and clear main menu
// Disable and clear main menu
var terminalMainMenu = clearEventListeners("terminal-menu-link");
var statsMainMenu = clearEventListeners("stats-menu-link");
var activeScriptsMainMenu = clearEventListeners("active-scripts-menu-link");
@@ -113,7 +116,7 @@ function iTutorialEvaluateStep() {
cityMainMenu.removeAttribute("class");
tutorialMainMenu.removeAttribute("class");
//Interactive Tutorial Next button
// Interactive Tutorial Next button
var nextBtn = document.getElementById("interactive-tutorial-next");
switch(ITutorial.currStep) {
@@ -131,7 +134,7 @@ function iTutorialEvaluateStep() {
"the main navigation menu (left-hand side of the screen)");
nextBtn.style.display = "none";
//Flash 'Stats' menu and set its tutorial click handler
// Flash 'Stats' menu and set its tutorial click handler
statsMainMenu.setAttribute("class", "flashing-button");
statsMainMenu.addEventListener("click", function() {
Engine.loadCharacterContent();
@@ -151,7 +154,7 @@ function iTutorialEvaluateStep() {
"main navigation menu.");
nextBtn.style.display = "none";
//Flash 'Terminal' menu and set its tutorial click handler
// Flash 'Terminal' menu and set its tutorial click handler
terminalMainMenu.setAttribute("class", "flashing-button");
terminalMainMenu.addEventListener("click", function() {
Engine.loadTerminalContent();
@@ -169,13 +172,13 @@ function iTutorialEvaluateStep() {
Engine.loadTerminalContent();
iTutorialSetText("Let's try it out. Start by entering the 'help' command into the Terminal " +
"(Don't forget to press Enter after typing the command)");
nextBtn.style.display = "none"; //next step triggered by terminal command
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalLs:
Engine.loadTerminalContent();
iTutorialSetText("The 'help' command displays a list of all available Terminal commands, how to use them, " +
"and a description of what they do. <br><br>Let's try another command. Enter the 'ls' command");
nextBtn.style.display = "none"; //next step triggered by terminal command
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalScan:
Engine.loadTerminalContent();
@@ -184,7 +187,7 @@ function iTutorialEvaluateStep() {
"We'll get to what this does later. <br><br>Using your home computer's terminal, you can connect " +
"to other machines throughout the world. Let's do that now by first entering " +
"the 'scan' command.");
nextBtn.style.display = "none"; //next step triggered by terminal command
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalScanAnalyze1:
Engine.loadTerminalContent();
@@ -194,7 +197,7 @@ function iTutorialEvaluateStep() {
"That's great and all, but there's so many servers. Which one should you go to? " +
"The 'scan-analyze' command gives some more detailed information about servers on the " +
"network. Try it now");
nextBtn.style.display = "none"; //next step triggered by terminal command
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalScanAnalyze2:
Engine.loadTerminalContent();
@@ -202,7 +205,7 @@ function iTutorialEvaluateStep() {
"information about each server that you can connect to (servers that are a distance of " +
"one node away). <br><br> It is also possible to run 'scan-analyze' with " +
"a higher depth. Let's try a depth of two with the following command: 'scan-analyze 2'.")
nextBtn.style.display = "none"; //next step triggered by terminal command
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalConnect:
Engine.loadTerminalContent();
@@ -212,7 +215,7 @@ function iTutorialEvaluateStep() {
"the ip or the hostname, but dont use both.<br><br>" +
"From the results of the 'scan-analyze' command, we can see that the 'foodnstuff' server is " +
"only one node away. Let's connect so it now using: 'connect foodnstuff'");
nextBtn.style.display = "none"; //next step triggered by terminal command
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalAnalyze:
Engine.loadTerminalContent();
@@ -221,7 +224,7 @@ function iTutorialEvaluateStep() {
"on servers and computers. Using your hacking abilities, you can hack servers " +
"to steal money and gain experience. <br><br> " +
"Before you try to hack a server, you should run diagnostics using the 'analyze' command");
nextBtn.style.display = "none"; //next step triggered by terminal command
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalNuke:
Engine.loadTerminalContent();
@@ -233,13 +236,13 @@ function iTutorialEvaluateStep() {
"open ports.<br><br> The 'analyze' results shows that there do not need to be any open ports " +
"on this machine for the NUKE virus to work, so go ahead and run the virus using the " +
"'run NUKE.exe' command.");
nextBtn.style.display = "none"; //next step triggered by terminal command
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalManualHack:
Engine.loadTerminalContent();
iTutorialSetText("You now have root access! You can hack the server using the 'hack' command. " +
"Try doing that now.");
nextBtn.style.display = "none"; //next step triggered by terminal command
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalHackingMechanics:
Engine.loadTerminalContent();
@@ -262,7 +265,7 @@ function iTutorialEvaluateStep() {
"command. Scripts must end with the '.script' extension. Let's make a script now by " +
"entering 'nano foodnstuff.script' after the hack command finishes running (Sidenote: Pressing ctrl + c" +
" will end a command like hack early)");
nextBtn.style.display = "none"; //next step triggered by terminal command
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalTypeScript:
Engine.loadScriptEditorContent("foodnstuff.script", "");
@@ -278,7 +281,7 @@ function iTutorialEvaluateStep() {
"For anyone with basic programming experience, this code should be straightforward. " +
"This script will continuously hack the 'foodnstuff' server.<br><br>" +
"To save and close the script editor, press the button in the bottom left, or press ctrl + b.");
nextBtn.style.display = "none"; //next step triggered in saveAndCloseScriptEditor() (Script.js)
nextBtn.style.display = "none"; // next step triggered in saveAndCloseScriptEditor() (Script.js)
break;
case iTutorialSteps.TerminalFree:
Engine.loadTerminalContent();
@@ -286,13 +289,13 @@ function iTutorialEvaluateStep() {
"run on any machine which you have root access to. Different servers have different " +
"amounts of RAM. You can also purchase more RAM for your home server.<br><br>To check how much " +
"RAM is available on this machine, enter the 'free' command.");
nextBtn.style.display = "none"; //next step triggered by terminal commmand
nextBtn.style.display = "none"; // next step triggered by terminal commmand
break;
case iTutorialSteps.TerminalRunScript:
Engine.loadTerminalContent();
iTutorialSetText("We have 16GB of free RAM on this machine, which is enough to run our " +
"script. Let's run our script using 'run foodnstuff.script'.");
nextBtn.style.display = "none"; //next step triggered by terminal commmand
nextBtn.style.display = "none"; // next step triggered by terminal commmand
break;
case iTutorialSteps.TerminalGoToActiveScriptsPage:
Engine.loadTerminalContent();
@@ -306,7 +309,7 @@ function iTutorialEvaluateStep() {
"'Active Scripts' link in the main navigation menu.");
nextBtn.style.display = "none";
//Flash 'Active Scripts' menu and set its tutorial click handler
// Flash 'Active Scripts' menu and set its tutorial click handler
activeScriptsMainMenu.setAttribute("class", "flashing-button");
activeScriptsMainMenu.addEventListener("click", function() {
Engine.loadActiveScriptsContent();
@@ -318,11 +321,11 @@ function iTutorialEvaluateStep() {
Engine.loadActiveScriptsContent();
iTutorialSetText("This page displays stats/information about all of your scripts that are " +
"running across every existing server. You can use this to gauge how well " +
"your scripts are doing. Let's go back to the Terminal now using the 'Terminal'" +
"your scripts are doing. Let's go back to the Terminal now using the 'Terminal' " +
"link.");
nextBtn.style.display = "none";
//Flash 'Terminal' button and set its tutorial click handler
// Flash 'Terminal' button and set its tutorial click handler
terminalMainMenu.setAttribute("class", "flashing-button");
terminalMainMenu.addEventListener("click", function() {
Engine.loadTerminalContent();
@@ -335,7 +338,7 @@ function iTutorialEvaluateStep() {
iTutorialSetText("One last thing about scripts, each active script contains logs that detail " +
"what it's doing. We can check these logs using the 'tail' command. Do that " +
"now for the script we just ran by typing 'tail foodnstuff.script'");
nextBtn.style.display = "none"; //next step triggered by terminal command
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalTailScript:
Engine.loadTerminalContent();
@@ -356,7 +359,7 @@ function iTutorialEvaluateStep() {
"the 'Hacknet Nodes' page through the main navigation menu now.");
nextBtn.style.display = "none";
//Flash 'Hacknet' menu and set its tutorial click handler
// Flash 'Hacknet' menu and set its tutorial click handler
hacknetMainMenu.setAttribute("class", "flashing-button");
hacknetMainMenu.addEventListener("click", function() {
Engine.loadHacknetNodesContent();
@@ -368,7 +371,7 @@ function iTutorialEvaluateStep() {
Engine.loadHacknetNodesContent();
iTutorialSetText("From this page you can purchase new Hacknet Nodes and upgrade your " +
"existing ones. Let's purchase a new one now.");
nextBtn.style.display = "none"; //Next step triggered by purchaseHacknet() (HacknetNode.js)
nextBtn.style.display = "none"; // Next step triggered by purchaseHacknet() (HacknetNode.js)
break;
case iTutorialSteps.HacknetNodesGoToWorldPage:
Engine.loadHacknetNodesContent();
@@ -379,7 +382,7 @@ function iTutorialEvaluateStep() {
"Let's go to the 'City' page through the main navigation menu.");
nextBtn.style.display = "none";
//Flash 'City' menu and set its tutorial click handler
// Flash 'City' menu and set its tutorial click handler
cityMainMenu.setAttribute("class", "flashing-button");
cityMainMenu.addEventListener("click", function() {
Engine.loadLocationContent();
@@ -396,7 +399,7 @@ function iTutorialEvaluateStep() {
"Lastly, click on the 'Tutorial' link in the main navigation menu.");
nextBtn.style.display = "none";
//Flash 'Tutorial' menu and set its tutorial click handler
// Flash 'Tutorial' menu and set its tutorial click handler
tutorialMainMenu.setAttribute("class", "flashing-button");
tutorialMainMenu.addEventListener("click", function() {
Engine.loadTutorialContent();
@@ -425,9 +428,9 @@ function iTutorialEvaluateStep() {
}
}
//Go to the next step and evaluate it
// Go to the next step and evaluate it
function iTutorialNextStep() {
//Special behavior for certain steps
// Special behavior for certain steps
if (ITutorial.currStep === iTutorialSteps.GoToCharacterPage) {
document.getElementById("stats-menu-link").removeAttribute("class");
}
@@ -457,7 +460,7 @@ function iTutorialNextStep() {
iTutorialEvaluateStep();
}
//Go to previous step and evaluate
// Go to previous step and evaluate
function iTutorialPrevStep() {
if (ITutorial.currStep > iTutorialSteps.Start) {
ITutorial.currStep -= 1;
@@ -466,7 +469,7 @@ function iTutorialPrevStep() {
}
function iTutorialEnd() {
//Re-enable auto save
// Re-enable auto save
if (Settings.AutosaveInterval === 0) {
Engine.Counters.autoSaveCounter = Infinity;
} else {
@@ -491,7 +494,7 @@ function iTutorialEnd() {
ITutorial.isRunning = false;
document.getElementById("interactive-tutorial-container").style.display = "none";
//Create a popup with final introductory stuff
// Create a popup with final introductory stuff
var popupId = "interactive-tutorial-ending-popup";
var txt = createElement("p", {
innerHTML:

View File

@@ -1,8 +1,9 @@
import {dialogBoxCreate} from "../utils/DialogBox";
/* Literature.js
* Lore / world building literature that can be found on servers
/**
* Lore / world building literature files that can be found on servers.
* These files can be read by the player
*/
import { dialogBoxCreate } from "../utils/DialogBox";
function Literature(title, filename, txt) {
this.title = title;
this.fn = filename;
@@ -10,10 +11,9 @@ function Literature(title, filename, txt) {
}
function showLiterature(fn) {
var litObj = Literatures[fn];
if (litObj == null) {return;}
var txt = "<i>" + litObj.title + "</i><br><br>" +
litObj.txt;
const litObj = Literatures[fn];
if (litObj == null) { return; }
const txt = `<i>${litObj.title}</i><br><br>${litObj.txt}`;
dialogBoxCreate(txt);
}
@@ -430,7 +430,10 @@ function initLiterature() {
fn = "the-secret-war.lit";
txt = ""
Literatures[fn] = new Literature(title, fn, txt);
}
export {Literatures, initLiterature, showLiterature};
export {
Literatures,
initLiterature,
showLiterature
};

View File

@@ -2,46 +2,53 @@
* Location and traveling-related helper functions.
* Mostly used for UI
*/
import { CONSTANTS } from "../Constants";
import { CONSTANTS } from "../Constants";
import { CityName } from "./data/CityNames";
import { CityName } from "./data/CityNames";
import { IPlayer } from "../PersonObjects/IPlayer";
import { AllServers,
AddToAllServers } from "../Server/AllServers";
import { Server } from "../Server/Server";
import { getPurchaseServerCost,
purchaseRamForHomeComputer,
purchaseServer } from "../Server/ServerPurchases";
import { SpecialServerIps } from "../Server/SpecialServerIps";
import { Settings } from "../Settings/Settings";
import { IPlayer } from "../PersonObjects/IPlayer";
import {
AddToAllServers,
createUniqueRandomIp,
} from "../Server/AllServers";
import { safetlyCreateUniqueServer } from "../Server/ServerHelpers";
import {
getPurchaseServerCost,
purchaseRamForHomeComputer,
purchaseServer
} from "../Server/ServerPurchases";
import { SpecialServerIps } from "../Server/SpecialServerIps";
import { Settings } from "../Settings/Settings";
import { numeralWrapper } from "../ui/numeralFormat";
import { numeralWrapper } from "../ui/numeralFormat";
import { dialogBoxCreate } from "../../utils/DialogBox";
import { createRandomIp } from "../../utils/IPAddress";
import { yesNoBoxGetYesButton,
yesNoBoxGetNoButton,
yesNoBoxClose,
yesNoBoxCreate,
yesNoTxtInpBoxGetYesButton,
yesNoTxtInpBoxGetNoButton,
yesNoTxtInpBoxClose,
yesNoTxtInpBoxCreate } from "../../utils/YesNoBox";
import { dialogBoxCreate } from "../../utils/DialogBox";
import {
yesNoBoxGetYesButton,
yesNoBoxGetNoButton,
yesNoBoxClose,
yesNoBoxCreate,
yesNoTxtInpBoxGetYesButton,
yesNoTxtInpBoxGetNoButton,
yesNoTxtInpBoxClose,
yesNoTxtInpBoxCreate
} from "../../utils/YesNoBox";
import { createElement } from "../../utils/uiHelpers/createElement";
import { createPopup } from "../../utils/uiHelpers/createPopup";
import { createPopupCloseButton } from "../../utils/uiHelpers/createPopupCloseButton";
import { removeElementById } from "../../utils/uiHelpers/removeElementById";
import { createElement } from "../../utils/uiHelpers/createElement";
import { createPopup } from "../../utils/uiHelpers/createPopup";
import { createPopupCloseButton } from "../../utils/uiHelpers/createPopupCloseButton";
import { removeElementById } from "../../utils/uiHelpers/removeElementById";
/**
* Create a pop-up box that lets the player confirm traveling to a different city
* 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) {
@@ -75,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");
@@ -106,9 +113,10 @@ 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; }
if (!p.canAccessCorporation() || p.hasCorporation()) { return; }
const popupId = "create-corporation-popup";
const txt = createElement("p", {
@@ -167,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;
}
@@ -182,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();
@@ -210,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();
});
@@ -221,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 " +
"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));
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)
);
}
/**
* 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();
@@ -250,25 +266,31 @@ export function createUpgradeHomeRamPopup(p: IPlayer) {
yesNoBoxClose();
});
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'));
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')
);
}
/**
* Attempt to purchase a TOR router
* @param p - Player object
* @param {IPlayer} p - Player object
*/
export function purchaseTorRouter(p: IPlayer) {
if (p.hasTorRouter()) {
dialogBoxCreate(`You already have a TOR Router`);
return;
}
if (!p.canAfford(CONSTANTS.TorRouterCost)) {
dialogBoxCreate("You cannot afford to purchase the Tor router");
return;
}
p.loseMoney(CONSTANTS.TorRouterCost);
const darkweb = new Server({
ip: createRandomIp(), hostname:"darkweb", organizationName:"",
const darkweb = safetlyCreateUniqueServer({
ip: createUniqueRandomIp(), hostname:"darkweb", organizationName:"",
isConnectedTo:false, adminRights:false, purchasedByPlayer:false, maxRam:1
});
AddToAllServers(darkweb);
@@ -276,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>" +
"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.");
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."
);
}

View File

@@ -362,7 +362,7 @@ export class CompanyLocation extends React.Component<IProps, IState> {
<StdButton
onClick={this.startInfiltration}
style={this.btnStyle}
text={"Infiltration Company"}
text={"Infiltrate Company"}
/>
}
</div>

View File

@@ -54,6 +54,9 @@ export class TechVendorLocation extends React.Component<IProps, any> {
purchaseTorRouter() {
purchaseTorRouter(this.props.p);
this.setState({
torPurchased: this.props.p.hasTorRouter(),
});
}
render() {

View File

@@ -1,15 +1,15 @@
import { Message } from "./Message";
import { Augmentatation } from "../Augmentation/Augmentation";
import { Augmentations } from "../Augmentation/Augmentations";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { Programs } from "../Programs/Programs";
import { inMission } from "../Missions";
import { Player } from "../Player";
import { redPillFlag } from "../RedPill";
import { GetServerByHostname } from "../Server/ServerHelpers";
import { Settings } from "../Settings/Settings";
import { dialogBoxCreate,
dialogBoxOpened} from "../../utils/DialogBox";
import { Message } from "./Message";
import { Augmentatation } from "../Augmentation/Augmentation";
import { Augmentations } from "../Augmentation/Augmentations";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { Programs } from "../Programs/Programs";
import { inMission } from "../Missions";
import { Player } from "../Player";
import { redPillFlag } from "../RedPill";
import { GetServerByHostname } from "../Server/ServerHelpers";
import { Settings } from "../Settings/Settings";
import { dialogBoxCreate, dialogBoxOpened} from "../../utils/DialogBox";
import { Reviver } from "../../utils/JSONReviver";
//Sends message to player, including a pop up
function sendMessage(msg, forced=false) {
@@ -31,7 +31,7 @@ function showMessage(msg) {
function addMessageToServer(msg, serverHostname) {
var server = GetServerByHostname(serverHostname);
if (server == null) {
console.log("WARNING: Did not locate " + serverHostname);
console.warn(`Could not find server ${serverHostname}`);
return;
}
for (var i = 0; i < server.messages.length; ++i) {

View File

@@ -1,16 +1,21 @@
import { CONSTANTS } from "./Constants";
import { Engine } from "./engine";
import { displayFactionContent } from "./Faction/FactionHelpers";
import { Player } from "./Player";
import { dialogBoxCreate } from "../utils/DialogBox";
import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners";
import { addOffset } from "../utils/helpers/addOffset";
import { formatNumber } from "../utils/StringHelperFunctions";
import { getRandomInt } from "../utils/helpers/getRandomInt";
import { isString } from "../utils/helpers/isString";
import jsplumb from 'jsplumb'
import { CONSTANTS } from "./Constants";
import { Engine } from "./engine";
import { displayFactionContent } from "./Faction/FactionHelpers";
import { Player } from "./Player";
let inMission = false; //Flag to denote whether a mission is running
import { dialogBoxCreate } from "../utils/DialogBox";
import { formatNumber } from "../utils/StringHelperFunctions";
import { addOffset } from "../utils/helpers/addOffset";
import { getRandomInt } from "../utils/helpers/getRandomInt";
import { isString } from "../utils/helpers/isString";
import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners";
import jsplumb from "jsplumb";
let inMission = false; // Flag to denote whether a mission is running
let currMission = null;
function setInMission(bool, mission) {
inMission = bool;
@@ -21,26 +26,26 @@ function setInMission(bool, mission) {
}
}
//Keyboard shortcuts
// Keyboard shortcuts
$(document).keydown(function(e) {
if (inMission && currMission && currMission.selectedNode.length != 0) {
switch (e.keyCode) {
case 65: //a for Attack
case 65: // a for Attack
currMission.actionButtons[0].click();
break;
case 83: //s for Scan
case 83: // s for Scan
currMission.actionButtons[1].click();
break;
case 87: //w for Weaken
case 87: // w for Weaken
currMission.actionButtons[2].click();
break;
case 70: //f for Fortify
case 70: // f for Fortify
currMission.actionButtons[3].click();
break;
case 82: //r for Overflow
case 82: // r for Overflow
currMission.actionButtons[4].click();
break;
case 68: //d for Detach connection
case 68: // d for Detach connection
currMission.actionButtons[5].click();
break;
default:
@@ -50,20 +55,20 @@ $(document).keydown(function(e) {
});
let NodeTypes = {
Core: "CPU Core Node", //All actions available
Firewall: "Firewall Node", //No actions available
Database: "Database Node", //No actions available
Spam: "Spam Node", //No actions Available
Transfer: "Transfer Node", //Can Weaken, Scan, Fortify and Overflow
Shield: "Shield Node" //Can Fortify
Core: "CPU Core Node", // All actions available
Firewall: "Firewall Node", // No actions available
Database: "Database Node", // No actions available
Spam: "Spam Node", // No actions Available
Transfer: "Transfer Node", // Can Weaken, Scan, Fortify and Overflow
Shield: "Shield Node" // Can Fortify
}
let NodeActions = {
Attack: "Attacking", //Damaged based on attack stat + hacking level + opp def
Scan: "Scanning", //-Def for target, affected by attack and hacking level
Weaken: "Weakening", //-Attack for target, affected by attack and hacking level
Fortify: "Fortifying", //+Defense for Node, affected by hacking level
Overflow: "Overflowing", //+Attack but -Defense for Node, affected by hacking level
Attack: "Attacking", // Damaged based on attack stat + hacking level + opp def
Scan: "Scanning", // -Def for target, affected by attack and hacking level
Weaken: "Weakening", // -Attack for target, affected by attack and hacking level
Fortify: "Fortifying", // +Defense for Node, affected by hacking level
Overflow: "Overflowing", // +Attack but -Defense for Node, affected by hacking level
}
function Node(type, stats) {
@@ -74,14 +79,15 @@ function Node(type, stats) {
this.maxhp = this.hp;
this.plyrCtrl = false;
this.enmyCtrl = false;
this.pos = [0, 0]; //x, y
this.el = null; //Holds the Node's DOM element
this.pos = [0, 0]; // x, y
this.el = null; // Holds the Node's DOM element
this.action = null;
this.targetedCount = 0; //Count of how many connections this node is the target of
this.targetedCount = 0; // Count of how many connections this node is the target of
//Holds the JsPlumb Connection object for this Node,
//where this Node is the Source (since each Node
//can only have 1 outgoing Connection)
/**
* Holds the JsPlumb Connection object for this Node, where this Node is the Source (since each Node
* can only have 1 outgoing Connection)
*/
this.conn = null;
}
@@ -107,12 +113,12 @@ Node.prototype.setControlledByEnemy = function() {
}
}
//Sets this node to be the active node
// Sets this node to be the active node
Node.prototype.select = function(actionButtons) {
if (this.enmyCtrl) {return;}
this.el.classList.add("hack-mission-player-node-active");
//Make all buttons inactive
// Make all buttons inactive
for (var i = 0; i < actionButtons.length; ++i) {
actionButtons[i].classList.remove("a-link-button");
actionButtons[i].classList.add("a-link-button-inactive");
@@ -120,7 +126,7 @@ Node.prototype.select = function(actionButtons) {
switch(this.type) {
case NodeTypes.Core:
//All buttons active
// All buttons active
for (var i = 0; i < actionButtons.length; ++i) {
actionButtons[i].classList.remove("a-link-button-inactive");
actionButtons[i].classList.add("a-link-button");
@@ -165,31 +171,33 @@ Node.prototype.untarget = function() {
--this.targetedCount;
}
//Hacking mission instance
//Takes in the reputation of the Faction for which the mission is
//being conducted
/**
* Hacking mission instance
* @param rep {number} How much reputation the player has for the faction
* @param fac {Faction} Faction for which this mission is being conducted
*/
function HackingMission(rep, fac) {
this.faction = fac;
this.started = false;
this.time = 180000; //5 minutes to start, milliseconds
this.time = 180000; // 5 minutes to start, milliseconds
this.playerCores = [];
this.playerNodes = []; //Non-core nodes
this.playerNodes = []; // Non-core nodes
this.playerAtk = 0;
this.playerDef = 0;
this.enemyCores = [];
this.enemyDatabases = [];
this.enemyNodes = []; //Non-core nodes
this.enemyNodes = []; // Non-core nodes
this.enemyAtk = 0;
this.enemyDef = 0;
this.miscNodes = [];
this.selectedNode = []; //Which of the player's nodes are currently selected
this.selectedNode = []; // Which of the player's nodes are currently selected
this.actionButtons = []; //DOM buttons for actions
this.actionButtons = []; // DOM buttons for actions
this.availablePositions = [];
for (var r = 0; r < 8; ++r) {
@@ -211,10 +219,10 @@ function HackingMission(rep, fac) {
}
HackingMission.prototype.init = function() {
//Create Header DOM
// Create Header DOM
this.createPageDom();
//Create player starting nodes
// Create player starting nodes
var home = Player.getHomeComputer()
for (var i = 0; i < home.cpuCores; ++i) {
var stats = {
@@ -228,7 +236,7 @@ HackingMission.prototype.init = function() {
this.removeAvailablePosition(i, 0);
}
//Randomly generate enemy nodes (CPU and Firewall) based on difficulty
// Randomly generate enemy nodes (CPU and Firewall) based on difficulty
var numNodes = Math.min(8, Math.max(1, Math.round(this.difficulty / 4)));
var numFirewalls = Math.min(20,
getRandomInt(Math.round(this.difficulty/3), Math.round(this.difficulty/3) + 1));
@@ -304,10 +312,10 @@ HackingMission.prototype.createPageDom = function() {
wikiGuideBtn.style.display = "inline-block";
wikiGuideBtn.classList.add("hack-mission-header-element");
wikiGuideBtn.target = "_blank";
//TODO Add link to wiki page wikiGuideBtn.href =
// TODO Add link to wiki page wikiGuideBtn.href =
//Start button will get replaced with forfeit when game is started
// Start button will get replaced with forfeit when game is started
var startBtn = document.createElement("a");
startBtn.innerHTML = "Start";
startBtn.setAttribute("id", "hack-mission-start-btn");
@@ -334,15 +342,15 @@ HackingMission.prototype.createPageDom = function() {
timer.style.display = "inline-block";
timer.style.margin = "6px";
//Create Action Buttons (Attack/Scan/Weaken/ etc...)
// Create Action Buttons (Attack/Scan/Weaken/ etc...)
var actionsContainer = document.createElement("span");
actionsContainer.style.display = "block";
actionsContainer.classList.add("hack-mission-action-buttons-container");
for (var i = 0; i < 6; ++i) {
this.actionButtons.push(document.createElement("a"));
this.actionButtons[i].style.display = "inline-block";
this.actionButtons[i].classList.add("a-link-button-inactive"); //Disabled at start
this.actionButtons[i].classList.add("tooltip"); //Disabled at start
this.actionButtons[i].classList.add("a-link-button-inactive"); // Disabled at start
this.actionButtons[i].classList.add("tooltip"); // Disabled at start
this.actionButtons[i].classList.add("hack-mission-header-element");
actionsContainer.appendChild(this.actionButtons[i]);
}
@@ -383,7 +391,7 @@ HackingMission.prototype.createPageDom = function() {
"also be done by simply clicking the white connection line.";
this.actionButtons[5].appendChild(dropconnTooltip);
//Player/enemy defense displays will be in action container
// Player/enemy defense displays will be in action container
var playerStats = document.createElement("p");
var enemyStats = document.createElement("p");
playerStats.style.display = "inline-block";
@@ -397,7 +405,7 @@ HackingMission.prototype.createPageDom = function() {
actionsContainer.appendChild(playerStats);
actionsContainer.appendChild(enemyStats);
//Set Action Button event listeners
// Set Action Button event listeners
this.actionButtons[0].addEventListener("click", ()=>{
if (!(this.selectedNode.length > 0)) {
console.log("ERR: Pressing Action button without selected node");
@@ -405,7 +413,7 @@ HackingMission.prototype.createPageDom = function() {
}
if (this.selectedNode[0].type !== NodeTypes.Core) {return;}
this.setActionButtonsActive(this.selectedNode[0].type);
this.setActionButton(NodeActions.Attack, false); //Set attack button inactive
this.setActionButton(NodeActions.Attack, false); // Set attack button inactive
this.selectedNode.forEach(function(node){
node.action = NodeActions.Attack;
});
@@ -416,10 +424,10 @@ HackingMission.prototype.createPageDom = function() {
console.log("ERR: Pressing Action button without selected node");
return;
}
var nodeType = this.selectedNode[0].type; //In a multiselect, every Node will have the same type
var nodeType = this.selectedNode[0].type; // In a multiselect, every Node will have the same type
if (nodeType !== NodeTypes.Core && nodeType !== NodeTypes.Transfer) {return;}
this.setActionButtonsActive(nodeType);
this.setActionButton(NodeActions.Scan, false); //Set scan button inactive
this.setActionButton(NodeActions.Scan, false); // Set scan button inactive
this.selectedNode.forEach(function(node){
node.action = NodeActions.Scan;
});
@@ -430,10 +438,10 @@ HackingMission.prototype.createPageDom = function() {
console.log("ERR: Pressing Action button without selected node");
return;
}
var nodeType = this.selectedNode[0].type; //In a multiselect, every Node will have the same type
var nodeType = this.selectedNode[0].type; // In a multiselect, every Node will have the same type
if (nodeType !== NodeTypes.Core && nodeType !== NodeTypes.Transfer) {return;}
this.setActionButtonsActive(nodeType);
this.setActionButton(NodeActions.Weaken, false); //Set Weaken button inactive
this.setActionButton(NodeActions.Weaken, false); // Set Weaken button inactive
this.selectedNode.forEach(function(node){
node.action = NodeActions.Weaken;
});
@@ -445,7 +453,7 @@ HackingMission.prototype.createPageDom = function() {
return;
}
this.setActionButtonsActive(this.selectedNode[0].type);
this.setActionButton(NodeActions.Fortify, false); //Set Fortify button inactive
this.setActionButton(NodeActions.Fortify, false); // Set Fortify button inactive
this.selectedNode.forEach(function(node){
node.action = NodeActions.Fortify;
});
@@ -459,7 +467,7 @@ HackingMission.prototype.createPageDom = function() {
var nodeType = this.selectedNode[0].type;
if (nodeType !== NodeTypes.Core && nodeType !== NodeTypes.Transfer) {return;}
this.setActionButtonsActive(nodeType);
this.setActionButton(NodeActions.Overflow, false); //Set Overflow button inactive
this.setActionButton(NodeActions.Overflow, false); // Set Overflow button inactive
this.selectedNode.forEach(function(node){
node.action = NodeActions.Overflow;
});
@@ -477,11 +485,7 @@ HackingMission.prototype.createPageDom = function() {
}
node.action = NodeActions.Fortify;
});
// if (this.selectedNode.conn) {
// var endpoints = this.selectedNode.conn.endpoints;
// endpoints[0].detachFrom(endpoints[1]);
// }
})
});
var timeDisplay = document.createElement("p");
@@ -508,8 +512,10 @@ HackingMission.prototype.setActionButtonsActive = function(nodeType=null) {
this.actionButtons[i].classList.remove("a-link-button-inactive");
}
//For Transfer, FireWall and Shield Nodes, certain buttons should always be disabled
//0 = Attack, 1 = Scan, 2 = Weaken, 3 = Fortify, 4 = overflow, 5 = Drop conn
/**
* For Transfer, FireWall and Shield Nodes, certain buttons should always be disabled
* 0 = Attack, 1 = Scan, 2 = Weaken, 3 = Fortify, 4 = overflow, 5 = Drop conn
*/
if (nodeType) {
switch (nodeType) {
case NodeTypes.Firewall:
@@ -535,7 +541,7 @@ HackingMission.prototype.setActionButtonsActive = function(nodeType=null) {
}
}
//True for active, false for inactive
// True for active, false for inactive
HackingMission.prototype.setActionButton = function(i, active=true) {
if (isString(i)) {
switch (i) {
@@ -652,7 +658,7 @@ HackingMission.prototype.setNodePosition = function(nodeObj, x, y) {
HackingMission.prototype.setNodeRandomPosition = function(nodeObj, xlimit=0) {
var i = getRandomInt(0, this.availablePositions.length - 1);
if (this.availablePositions[i][1] < xlimit) {
//Recurse if not within limit
// Recurse if not within limit
return this.setNodeRandomPosition(nodeObj, xlimit);
}
var pos = this.availablePositions.splice(i, 1);
@@ -661,15 +667,15 @@ HackingMission.prototype.setNodeRandomPosition = function(nodeObj, xlimit=0) {
}
HackingMission.prototype.createMap = function() {
//Use a grid
// Use a grid
var map = document.createElement("div");
map.classList.add("hack-mission-grid");
map.setAttribute("id", "hacking-mission-map");
document.getElementById("mission-container").appendChild(map);
//Create random Nodes for every space in the map that
//hasn't been filled yet. The stats of each Node will be based on
//the player/enemy attack
// Create random Nodes for every space in the map that
// hasn't been filled yet. The stats of each Node will be based on
// the player/enemy attack
var averageAttack = (this.playerAtk + this.enemyAtk) / 2;
for (var x = 0; x < 8; ++x) {
for (var y = 0; y < 8; ++y) {
@@ -677,7 +683,7 @@ HackingMission.prototype.createMap = function() {
var node, type = getRandomInt(0, 2);
var randMult = addOffset(0.85 + (this.difficulty / 2), 15);
switch (type) {
case 0: //Spam
case 0: // Spam
var stats = {
atk: 0,
def: averageAttack * 1.1 + getRandomInt(15, 45),
@@ -685,7 +691,7 @@ HackingMission.prototype.createMap = function() {
}
node = new Node(NodeTypes.Spam, stats);
break;
case 1: //Transfer
case 1: // Transfer
var stats = {
atk: 0,
def: averageAttack * 1.1 + getRandomInt(15, 45),
@@ -693,7 +699,7 @@ HackingMission.prototype.createMap = function() {
}
node = new Node(NodeTypes.Transfer, stats);
break;
case 2: //Shield
case 2: // Shield
default:
var stats = {
atk: 0,
@@ -710,14 +716,14 @@ HackingMission.prototype.createMap = function() {
}
}
//Create DOM elements in order
// Create DOM elements in order
for (var r = 0; r < 8; ++r) {
for (var c = 0; c < 8; ++c) {
this.createNodeDomElement(this.map[r][c]);
}
}
//Configure all Player CPUS
// Configure all Player CPUS
for (var i = 0; i < this.playerCores.length; ++i) {
console.log("Configuring Player Node: " + this.playerCores[i].el.id);
this.configurePlayerNodeElement(this.playerCores[i].el);
@@ -728,12 +734,12 @@ HackingMission.prototype.createNodeDomElement = function(nodeObj) {
var nodeDiv = document.createElement("a"), txtEl = document.createElement('p');
nodeObj.el = nodeDiv;
//Set the node element's id based on its coordinates
// Set the node element's id based on its coordinates
var id = "hacking-mission-node-" + nodeObj.pos[0] + "-" + nodeObj.pos[1];
nodeDiv.setAttribute("id", id);
txtEl.setAttribute("id", id + "-txt");
//Set node classes for owner
// Set node classes for owner
nodeDiv.classList.add("hack-mission-node");
if (nodeObj.plyrCtrl) {
nodeDiv.classList.add("hack-mission-player-node");
@@ -741,7 +747,7 @@ HackingMission.prototype.createNodeDomElement = function(nodeObj) {
nodeDiv.classList.add("hack-mission-enemy-node");
}
//Set node classes based on type
// Set node classes based on type
var txt;
switch (nodeObj.type) {
case NodeTypes.Core:
@@ -794,7 +800,7 @@ HackingMission.prototype.updateNodeDomElement = function(nodeObj) {
var id = "hacking-mission-node-" + nodeObj.pos[0] + "-" + nodeObj.pos[1];
var nodeDiv = document.getElementById(id), txtEl = document.getElementById(id + "-txt");
//Set node classes based on type
// Set node classes based on type
var txt;
switch (nodeObj.type) {
case NodeTypes.Core:
@@ -832,9 +838,11 @@ HackingMission.prototype.updateNodeDomElement = function(nodeObj) {
txtEl.innerHTML = txt;
}
//Gets a Node DOM element's corresponding Node object using its
//element id. Function accepts either the DOM element object or the ID as
//an argument
/**
* Gets a Node DOM element's corresponding Node object using its
* element id. Function accepts either the DOM element object or the ID as
* an argument
*/
HackingMission.prototype.getNodeFromElement = function(el) {
var id;
if (isString(el)) {
@@ -897,14 +905,16 @@ function clearAllSelectedNodes(hackMissionInst) {
}
}
//Configures a DOM element representing a player-owned node to
//be selectable and actionable
//Note: Does NOT change its css class. This is handled by Node.setControlledBy...
/**
* Configures a DOM element representing a player-owned node to
* be selectable and actionable.
* Note: Does NOT change its css class. This is handled by Node.setControlledBy...
*/
HackingMission.prototype.configurePlayerNodeElement = function(el) {
var nodeObj = this.getNodeFromElement(el);
if (nodeObj == null) {console.log("Error getting Node object");}
//Add event listener
// Add event listener
var self = this;
function selectNodeWrapper() {
selectNode(self, el);
@@ -922,10 +932,12 @@ HackingMission.prototype.configurePlayerNodeElement = function(el) {
}
}
//Configures a DOM element representing an enemy-node by removing
//any event listeners
/**
* Configures a DOM element representing an enemy-node by removing
* any event listeners
*/
HackingMission.prototype.configureEnemyNodeElement = function(el) {
//Deselect node if it was the selected node
// Deselect node if it was the selected node
var nodeObj = this.getNodeFromElement(el);
for (var i = 0; i < this.selectedNode.length; ++i) {
if (this.selectedNode[i] == nodeObj) {
@@ -936,8 +948,10 @@ HackingMission.prototype.configureEnemyNodeElement = function(el) {
}
}
//Returns bool indicating whether a node is reachable by player
//by checking if any of the adjacent nodes are owned by the player
/**
* Returns bool indicating whether a node is reachable by player
* by checking if any of the adjacent nodes are owned by the player
*/
HackingMission.prototype.nodeReachable = function(node) {
var x = node.pos[0], y = node.pos[1];
if (x > 0 && this.map[x-1][y].plyrCtrl) {return true;}
@@ -980,7 +994,7 @@ HackingMission.prototype.initJsPlumb = function() {
this.jsplumbinstance = instance;
//All player cores are sources
// All player cores are sources
for (var i = 0; i < this.playerCores.length; ++i) {
instance.makeSource(this.playerCores[i].el, {
deleteEndpointsOnEmpty:true,
@@ -990,7 +1004,7 @@ HackingMission.prototype.initJsPlumb = function() {
});
}
//Everything else is a target
// Everything else is a target
for (var i = 0; i < this.enemyCores.length; ++i) {
instance.makeTarget(this.enemyCores[i].el, {
maxConnections:-1,
@@ -1020,7 +1034,7 @@ HackingMission.prototype.initJsPlumb = function() {
});
}
//Clicking a connection drops it
// Clicking a connection drops it
instance.bind("click", (conn, originalEvent) => {
// Cannot drop enemy's connections
const sourceNode = this.getNodeFromElement(conn.source);
@@ -1030,15 +1044,15 @@ HackingMission.prototype.initJsPlumb = function() {
endpoints[0].detachFrom(endpoints[1]);
});
//Connection events
// Connection events
instance.bind("connection", (info) => {
var targetNode = this.getNodeFromElement(info.target);
//Do not detach for enemy nodes
// Do not detach for enemy nodes
var thisNode = this.getNodeFromElement(info.source);
if (thisNode.enmyCtrl) {return;}
//If the node is not reachable, drop the connection
// If the node is not reachable, drop the connection
if (!this.nodeReachable(targetNode)) {
info.sourceEndpoint.detachFrom(info.targetEndpoint);
return;
@@ -1050,7 +1064,7 @@ HackingMission.prototype.initJsPlumb = function() {
++targetNode.targetedCount;
});
//Detach Connection events
// Detach Connection events
instance.bind("connectionDetached", (info, originalEvent)=>{
var sourceNode = this.getNodeFromElement(info.source);
sourceNode.conn = null;
@@ -1060,7 +1074,7 @@ HackingMission.prototype.initJsPlumb = function() {
}
//Drops all connections where the specified node is the source
// Drops all connections where the specified node is the source
HackingMission.prototype.dropAllConnectionsFromNode = function(node) {
var allConns = this.jsplumbinstance.getAllConnections();
for (var i = allConns.length-1; i >= 0; --i) {
@@ -1070,7 +1084,7 @@ HackingMission.prototype.dropAllConnectionsFromNode = function(node) {
}
}
//Drops all connections where the specified node is the target
// Drops all connections where the specified node is the target
HackingMission.prototype.dropAllConnectionsToNode = function(node) {
var allConns = this.jsplumbinstance.getAllConnections();
for (var i = allConns.length-1; i >= 0; --i) {
@@ -1085,10 +1099,10 @@ var storedCycles = 0;
HackingMission.prototype.process = function(numCycles=1) {
if (!this.started) {return;}
storedCycles += numCycles;
if (storedCycles < 2) {return;} //Only process every 3 cycles minimum
if (storedCycles < 2) {return;} // Only process every 3 cycles minimum
var res = false;
//Process actions of all player nodes
// Process actions of all player nodes
this.playerCores.forEach((node)=>{
res |= this.processNode(node, storedCycles);
});
@@ -1101,7 +1115,7 @@ HackingMission.prototype.process = function(numCycles=1) {
}
});
//Process actions of all enemy nodes
// Process actions of all enemy nodes
this.enemyCores.forEach((node)=>{
this.enemyAISelectAction(node);
res |= this.processNode(node, storedCycles);
@@ -1116,7 +1130,7 @@ HackingMission.prototype.process = function(numCycles=1) {
}
});
//The hp of enemy databases increases slowly
// The hp of enemy databases increases slowly
this.enemyDatabases.forEach((node)=>{
node.maxhp += (0.1 * storedCycles);
node.hp += (0.1 * storedCycles);
@@ -1127,19 +1141,19 @@ HackingMission.prototype.process = function(numCycles=1) {
this.calculateDefenses();
}
//Win if all enemy databases are conquered
// Win if all enemy databases are conquered
if (this.enemyDatabases.length === 0) {
this.finishMission(true);
return;
}
//Lose if all your cores are gone
// Lose if all your cores are gone
if (this.playerCores.length === 0) {
this.finishMission(false);
return;
}
//Defense/hp of misc nodes increases slowly over time
// Defense/hp of misc nodes increases slowly over time
this.miscNodes.forEach((node)=>{
node.def += (0.1 * storedCycles);
node.maxhp += (0.05 * storedCycles);
@@ -1148,7 +1162,7 @@ HackingMission.prototype.process = function(numCycles=1) {
this.updateNodeDomElement(node);
});
//Update timer and check if player lost
// Update timer and check if player lost
this.time -= (storedCycles * Engine._idleSpeed);
if (this.time <= 0) {
this.finishMission(false);
@@ -1159,7 +1173,7 @@ HackingMission.prototype.process = function(numCycles=1) {
storedCycles = 0;
}
//Returns a bool representing whether defenses need to be re-calculated
// Returns a bool representing whether defenses need to be re-calculated
HackingMission.prototype.processNode = function(nodeObj, numCycles=1) {
if (nodeObj.action == null) {
return;
@@ -1174,21 +1188,21 @@ HackingMission.prototype.processNode = function(nodeObj, numCycles=1) {
}
if (targetNode == null) {
//Player is in the middle of dragging the connection,
//so the target node is null. Do nothing here
// Player is in the middle of dragging the connection,
// so the target node is null. Do nothing here
} else if (targetNode.plyrCtrl) {
def = this.playerDef;
atk = this.enemyAtk;
} else if (targetNode.enmyCtrl) {
def = this.enemyDef;
atk = this.playerAtk;
} else { //Misc Node
} else { // Misc Node
def = targetNode.def;
nodeObj.plyrCtrl ? atk = this.playerAtk : atk = this.enemyAtk;
}
}
//Calculations are per second, so divide everything by 5
// Calculations are per second, so divide everything by 5
var calcStats = false, plyr = nodeObj.plyrCtrl;
var enmyHacking = this.difficulty * CONSTANTS.HackingMissionDifficultyToHacking;
switch(nodeObj.action) {
@@ -1229,13 +1243,13 @@ HackingMission.prototype.processNode = function(nodeObj, numCycles=1) {
break;
}
//Stats can't go below 0
// Stats can't go below 0
if (nodeObj.atk < 0) {nodeObj.atk = 0;}
if (nodeObj.def < 0) {nodeObj.def = 0;}
if (targetNode && targetNode.atk < 0) {targetNode.atk = 0;}
if (targetNode && targetNode.def < 0) {targetNode.def = 0;}
//Conquering a node
// Conquering a node
if (targetNode && targetNode.hp <= 0) {
var conqueredByPlayer = nodeObj.plyrCtrl;
targetNode.hp = targetNode.maxhp;
@@ -1245,18 +1259,18 @@ HackingMission.prototype.processNode = function(nodeObj, numCycles=1) {
targetNode.deselect(this.actionButtons);
}
//The conquered node has its stats reduced
// The conquered node has its stats reduced
targetNode.atk /= 2;
targetNode.def /= 3.5;
//Flag for whether the target node was a misc node
// Flag for whether the target node was a misc node
var isMiscNode = !targetNode.plyrCtrl && !targetNode.enmyCtrl;
//Remove all connections from Node
// Remove all connections from Node
this.dropAllConnectionsToNode(targetNode);
this.dropAllConnectionsFromNode(targetNode);
//Changes the css class and turn the node into a JsPlumb Source/Target
// Changes the css class and turn the node into a JsPlumb Source/Target
if (conqueredByPlayer) {
targetNode.setControlledByPlayer();
this.jsplumbinstance.unmakeTarget(targetNode.el);
@@ -1268,7 +1282,7 @@ HackingMission.prototype.processNode = function(nodeObj, numCycles=1) {
});
} else {
targetNode.setControlledByEnemy();
nodeObj.conn = null; //Clear connection
nodeObj.conn = null; // Clear connection
this.jsplumbinstance.unmakeSource(targetNode.el);
this.jsplumbinstance.makeTarget(targetNode.el, {
maxConnections:-1,
@@ -1279,7 +1293,7 @@ HackingMission.prototype.processNode = function(nodeObj, numCycles=1) {
calcStats = true;
//Helper function to swap nodes between the respective enemyNodes/playerNodes arrays
// Helper function to swap nodes between the respective enemyNodes/playerNodes arrays
function swapNodes(orig, dest, targetNode) {
for (var i = 0; i < orig.length; ++i) {
if (orig[i] == targetNode) {
@@ -1319,7 +1333,7 @@ HackingMission.prototype.processNode = function(nodeObj, numCycles=1) {
case NodeTypes.Spam:
if (conqueredByPlayer) {
swapNodes(isMiscNode ? this.miscNodes : this.enemyNodes, this.playerNodes, targetNode);
//Conquering spam node increases time limit
// Conquering spam node increases time limit
this.time += CONSTANTS.HackingMissionSpamTimeIncrease;
} else {
swapNodes(isMiscNode ? this.miscNodes : this.playerNodes, this.enemyNodes, targetNode);
@@ -1327,7 +1341,7 @@ HackingMission.prototype.processNode = function(nodeObj, numCycles=1) {
break;
case NodeTypes.Transfer:
//Conquering a Transfer node increases the attack of all cores by some percentages
// Conquering a Transfer node increases the attack of all cores by some percentages
if (conqueredByPlayer) {
swapNodes(isMiscNode ? this.miscNodes : this.enemyNodes, this.playerNodes, targetNode);
this.playerCores.forEach(function(node) {
@@ -1353,7 +1367,7 @@ HackingMission.prototype.processNode = function(nodeObj, numCycles=1) {
break;
}
//If a misc node was conquered, the defense for all misc nodes increases by some fixed amount
// If a misc node was conquered, the defense for all misc nodes increases by some fixed amount
if (isMiscNode) { //&& conqueredByPlayer) {
this.miscNodes.forEach((node)=>{
if (node.targetedCount === 0) {
@@ -1363,23 +1377,25 @@ HackingMission.prototype.processNode = function(nodeObj, numCycles=1) {
}
}
//Update node DOMs
// Update node DOMs
this.updateNodeDomElement(nodeObj);
if (targetNode) {this.updateNodeDomElement(targetNode);}
return calcStats;
}
//Enemy "AI" for CPU Core and Transfer Nodes
// Enemy "AI" for CPU Core and Transfer Nodes
HackingMission.prototype.enemyAISelectAction = function(nodeObj) {
if (nodeObj == null) {return;}
switch(nodeObj.type) {
case NodeTypes.Core:
//Select a single RANDOM target from miscNodes and player's Nodes
//If it is reachable, it will target it. If not, no target will
//be selected for now, and the next time process() gets called this will repeat
/**
* Select a single RANDOM target from miscNodes and player's Nodes
* If it is reachable, it will target it. If not, no target will
* be selected for now, and the next time process() gets called this will repeat
*/
if (nodeObj.conn == null) {
if (this.miscNodes.length === 0) {
//Randomly pick a player node and attack it if its reachable
// Randomly pick a player node and attack it if its reachable
var rand = getRandomInt(0, this.playerNodes.length-1);
var node;
if (this.playerNodes.length === 0) {
@@ -1388,23 +1404,23 @@ HackingMission.prototype.enemyAISelectAction = function(nodeObj) {
node = this.playerNodes[rand];
}
if (this.nodeReachableByEnemy(node)) {
//Create connection
// Create connection
nodeObj.conn = this.jsplumbinstance.connect({
source:nodeObj.el,
target:node.el
});
++node.targetedCount;
} else {
//Randomly pick a player core and attack it if its reachable
// Randomly pick a player core and attack it if its reachable
rand = getRandomInt(0, this.playerCores.length-1);
if (this.playerCores.length === 0) {
return; //No Misc Nodes, no player Nodes, no Player cores. Player lost
return; // No Misc Nodes, no player Nodes, no Player cores. Player lost
} else {
node = this.playerCores[rand];
}
if (this.nodeReachableByEnemy(node)) {
//Create connection
// Create connection
nodeObj.conn = this.jsplumbinstance.connect({
source:nodeObj.el,
target:node.el
@@ -1413,7 +1429,7 @@ HackingMission.prototype.enemyAISelectAction = function(nodeObj) {
}
}
} else {
//Randomly pick a misc node and attack it if its reachable
// Randomly pick a misc node and attack it if its reachable
var rand = getRandomInt(0, this.miscNodes.length-1);
var node = this.miscNodes[rand];
if (this.nodeReachableByEnemy(node)) {
@@ -1425,10 +1441,10 @@ HackingMission.prototype.enemyAISelectAction = function(nodeObj) {
}
}
//If no connection was made, set the Core to Fortify
// If no connection was made, set the Core to Fortify
nodeObj.action = NodeActions.Fortify;
} else {
//If this node has a selected target
// If this node has a selected target
var targetNode;
if (nodeObj.conn.target) {
targetNode = this.getNodeFromElement(nodeObj.conn.target);
@@ -1453,7 +1469,7 @@ HackingMission.prototype.enemyAISelectAction = function(nodeObj) {
}
break;
case NodeTypes.Transfer:
//Switch between fortifying and overflowing as necessary
// Switch between fortifying and overflowing as necessary
if (nodeObj.def < 125) {
nodeObj.action = NodeActions.Fortify;
} else {
@@ -1469,11 +1485,11 @@ HackingMission.prototype.enemyAISelectAction = function(nodeObj) {
}
}
var hackEffWeightSelf = 130; //Weight for Node actions on self
var hackEffWeightTarget = 25; //Weight for Node Actions against Target
var hackEffWeightAttack = 80; //Weight for Attack action
var hackEffWeightSelf = 130; // Weight for Node actions on self
var hackEffWeightTarget = 25; // Weight for Node Actions against Target
var hackEffWeightAttack = 80; // Weight for Attack action
//Returns damage per cycle based on stats
// Returns damage per cycle based on stats
HackingMission.prototype.calculateAttackDamage = function(atk, def, hacking = 0) {
return Math.max(0.55 * (atk + (hacking / hackEffWeightAttack) - def), 1);
}
@@ -1494,11 +1510,11 @@ HackingMission.prototype.calculateOverflowEffect = function(hacking=0) {
return 0.95 * hacking / hackEffWeightSelf;
}
//Updates timer display
// Updates timer display
HackingMission.prototype.updateTimer = function() {
var timer = document.getElementById("hacking-mission-timer");
//Convert time remaining to a string of the form mm:ss
// Convert time remaining to a string of the form mm:ss
var seconds = Math.round(this.time / 1000);
var minutes = Math.trunc(seconds / 60);
seconds %= 60;
@@ -1506,7 +1522,7 @@ HackingMission.prototype.updateTimer = function() {
timer.innerText = "Time left: " + str;
}
//The 'win' argument is a bool for whether or not the player won
// The 'win' argument is a bool for whether or not the player won
HackingMission.prototype.finishMission = function(win) {
inMission = false;
currMission = null;
@@ -1525,13 +1541,13 @@ HackingMission.prototype.finishMission = function(win) {
dialogBoxCreate("Mission lost/forfeited! You did not gain any faction reputation.");
}
//Clear mission container
// Clear mission container
var container = document.getElementById("mission-container");
while(container.firstChild) {
container.removeChild(container.firstChild);
}
//Return to Faction page
// Return to Faction page
document.getElementById("mainmenu-container").style.visibility = "visible";
document.getElementById("character-overview-wrapper").style.visibility = "visible";
Engine.loadFactionContent();

View File

@@ -0,0 +1,72 @@
/**
* The environment in which a script runs. The environment holds
* Netscript functions and arguments for that script.
*/
import { IMap } from "../types";
export class Environment {
/**
* Parent environment. Used to implement "scope"
*/
parent: Environment | null = null;
/**
* Whether or not the script that uses this Environment should stop running
*/
stopFlag: boolean = false;
/**
* Environment variables (currently only Netscript functions)
*/
vars: IMap<any> = {};
constructor(parent: Environment | null) {
if (parent instanceof Environment) {
this.vars = Object.assign({}, parent.vars);
}
this.parent = parent;
}
/**
* Finds the scope where the variable with the given name is defined
*/
lookup(name: string): Environment | null {
let scope: Environment | null = this;
while (scope) {
if (Object.prototype.hasOwnProperty.call(scope.vars, name)) {
return scope;
}
scope = scope.parent;
}
return null;
}
//Get the current value of a variable
get(name: string): any {
if (name in this.vars) {
return this.vars[name];
}
throw new Error(`Undefined variable ${name}`);
}
//Sets the value of a variable in any scope
set(name: string, value: any) {
var scope = this.lookup(name);
//If scope has a value, then this variable is already set in a higher scope, so
//set is there. Otherwise, create a new variable in the local scope
if (scope !== null) {
return scope.vars[name] = value;
} else {
return this.vars[name] = value;
}
}
//Creates (or overwrites) a variable in the current scope
def(name: string, value: any) {
return this.vars[name] = value;
}
}

View File

@@ -0,0 +1,332 @@
import { IMap } from "../types";
// TODO remember to update RamCalculations.js and WorkerScript.js
// RAM costs for Netscript functions
export const RamCostConstants: IMap<number> = {
ScriptBaseRamCost: 1.6,
ScriptDomRamCost: 25,
ScriptHackRamCost: 0.1,
ScriptHackAnalyzeRamCost: 1,
ScriptGrowRamCost: 0.15,
ScriptGrowthAnalyzeRamCost: 1,
ScriptWeakenRamCost: 0.15,
ScriptScanRamCost: 0.2,
ScriptPortProgramRamCost: 0.05,
ScriptRunRamCost: 1.0,
ScriptExecRamCost: 1.3,
ScriptSpawnRamCost: 2.0,
ScriptScpRamCost: 0.6,
ScriptKillRamCost: 0.5,
ScriptHasRootAccessRamCost: 0.05,
ScriptGetHostnameRamCost: 0.05,
ScriptGetHackingLevelRamCost: 0.05,
ScriptGetMultipliersRamCost: 4.0,
ScriptGetServerRamCost: 0.1,
ScriptFileExistsRamCost: 0.1,
ScriptIsRunningRamCost: 0.1,
ScriptHacknetNodesRamCost: 4.0,
ScriptHNUpgLevelRamCost: 0.4,
ScriptHNUpgRamRamCost: 0.6,
ScriptHNUpgCoreRamCost: 0.8,
ScriptGetStockRamCost: 2.0,
ScriptBuySellStockRamCost: 2.5,
ScriptGetPurchaseServerRamCost: 0.25,
ScriptPurchaseServerRamCost: 2.25,
ScriptGetPurchasedServerLimit: 0.05,
ScriptGetPurchasedServerMaxRam: 0.05,
ScriptRoundRamCost: 0.05,
ScriptReadWriteRamCost: 1.0,
ScriptArbScriptRamCost: 1.0,
ScriptGetScriptRamCost: 0.1,
ScriptGetHackTimeRamCost: 0.05,
ScriptGetFavorToDonate: 0.10,
ScriptCodingContractBaseRamCost: 10,
ScriptSleeveBaseRamCost: 4,
ScriptSingularityFn1RamCost: 2,
ScriptSingularityFn2RamCost: 3,
ScriptSingularityFn3RamCost: 5,
ScriptGangApiBaseRamCost: 4,
ScriptBladeburnerApiBaseRamCost: 4,
}
export const RamCosts: IMap<any> = {
hacknet: {
numNodes: () => 0,
purchaseNode: () => 0,
getPurchaseNodeCost: () => 0,
getNodeStats: () => 0,
upgradeLevel: () => 0,
upgradeRam: () => 0,
upgradeCore: () => 0,
upgradeCache: () => 0,
getLevelUpgradeCost: () => 0,
getRamUpgradeCost: () => 0,
getCoreUpgradeCost: () => 0,
getCacheUpgradeCost: () => 0,
numHashes: () => 0,
hashCost: () => 0,
spendHashes: () => 0,
},
sprintf: () => 0,
vsprintf: () => 0,
scan: () => RamCostConstants.ScriptScanRamCost,
hack: () => RamCostConstants.ScriptHackRamCost,
hackAnalyzeThreads: () => RamCostConstants.ScriptHackAnalyzeRamCost,
hackAnalyzePercent: () => RamCostConstants.ScriptHackAnalyzeRamCost,
hackChance: () => RamCostConstants.ScriptHackAnalyzeRamCost,
sleep: () => 0,
grow: () => RamCostConstants.ScriptGrowRamCost,
growthAnalyze: () => RamCostConstants.ScriptGrowthAnalyzeRamCost,
weaken: () => RamCostConstants.ScriptWeakenRamCost,
print: () => 0,
tprint: () => 0,
clearLog: () => 0,
disableLog: () => 0,
enableLog: () => 0,
isLogEnabled: () => 0,
getScriptLogs: () => 0,
nuke: () => RamCostConstants.ScriptPortProgramRamCost,
brutessh: () => RamCostConstants.ScriptPortProgramRamCost,
ftpcrack: () => RamCostConstants.ScriptPortProgramRamCost,
relaysmtp: () => RamCostConstants.ScriptPortProgramRamCost,
httpworm: () => RamCostConstants.ScriptPortProgramRamCost,
sqlinject: () => RamCostConstants.ScriptPortProgramRamCost,
run: () => RamCostConstants.ScriptRunRamCost,
exec: () => RamCostConstants.ScriptExecRamCost,
spawn: () => RamCostConstants.ScriptSpawnRamCost,
kill: () => RamCostConstants.ScriptKillRamCost,
killall: () => RamCostConstants.ScriptKillRamCost,
exit: () => 0,
scp: () => RamCostConstants.ScriptScpRamCost,
ls: () => RamCostConstants.ScriptScanRamCost,
ps: () => RamCostConstants.ScriptScanRamCost,
hasRootAccess: () => RamCostConstants.ScriptHasRootAccessRamCost,
getIp: () => RamCostConstants.ScriptGetHostnameRamCost,
getHostname: () => RamCostConstants.ScriptGetHostnameRamCost,
getHackingLevel: () => RamCostConstants.ScriptGetHackingLevelRamCost,
getHackingMultipliers: () => RamCostConstants.ScriptGetMultipliersRamCost,
getHacknetMultipliers: () => RamCostConstants.ScriptGetMultipliersRamCost,
getBitNodeMultipliers: () => RamCostConstants.ScriptGetMultipliersRamCost,
getServerMoneyAvailable: () => RamCostConstants.ScriptGetServerRamCost,
getServerSecurityLevel: () => RamCostConstants.ScriptGetServerRamCost,
getServerBaseSecurityLevel: () => RamCostConstants.ScriptGetServerRamCost,
getServerMinSecurityLevel: () => RamCostConstants.ScriptGetServerRamCost,
getServerRequiredHackingLevel: () => RamCostConstants.ScriptGetServerRamCost,
getServerMaxMoney: () => RamCostConstants.ScriptGetServerRamCost,
getServerGrowth: () => RamCostConstants.ScriptGetServerRamCost,
getServerNumPortsRequired: () => RamCostConstants.ScriptGetServerRamCost,
getServerRam: () => RamCostConstants.ScriptGetServerRamCost,
serverExists: () => RamCostConstants.ScriptGetServerRamCost,
fileExists: () => RamCostConstants.ScriptFileExistsRamCost,
isRunning: () => RamCostConstants.ScriptIsRunningRamCost,
getStockSymbols: () => RamCostConstants.ScriptGetStockRamCost,
getStockPrice: () => RamCostConstants.ScriptGetStockRamCost,
getStockAskPrice: () => RamCostConstants.ScriptGetStockRamCost,
getStockBidPrice: () => RamCostConstants.ScriptGetStockRamCost,
getStockPosition: () => RamCostConstants.ScriptGetStockRamCost,
getStockMaxShares: () => RamCostConstants.ScriptGetStockRamCost,
getStockPurchaseCost: () => RamCostConstants.ScriptGetStockRamCost,
getStockSaleGain: () => RamCostConstants.ScriptGetStockRamCost,
buyStock: () => RamCostConstants.ScriptBuySellStockRamCost,
sellStock: () => RamCostConstants.ScriptBuySellStockRamCost,
shortStock: () => RamCostConstants.ScriptBuySellStockRamCost,
sellShort: () => RamCostConstants.ScriptBuySellStockRamCost,
placeOrder: () => RamCostConstants.ScriptBuySellStockRamCost,
cancelOrder: () => RamCostConstants.ScriptBuySellStockRamCost,
getOrders: () => RamCostConstants.ScriptBuySellStockRamCost,
getStockVolatility: () => RamCostConstants.ScriptBuySellStockRamCost,
getStockForecast: () => RamCostConstants.ScriptBuySellStockRamCost,
purchase4SMarketData: () => RamCostConstants.ScriptBuySellStockRamCost,
purchase4SMarketDataTixApi: () => RamCostConstants.ScriptBuySellStockRamCost,
getPurchasedServerLimit: () => RamCostConstants.ScriptGetPurchasedServerLimit,
getPurchasedServerMaxRam: () => RamCostConstants.ScriptGetPurchasedServerMaxRam,
getPurchasedServerCost: () => RamCostConstants.ScriptGetPurchaseServerRamCost,
purchaseServer: () => RamCostConstants.ScriptPurchaseServerRamCost,
deleteServer: () => RamCostConstants.ScriptPurchaseServerRamCost,
getPurchasedServers: () => RamCostConstants.ScriptPurchaseServerRamCost,
write: () => RamCostConstants.ScriptReadWriteRamCost,
tryWrite: () => RamCostConstants.ScriptReadWriteRamCost,
read: () => RamCostConstants.ScriptReadWriteRamCost,
peek: () => RamCostConstants.ScriptReadWriteRamCost,
clear: () => RamCostConstants.ScriptReadWriteRamCost,
getPortHandle: () => RamCostConstants.ScriptReadWriteRamCost * 10,
rm: () => RamCostConstants.ScriptReadWriteRamCost,
scriptRunning: () => RamCostConstants.ScriptArbScriptRamCost,
scriptKill: () => RamCostConstants.ScriptArbScriptRamCost,
getScriptName: () => 0,
getScriptRam: () => RamCostConstants.ScriptGetScriptRamCost,
getHackTime: () => RamCostConstants.ScriptGetHackTimeRamCost,
getGrowTime: () => RamCostConstants.ScriptGetHackTimeRamCost,
getWeakenTime: () => RamCostConstants.ScriptGetHackTimeRamCost,
getScriptIncome: () => RamCostConstants.ScriptGetScriptRamCost,
getScriptExpGain: () => RamCostConstants.ScriptGetScriptRamCost,
nFormat: () => 0,
getTimeSinceLastAug: () => RamCostConstants.ScriptGetHackTimeRamCost,
prompt: () => 0,
wget: () => 0,
getFavorToDonate: () => RamCostConstants.ScriptGetFavorToDonate,
// Singularity Functions
universityCourse: () => RamCostConstants.ScriptSingularityFn1RamCost,
gymWorkout: () => RamCostConstants.ScriptSingularityFn1RamCost,
travelToCity: () => RamCostConstants.ScriptSingularityFn1RamCost,
purchaseTor: () => RamCostConstants.ScriptSingularityFn1RamCost,
purchaseProgram: () => RamCostConstants.ScriptSingularityFn1RamCost,
getStats: () => RamCostConstants.ScriptSingularityFn1RamCost / 4,
getCharacterInformation: () => RamCostConstants.ScriptSingularityFn1RamCost / 4,
isBusy: () => RamCostConstants.ScriptSingularityFn1RamCost / 4,
stopAction: () => RamCostConstants.ScriptSingularityFn1RamCost / 2,
upgradeHomeRam: () => RamCostConstants.ScriptSingularityFn2RamCost,
getUpgradeHomeRamCost: () => RamCostConstants.ScriptSingularityFn2RamCost / 2,
workForCompany: () => RamCostConstants.ScriptSingularityFn2RamCost,
applyToCompany: () => RamCostConstants.ScriptSingularityFn2RamCost,
getCompanyRep: () => RamCostConstants.ScriptSingularityFn2RamCost / 3,
getCompanyFavor: () => RamCostConstants.ScriptSingularityFn2RamCost / 3,
getCompanyFavorGain: () => RamCostConstants.ScriptSingularityFn2RamCost / 4,
checkFactionInvitations: () => RamCostConstants.ScriptSingularityFn2RamCost,
joinFaction: () => RamCostConstants.ScriptSingularityFn2RamCost,
workForFaction: () => RamCostConstants.ScriptSingularityFn2RamCost,
getFactionRep: () => RamCostConstants.ScriptSingularityFn2RamCost / 3,
getFactionFavor: () => RamCostConstants.ScriptSingularityFn2RamCost / 3,
getFactionFavorGain: () => RamCostConstants.ScriptSingularityFn2RamCost / 4,
donateToFaction: () => RamCostConstants.ScriptSingularityFn3RamCost,
createProgram: () => RamCostConstants.ScriptSingularityFn3RamCost,
commitCrime: () => RamCostConstants.ScriptSingularityFn3RamCost,
getCrimeChance: () => RamCostConstants.ScriptSingularityFn3RamCost,
getOwnedAugmentations: () => RamCostConstants.ScriptSingularityFn3RamCost,
getOwnedSourceFiles: () => RamCostConstants.ScriptSingularityFn3RamCost,
getAugmentationsFromFaction: () => RamCostConstants.ScriptSingularityFn3RamCost,
getAugmentationPrereq: () => RamCostConstants.ScriptSingularityFn3RamCost,
getAugmentationCost: () => RamCostConstants.ScriptSingularityFn3RamCost,
purchaseAugmentation: () => RamCostConstants.ScriptSingularityFn3RamCost,
installAugmentations: () => RamCostConstants.ScriptSingularityFn3RamCost,
// Gang API
gang : {
getMemberNames: () => RamCostConstants.ScriptGangApiBaseRamCost / 4,
getGangInformation: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,
getOtherGangInformation: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,
getMemberInformation: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,
canRecruitMember: () => RamCostConstants.ScriptGangApiBaseRamCost / 4,
recruitMember: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,
getTaskNames: () => RamCostConstants.ScriptGangApiBaseRamCost / 4,
setMemberTask: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,
getEquipmentNames: () => RamCostConstants.ScriptGangApiBaseRamCost / 4,
getEquipmentCost: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,
getEquipmentType: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,
purchaseEquipment: () => RamCostConstants.ScriptGangApiBaseRamCost,
ascendMember: () => RamCostConstants.ScriptGangApiBaseRamCost,
setTerritoryWarfare: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,
getChanceToWinClash: () => RamCostConstants.ScriptGangApiBaseRamCost,
getBonusTime: () => 0,
},
// Bladeburner API
bladeburner : {
getContractNames: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10,
getOperationNames: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10,
getBlackOpNames: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10,
getBlackOpRank: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 2,
getGeneralActionNames: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10,
getSkillNames: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10,
startAction: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
stopBladeburnerAction: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 2,
getCurrentAction: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 4,
getActionTime: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionEstimatedSuccessChance: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionRepGain: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionCountRemaining: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionMaxLevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionCurrentLevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionAutolevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
setActionAutolevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
setActionLevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getRank: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getSkillPoints: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getSkillLevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getSkillUpgradeCost: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
upgradeSkill: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getTeamSize: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
setTeamSize: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getCityEstimatedPopulation: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getCityEstimatedCommunities: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getCityChaos: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getCity: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
switchCity: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getStamina: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
joinBladeburnerFaction: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
joinBladeburnerDivision: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getBonusTime: () => 0,
},
// Coding Contract API
codingcontract : {
attempt: () => RamCostConstants.ScriptCodingContractBaseRamCost,
getContractType: () => RamCostConstants.ScriptCodingContractBaseRamCost / 2,
getData: () => RamCostConstants.ScriptCodingContractBaseRamCost / 2,
getDescription: () => RamCostConstants.ScriptCodingContractBaseRamCost / 2,
getNumTriesRemaining: () => RamCostConstants.ScriptCodingContractBaseRamCost / 5,
},
// Duplicate Sleeve API
sleeve : {
getNumSleeves: () => RamCostConstants.ScriptSleeveBaseRamCost,
setToShockRecovery: () => RamCostConstants.ScriptSleeveBaseRamCost,
setToSynchronize: () => RamCostConstants.ScriptSleeveBaseRamCost,
setToCommitCrime: () => RamCostConstants.ScriptSleeveBaseRamCost,
setToUniversityCourse: () => RamCostConstants.ScriptSleeveBaseRamCost,
travel: () => RamCostConstants.ScriptSleeveBaseRamCost,
setToCompanyWork: () => RamCostConstants.ScriptSleeveBaseRamCost,
setToFactionWork: () => RamCostConstants.ScriptSleeveBaseRamCost,
setToGymWorkout: () => RamCostConstants.ScriptSleeveBaseRamCost,
getSleeveStats: () => RamCostConstants.ScriptSleeveBaseRamCost,
getTask: () => RamCostConstants.ScriptSleeveBaseRamCost,
getInformation: () => RamCostConstants.ScriptSleeveBaseRamCost,
getSleeveAugmentations: () => RamCostConstants.ScriptSleeveBaseRamCost,
getSleevePurchasableAugs: () => RamCostConstants.ScriptSleeveBaseRamCost,
purchaseSleeveAug: () => RamCostConstants.ScriptSleeveBaseRamCost,
},
heart: {
// Easter egg function
break : () => 0,
}
}
export function getRamCost(...args: string[]): number {
if (args.length === 0) {
console.warn(`No arguments passed to getRamCost()`);
return 0;
}
let curr = RamCosts[args[0]];
for (let i = 1; i < args.length; ++i) {
if (curr == null) {
console.warn(`Invalid function passed to getRamCost: ${args}`);
return 0;
}
const currType = typeof curr;
if (currType === "function" || currType === "number") {
break;
}
curr = curr[args[i]];
}
const currType = typeof curr;
if (currType === "function") {
return curr();
}
if (currType === "number") {
return curr;
}
console.warn(`Unexpected type (${currType}) for value [${args}]`);
return 0;
}

View File

@@ -0,0 +1,194 @@
/**
* The worker agent for running a script instance. Each running script instance
* has its own underlying WorkerScript object.
*
* Note that these objects are not saved and re-loaded when the game is refreshed.
* Instead, whenever the game is opened, WorkerScripts are re-created from
* RunningScript objects
*/
import { Environment } from "./Environment";
import { RamCostConstants } from "./RamCostGenerator";
import { RunningScript } from "../Script/RunningScript";
import { Script } from "../Script/Script";
import { AllServers } from "../Server/AllServers";
import { BaseServer } from "../Server/BaseServer";
import { IMap } from "../types";
export class WorkerScript {
/**
* Script's arguments
*/
args: any[];
/**
* Copy of the script's code
*/
code: string = "";
/**
* Holds the timeoutID (numeric value) for whenever this script is blocked by a
* timed Netscript function. i.e. Holds the return value of setTimeout()
*/
delay: number | null = null;
/**
* 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
*/
disableLogs: IMap<string> = {};
/**
* Used for dynamic RAM calculation. Stores names of all functions that have
* already been checked by this script.
* TODO: Could probably just combine this with loadedFns?
*/
dynamicLoadedFns: IMap<string> = {};
/**
* Tracks dynamic RAM usage
*/
dynamicRamUsage: number = RamCostConstants.ScriptBaseRamCost;
/**
* Netscript Environment for this script
*/
env: Environment;
/**
* Status message in case of script error. Currently unused I think
*/
errorMessage: string = "";
/**
* Used for static RAM calculation. Stores names of all functions that have
* already been checked by this script
*/
loadedFns: IMap<string> = {};
/**
* Filename of script
*/
name: string;
/**
* Script's output/return value. Currently not used or implemented
*/
output: string = "";
/**
* 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
*/
ramUsage: number = 0;
/**
* Whether or not this workerScript is currently running
*/
running: boolean = false;
/**
* Reference to underlying RunningScript object
*/
scriptRef: RunningScript;
/**
* IP Address on which this script is running
*/
serverIp: string;
constructor(runningScriptObj: RunningScript, 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) {
throw new Error(`WorkerScript constructed with invalid server ip: ${this.serverIp}`);
}
let found = false;
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === this.name) {
found = true;
this.code = server.scripts[i].code;
}
}
if (!found) {
throw new Error(`WorkerScript constructed with invalid script filename: ${this.name}`);
}
this.env = new Environment(null);
if (typeof nsFuncsGenerator === "function") {
this.env.vars = nsFuncsGenerator(this);
}
this.env.set("args", runningScriptObj.args.slice());
this.scriptRef = runningScriptObj;
this.args = runningScriptObj.args.slice();
}
/**
* Returns the Server on which this script is running
*/
getServer() {
return AllServers[this.serverIp];
}
/**
* Returns the Script object for the underlying script.
* Returns null if it cannot be found (which would be a bug)
*/
getScript(): Script | null {
let server = this.getServer();
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === this.name) {
return server.scripts[i];
}
}
console.error("Failed to find underlying Script object in WorkerScript.getScript(). This probably means somethings wrong");
return null;
}
/**
* Returns the script with the specified filename on the specified server,
* or null if it cannot be found
*/
getScriptOnServer(fn: string, server: BaseServer): Script | null {
if (server == null) {
server = this.getServer();
}
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === fn) {
return server.scripts[i];
}
}
return null;
}
shouldLog(fn: string): boolean {
return (this.disableLogs.ALL == null && this.disableLogs[fn] == null);
}
log(txt: string): void {
this.scriptRef.log(txt);
}
}

View File

@@ -0,0 +1,6 @@
/**
* Event emitter that triggers when scripts are started/stopped
*/
import { EventEmitter } from "../utils/EventEmitter";
export const WorkerScriptStartStopEventEmitter = new EventEmitter();

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