Compare commits

...

105 Commits

Author SHA1 Message Date
Olivier Gagnon
42704d8695 v0.52.9 2021-08-27 15:26:12 -04:00
Olivier Gagnon
e75197dee3 build 2021-08-27 14:19:36 -04:00
Olivier Gagnon
9e92df47a5 Added file diagnostic. 2021-08-27 14:17:25 -04:00
Olivier Gagnon
c110c22efb My corp infinity safeguard from 2 patch ago wasn't actually preventing it, just logging, now it returns to avoid it. 2021-08-27 11:18:06 -04:00
Olivier Gagnon
c9ab7908a7 another blocker against mku equal 0 and added tprintf 2021-08-27 11:05:36 -04:00
Olivier Gagnon
3ab306f9d7 fix the errors about node setTimeout instead of window 2021-08-27 01:11:11 -04:00
hydroflame
f08aa8924c Merge pull request #1102 from threehams/test-runner
Switch out test runner for jest
2021-08-27 00:53:45 -04:00
Olivier Gagnon
c4914fa54f build community prs 2021-08-27 00:45:11 -04:00
hydroflame
fa5e2f4964 Merge pull request #1079 from threehams/infil-instakill
Instakill player when automating infiltration
2021-08-26 21:42:08 -04:00
hydroflame
77eda1fd75 Merge pull request #1098 from brubsby/patch-1
add bladeburner_analysis_mult to getPlayer()
2021-08-26 21:42:01 -04:00
Olivier Gagnon
c987c91a11 add corp safeguard 2021-08-26 21:39:51 -04:00
David Edmondson
feaa74ed34 Only compile down imports during tests 2021-08-26 17:02:02 -07:00
David Edmondson
701fba7ec7 Drop cross-env 2021-08-26 16:45:39 -07:00
David Edmondson
51bd626e88 Remove unneeded stuff, .vscode on gitignore 2021-08-26 16:44:37 -07:00
David Edmondson
ab4863e7df Swap out mocha/chai for jest 2021-08-26 16:43:11 -07:00
David Edmondson
1a8bcf66cc Fix existing tests, update to jest 2021-08-26 16:43:03 -07:00
David Edmondson
7bfceb1690 Replace old-style import with type 2021-08-26 16:42:57 -07:00
David Edmondson
27e22814a9 Remove missing + unused variable 2021-08-26 16:42:47 -07:00
Olivier Gagnon
ceb4e304fd Hotfix corp mku getting set to zero and causing infinity 2021-08-26 15:22:06 -04:00
Olivier Gagnon
e2d74f9432 fix beautify 2021-08-25 16:14:47 -04:00
Olivier Gagnon
79345a49b4 Bladeburner automation status always displays the commands, even when disabled 2021-08-25 11:50:33 -04:00
Olivier Gagnon
7066a793a1 build fix 2021-08-24 21:40:50 -04:00
hydroflame
2a5cf62168 Merge pull request #1097 from Snarling/patch-2
Fix joining blade via ns
2021-08-24 21:39:29 -04:00
brubsby
6495be5705 add bladeburner_analysis_mult to getPlayer() 2021-08-24 20:02:39 -05:00
Snarling
0d6d05db49 Fix joining blade via ns
Pass Player as an argument in Bladeburner constructor call for ns.bladeburner.joinBladeburnerDivision()
2021-08-24 20:08:29 -04:00
Olivier Gagnon
5d59620dce click to copy every bladeburner action 2021-08-23 11:42:14 -04:00
Olivier Gagnon
60d95a90d0 Fix script not being saved on their individual computers. 2021-08-23 09:33:49 -04:00
Olivier Gagnon
51debc60da build omuretsu fix 2021-08-23 09:18:43 -04:00
Snarling
faf625b34d Update Root.tsx
Went back to tracking lastServer as a hostname, since server IPs are not static.
2021-08-23 04:04:52 -07:00
Snarling
1a8b194341 Update Root.tsx
Removed unnecessary conversions between server and ip
2021-08-23 04:04:52 -07:00
Snarling
386f8a11c5 Change lastServer to reference the server ip
Should fix issue with newly saved scripts failing to run
2021-08-23 04:04:52 -07:00
hydroflame
4278191b0e Merge pull request #1090 from danielyxie/dev
v0.52.8
2021-08-23 02:09:55 -04:00
Olivier Gagnon
6d2b8b4f6f v0.52.8 2021-08-23 02:09:49 -04:00
Olivier Gagnon
b148b2f0b5 logbox close on escape now 2021-08-23 01:15:20 -04:00
hydroflame
4a9bac99d2 Merge pull request #1083 from danielyxie/dev
Fix monaco jumping to end of file.
2021-08-22 23:57:16 -04:00
Olivier Gagnon
0b3c114cd0 Fix monaco jumping to end of file. 2021-08-22 23:57:00 -04:00
hydroflame
49cc75a575 Merge pull request #1082 from danielyxie/dev
trying to fix the jumping bug
2021-08-22 23:47:44 -04:00
Olivier Gagnon
e0d631f8b3 trying to fix the jumping bug 2021-08-22 23:46:30 -04:00
hydroflame
8289c9fc75 Merge pull request #1080 from danielyxie/dev
Fixed Script Editor not loading the same file after manually clicking it
2021-08-22 01:31:00 -04:00
Olivier Gagnon
d66e36b637 Fixed Script Editor not loading the same file after manually clicking it 2021-08-22 01:30:28 -04:00
David Edmondson
6cd7465b82 Instakill player when automating infiltration 2021-08-21 15:00:00 -07:00
hydroflame
c7125e2e46 Merge pull request #1077 from danielyxie/dev
Fix a few other bugs
2021-08-21 14:01:05 -04:00
Olivier Gagnon
a564957092 v0.52.7 2021-08-21 14:00:28 -04:00
Olivier Gagnon
4b8e63f342 Fix a few other bugs 2021-08-21 11:30:31 -04:00
hydroflame
480d47eece Merge pull request #1076 from danielyxie/dev
Fix log box dragging.
2021-08-21 02:39:17 -04:00
Olivier Gagnon
4de20f8cce Made logbox drag a little smoother. 2021-08-21 02:31:37 -04:00
Olivier Gagnon
4b38d296a8 Fix corp industry wrong initial value. 2021-08-21 02:10:58 -04:00
hydroflame
9ac75d5bf5 Merge pull request #1075 from danielyxie/dev
Fix Corp research popup box appearing behind one another.
2021-08-21 02:07:10 -04:00
Olivier Gagnon
6561413137 Fix Corp research popup box appearing behind one another. 2021-08-21 02:06:48 -04:00
hydroflame
1fb5105d0a Merge pull request #1074 from danielyxie/dev
hotfix broken editor shortcuts
2021-08-21 01:55:05 -04:00
Olivier Gagnon
b67c03ff8a hotfix broken editor shortcuts 2021-08-21 01:54:39 -04:00
hydroflame
7db3716256 Merge pull request #1072 from danielyxie/dev
hotfix the tutorial
2021-08-21 00:58:58 -04:00
Olivier Gagnon
ee5a70901b hotfix logbox width 2021-08-21 00:58:24 -04:00
Olivier Gagnon
63b2c77907 hotfix the tutorial 2021-08-21 00:51:07 -04:00
hydroflame
aa3ad3164c Merge pull request #1068 from danielyxie/dev
v0.52.6
2021-08-21 00:32:04 -04:00
Olivier Gagnon
474befa091 v0.52.6 2021-08-21 00:31:42 -04:00
hydroflame
cd1c1ce145 Merge pull request #1067 from danielyxie/monaco
Monaco
2021-08-21 00:17:58 -04:00
Olivier Gagnon
5aa24f22c4 final changes for monac 2021-08-21 00:17:26 -04:00
Olivier Gagnon
f02c6443cc ok figured out how to make the javascript autocomplete. 2021-08-20 17:14:20 -04:00
Olivier Gagnon
4497143785 basic autocomplete working 2021-08-20 16:11:49 -04:00
Olivier Gagnon
0b3c48827b Ok we can load a thing but it has no effect. 2021-08-20 15:46:59 -04:00
Olivier Gagnon
86de11e794 link for monaco 2021-08-20 15:46:59 -04:00
Olivier Gagnon
fb87385704 Added function definition for netscritp in typescript 2021-08-20 15:46:59 -04:00
Olivier Gagnon
b1caea796a another link 2021-08-20 15:46:59 -04:00
Olivier Gagnon
2dfd19c9e0 rename, kinda add option for tabs vs space 2021-08-20 15:46:59 -04:00
Olivier Gagnon
0e24020796 Move monaco UI back where it belongs ish 2021-08-20 15:46:59 -04:00
Olivier Gagnon
ed62a3ebc2 deleted ace and monaco 2021-08-20 15:46:59 -04:00
Olivier Gagnon
258716388e focus works 2021-08-20 15:46:59 -04:00
Olivier Gagnon
73ec97db87 first pass at monaco. 2021-08-20 15:46:58 -04:00
Olivier Gagnon
567c5dc230 lint 2021-08-20 15:45:21 -04:00
Olivier Gagnon
980665b77c Fix job bug 2021-08-20 15:41:15 -04:00
Olivier Gagnon
dcddc0c2d5 fix a few things 2021-08-20 14:39:24 -04:00
Olivier Gagnon
6e1100750e script log boxes can now be dragged around and multiple of them can be on screen at once. 2021-08-19 22:22:21 -04:00
hydroflame
fea25249a8 Merge pull request #1062 from danielyxie/dev
v0.52.5
2021-08-19 16:38:26 -04:00
Olivier Gagnon
df457a0c6e v0.52.5 2021-08-19 16:37:59 -04:00
hydroflame
3826de72ef Merge pull request #1061 from danielyxie/dev
hotfix some blade netscript functions not working
2021-08-19 11:04:24 -04:00
Olivier Gagnon
ee3530d9b9 hotfix some blade netscript functions not working 2021-08-19 11:04:01 -04:00
hydroflame
5098ef6232 Merge pull request #1057 from danielyxie/dev
v0.52.4 - Bladeburner in React
2021-08-19 01:46:16 -04:00
Olivier Gagnon
1a1a43c1ce v0.52.4 2021-08-19 01:45:26 -04:00
Olivier Gagnon
d6b349b6ff dialogBoxCreate now uses the same logic as other popups, now all popup can be dismissed with escape. 2021-08-18 00:51:51 -04:00
Olivier Gagnon
5c92360310 convert a few variables to const. 2021-08-18 00:08:23 -04:00
hydroflame
1fbb971d6f Merge pull request #1049 from danielyxie/react-blade
React blade
2021-08-17 23:54:52 -04:00
Olivier Gagnon
fa78b3f421 Fix React list without keys, fix int miscalculation of blade skills. 2021-08-17 23:52:18 -04:00
Olivier Gagnon
9af9bf58b6 fix final bugs 2021-08-17 23:28:40 -04:00
Olivier Gagnon
99afb156fa Refactoring mostly done, still a few bugs and test to do. 2021-08-17 23:28:40 -04:00
Olivier Gagnon
8d550157bc Blade is fully converted to React but now it needs refactoring. 2021-08-17 23:28:40 -04:00
Olivier Gagnon
4865563f26 Almost done converting blade to react. 2021-08-17 23:28:40 -04:00
Olivier Gagnon
cc8de58cff More converting blade to react. 2021-08-17 23:28:40 -04:00
Olivier Gagnon
58ada6d128 converting the giant Bladeburner object. 2021-08-17 23:28:40 -04:00
Olivier Gagnon
ae6f95b59a The blade UI is fully converted to React, the business logic is left to do. 2021-08-17 23:28:40 -04:00
Olivier Gagnon
99d4f17cdb work on blade to react 2021-08-17 23:28:40 -04:00
Olivier Gagnon
33f0efd49c converting more blade to react 2021-08-17 23:28:40 -04:00
Olivier Gagnon
988ca37764 converting more blade to react/ts 2021-08-17 23:28:40 -04:00
Olivier Gagnon
0e9d7450c9 Converting bladeburner to react 2021-08-17 23:28:40 -04:00
hydroflame
27ee65f524 Merge pull request #1051 from danielyxie/dev
hotfix 0 territory being softlocked.
2021-08-17 17:47:58 -04:00
Olivier Gagnon
78cd319c21 hotfix 0 territory being softlocked. 2021-08-17 17:47:22 -04:00
hydroflame
1d0f193c34 Merge pull request #1050 from danielyxie/dev
hotfix blocked in Gang
2021-08-17 17:14:11 -04:00
Olivier Gagnon
7367167019 hotfix blocked in Gang 2021-08-17 17:13:32 -04:00
hydroflame
08908c87ea Merge pull request #1048 from danielyxie/dev
Hotfix weird bladeburner ui bug
2021-08-15 17:14:38 -04:00
Olivier Gagnon
392f164f8e Hotfix weird bladeburner ui bug 2021-08-15 17:14:05 -04:00
hydroflame
3957a517db Merge pull request #1047 from danielyxie/dev
v0.52.3 - 2021-07-15 Gangs were OP (hydroflame)
2021-08-15 16:26:52 -04:00
hydroflame
21daab32c1 Merge pull request #1044 from danielyxie/dev
v0.52.2
2021-08-15 02:15:03 -04:00
hydroflame
5e2ed7a79e Merge pull request #1042 from danielyxie/dev
hotfix revert tutorial instructing the player to make a script on n00…
2021-08-11 01:05:46 -04:00
hydroflame
d9e60ea124 Merge pull request #1039 from danielyxie/dev
rebuild with the version inside the game correctly udpated
2021-08-10 21:10:00 -04:00
hydroflame
2750eb293a Merge pull request #1038 from danielyxie/dev
v0.52.1
2021-08-10 21:04:05 -04:00
149 changed files with 17959 additions and 17351 deletions

View File

@@ -1,3 +0,0 @@
{
"presets": ["@babel/preset-react"]
}

4
.gitignore vendored
View File

@@ -1,3 +1,4 @@
.vscode
Changelog.txt
Netburner.txt
/doc/build
@@ -6,3 +7,6 @@ Netburner.txt
/test/*.map
/test/*.bundle.*
/test/*.css
# editor files
.vscode

9
babel.config.js Normal file
View File

@@ -0,0 +1,9 @@
const TEST = process.env.NODE_ENV === "test";
module.exports = {
"presets": [
"@babel/preset-react",
TEST && "@babel/preset-env",
TEST && "@babel/preset-typescript",
].filter(Boolean),
}

View File

@@ -13,7 +13,6 @@
justify-content: center;
width: 100%;
height: 100%;
overflow: auto;
background-color: rbga(var(--my-background-color), 0.4);
}
@@ -58,8 +57,7 @@
background-color: #000;
}
.dialog-box-container,
#log-box-container {
.dialog-box-container {
display: block;
position: absolute;
z-index: 10;
@@ -74,8 +72,54 @@
border: 5px solid var(--my-highlight-color);
}
.dialog-box-content,
#log-box-content {
.log-box-container {
display: flex;
flex-flow: column;
background-color: gray;
width: 50%;
position: absolute;
left: 50%;
top: 40%;
margin: -10% 0 0 -25%;
height: auto;
max-height: 50%;
z-index: 10;
background-color: var(--my-background-color);
border: 2px solid var(--my-highlight-color);
}
.log-box-header {
background-color: #333;
border: 1px solid var(--my-highlight-color);
display: flex;
flex: row nowrap;
align-items: center;
justify-content: space-between;
}
.log-box-log-container {
overflow-y: auto;
}
.log-box-button {
color: #aaa;
font-size: $defaultFontSize;
font-weight: bold;
padding: 2px;
margin: 6px;
border: 1px solid #fff;
background-color: #000;
}
.log-box-button:hover,
.log-box-button:focus {
color: var(--my-font-color);
text-decoration: none;
cursor: pointer;
}
.dialog-box-content {
z-index: 2;
background-color: var(--my-background-color);
padding: 10px;

View File

@@ -9,19 +9,6 @@
background-color: transparent;
}
#ace-editor {
margin: 10px;
height: 80%;
width: 100%;
margin-left: 6px;
padding-left: 6px;
padding-top: 6px;
padding-bottom: 6px;
border: 2px solid var(--my-highlight-color);
z-index: 1;
font-family: $fontFamily;
}
/* This temp element is used for auto adjusting filename field */
.tmp-element {
visibility: hidden;
@@ -47,7 +34,6 @@
#script-editor-filename-wrapper {
background-color: #555;
margin-left: 6px;
margin-right: 0;
padding-left: 6px;
width: 100%;
@@ -108,17 +94,14 @@
}
}
/* Specific overrides for Ace Editor */
.ace_line,
.ace_line * {
background-color: transparent;
margin: 0;
padding: 0;
.editor-options-container {
display: flex;
flex-flow: column;
}
.ace_text-input {
font-size: $defaultFontSize;
background-color: transparent;
}
/* Specified overrides for Code mirror Editor are defined in codemirror-override.scss */
.editor-options-line {
display: flex;
flex: row nowrap;
align-items: center;
justify-content: start;
}

View File

@@ -17,6 +17,8 @@ body {
p,
pre,
h2,
h3,
h4,
.text,
td {
color: var(--my-font-color);

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +1,2 @@
!function(n){function t(t){for(var e,i,f=t[0],c=t[1],l=t[2],p=0,s=[];p<f.length;p++)i=f[p],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([858,0]),o()}({795:function(n,t,o){},797:function(n,t,o){},799:function(n,t,o){},801:function(n,t,o){},803:function(n,t,o){},805:function(n,t,o){},807:function(n,t,o){},809:function(n,t,o){},811:function(n,t,o){},813:function(n,t,o){},815:function(n,t,o){},817:function(n,t,o){},819:function(n,t,o){},821:function(n,t,o){},823:function(n,t,o){},825:function(n,t,o){},827:function(n,t,o){},829:function(n,t,o){},831:function(n,t,o){},833:function(n,t,o){},835:function(n,t,o){},837:function(n,t,o){},839:function(n,t,o){},841:function(n,t,o){},843:function(n,t,o){},845:function(n,t,o){},847:function(n,t,o){},849:function(n,t,o){},851:function(n,t,o){},853:function(n,t,o){},855:function(n,t,o){},858:function(n,t,o){"use strict";o.r(t);o(857),o(855),o(853),o(851),o(849),o(847),o(845),o(843),o(841),o(839),o(837),o(835),o(833),o(831),o(829),o(827),o(825),o(823),o(821),o(819),o(817),o(815),o(813),o(811),o(809),o(807),o(805),o(803),o(801),o(799),o(797),o(795)}});
!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([866,0]),o()}({803:function(n,t,o){},805:function(n,t,o){},807:function(n,t,o){},809:function(n,t,o){},811:function(n,t,o){},813:function(n,t,o){},815:function(n,t,o){},817:function(n,t,o){},819:function(n,t,o){},821:function(n,t,o){},823:function(n,t,o){},825:function(n,t,o){},827:function(n,t,o){},829:function(n,t,o){},831:function(n,t,o){},833:function(n,t,o){},835:function(n,t,o){},837:function(n,t,o){},839:function(n,t,o){},841:function(n,t,o){},843:function(n,t,o){},845:function(n,t,o){},847:function(n,t,o){},849:function(n,t,o){},851:function(n,t,o){},853:function(n,t,o){},855:function(n,t,o){},857:function(n,t,o){},859:function(n,t,o){},861:function(n,t,o){},863:function(n,t,o){},866:function(n,t,o){"use strict";o.r(t);o(865),o(863),o(861),o(859),o(857),o(855),o(853),o(851),o(849),o(847),o(845),o(843),o(841),o(839),o(837),o(835),o(833),o(831),o(829),o(827),o(825),o(823),o(821),o(819),o(817),o(815),o(813),o(811),o(809),o(807),o(805),o(803)}});
//# sourceMappingURL=engineStyle.bundle.js.map

89
dist/engineStyle.css vendored
View File

@@ -26,6 +26,8 @@ body {
p,
pre,
h2,
h3,
h4,
.text,
td {
color: var(--my-font-color); }
@@ -551,6 +553,8 @@ body {
p,
pre,
h2,
h3,
h4,
.text,
td {
color: var(--my-font-color); }
@@ -1338,18 +1342,6 @@ button {
#script-editor-container {
background-color: transparent; }
#ace-editor {
margin: 10px;
height: 80%;
width: 100%;
margin-left: 6px;
padding-left: 6px;
padding-top: 6px;
padding-bottom: 6px;
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"; }
/* This temp element is used for auto adjusting filename field */
.tmp-element {
visibility: hidden;
@@ -1371,7 +1363,6 @@ button {
#script-editor-filename-wrapper {
background-color: #555;
margin-left: 6px;
margin-right: 0;
padding-left: 6px;
width: 100%;
@@ -1424,18 +1415,15 @@ button {
#script-editor-options-panel fieldset input {
margin: 2px; }
/* Specific overrides for Ace Editor */
.ace_line,
.ace_line * {
background-color: transparent;
margin: 0;
padding: 0; }
.editor-options-container {
display: flex;
flex-flow: column; }
.ace_text-input {
font-size: 16px;
background-color: transparent; }
/* Specified overrides for Code mirror Editor are defined in codemirror-override.scss */
.editor-options-line {
display: flex;
flex: row nowrap;
align-items: center;
justify-content: start; }
/* COLORS */
/* Attributes */
@@ -1967,6 +1955,8 @@ body {
p,
pre,
h2,
h3,
h4,
.text,
td {
color: var(--my-font-color); }
@@ -2366,7 +2356,6 @@ input[type="checkbox"] {
justify-content: center;
width: 100%;
height: 100%;
overflow: auto;
background-color: rbga(var(--my-background-color), 0.4); }
.popup-box-content {
@@ -2406,8 +2395,7 @@ input[type="checkbox"] {
border: 1px solid #fff;
background-color: #000; }
.dialog-box-container,
#log-box-container {
.dialog-box-container {
display: block;
position: absolute;
z-index: 10;
@@ -2421,13 +2409,52 @@ input[type="checkbox"] {
background-color: var(--my-background-color);
border: 5px solid var(--my-highlight-color); }
.dialog-box-content,
#log-box-content {
.log-box-container {
display: flex;
flex-flow: column;
background-color: gray;
width: 50%;
position: absolute;
left: 50%;
top: 40%;
margin: -10% 0 0 -25%;
height: auto;
max-height: 50%;
z-index: 10;
background-color: var(--my-background-color);
border: 2px solid var(--my-highlight-color); }
.log-box-header {
background-color: #333;
border: 1px solid var(--my-highlight-color);
display: flex;
flex: row nowrap;
align-items: center;
justify-content: space-between; }
.log-box-log-container {
overflow-y: auto; }
.log-box-button {
color: #aaa;
font-size: 16px;
font-weight: bold;
padding: 2px;
margin: 6px;
border: 1px solid #fff;
background-color: #000; }
.log-box-button:hover,
.log-box-button:focus {
color: var(--my-font-color);
text-decoration: none;
cursor: pointer; }
.dialog-box-content {
z-index: 2;
background-color: var(--my-background-color);
padding: 10px; }
.dialog-box-content p span,
#log-box-content p span {
.dialog-box-content p span {
padding: 0;
margin: 0; }

77
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

5536
dist/vendor.css vendored

File diff suppressed because one or more lines are too long

View File

@@ -3,6 +3,155 @@
Changelog
=========
v0.52.9 - 2021-07-27 Less lag! (hydroflame & community)
-------------------------------------------
** Active Scripts page **
* Now less laggy, has pagination.
** File diagnostic **
* Added a popup found under options that shows the files you own and how
large they are. This help find bugs and leftover massive logs files.
** Corporation **
* Added safeguard against a very specific bug that causes NaN money. I'm
still not sure what the root cause is but it should prevent corp from
breaking.
** Netscript **
* tprintf is a new function that doesn't print the filename.
** Misc. **
* Infiltration kills you if you try to automate it. (@threehams)
* Fix beautify button not working
* Added bladeburner_analysis_mult to getPlayer() (@brubsby)
* Fixed joining bladeburner via netscript functions. (@omuretsu)
* All bladeburner actions are click-to-copy
* nerf noodle bar
v0.52.8 - 2021-07-23 Fixing the previous patch tbh ROUND 2 (hydroflame)
-------------------------------------------
** Script editor **
* Correctly reloads old script when clicking "Script Editor"
* No longer jumps to the end of the text for no reason.
** Hash upgrades **
* Fixed an issue where the default option would say ecorp but was really
foodnstuff
** Misc. **
* The "Delete all active script" button under the options has a clearer
description.
* Removed some debug console.log
* nerf noodle bar
v0.52.7 - 2021-07-21 Fixing the previous patch tbh (hydroflame)
-------------------------------------------
** Netscript **
* API BREAKING CHANGE: getActionEstimatedSuccessChance now returns a pair of
value to reflect the UI changes. I'm very sorry.
** Bladeburner **
* General actions now display time required.
* Recruitment now displays success chance.
* All other success chance now display a range instead of a single value
The real value is guaranteed to be within that range.
** Misc. **
* Fix tutorial not working after Monaco upate
* Fix logbox logs not taking up the whole logbox
* Fix script editor shortcut (ctrl+b)
* Fix Corporation popup appearing in the wrong order, hiding one of them
* Fix error when loading Corp
* Fix logbox dragging (smoother now)
* Fix logbox name collision
* Fix logbox allowing to open the same box multiple times
* Fix netscript write.
* nerf noodle bar
v0.52.6 - 2021-07-21 Logboxes and VS-code (hydroflame)
-------------------------------------------
** Text Editor **
* Ace and Codemirror have been removed in favor of monaco (web version of
vs-code). The options are a bit lackluster but more will be added as
feedback comes.
** Log boxes **
* Multiple log boxes can be opened at once. They can be moved around the
screen. (but the movement behavior is a bit weird.)
** Misc. **
* Job promotion now correctly updates the UI.
* Milestones now call the faction CyberSec instead of CSEC
* Can no longer create file that break the filesystem.
* Remove dollar sign in blade contract UI element
* nerf noodle bar
v0.52.5 - 2021-07-19 CPU cores are useful!? (hydroflame)
-------------------------------------------
** Terminal **
* When executing 'run SCRIPT' any script can now add '--tail' to
automatically bring up the logs.
** Netscript **
* The 'flags' function now works with single letter flags but they only take
one dash.
* Fix several broken bladeburner netscript functions.
* Fix gang.getMemberInformation returning inconsistent data after the gang
rework.
** CPU Cores **
* CPU Cores on the home computer now provide a bonus to grow() money gain
and makes weaken lower more security. Only for scripts running on 'home'
** Misc. **
* Fix weird scrolling in the new Bladeburner React console.
* nerf noodle bar
v0.52.4 - 2021-07-19 Bladeburner in React (hydroflame)
-------------------------------------------
** Bladeburner **
* The entire UI was rebuild in React. It should be more responsive
** Hacknet **
* Displays how many time each hash upgrade was bought.
* Displays cummulative effect of the upgrade.
* Removed "Close" button from hash upgrade menu.
** Misc. **
* More popup/modals have dark background, can be dismissed by clicking
outside, or by pressing escape.
* Small reword in the guide.
* Fix several typos in the bladeburner documentation.
* Linting (no one cares except the dev)
* nerf noodle bar
v0.52.3 - 2021-07-15 Gangs were OP (hydroflame)
-------------------------------------------
@@ -24,6 +173,7 @@ v0.52.3 - 2021-07-15 Gangs were OP (hydroflame)
* Fixed an issue where you could join the same faction twice via script and
UI simultaneously.
* Factions list screen converted to React.
* nerf noodle bar
v0.52.2 - 2021-07-15 Oh yeah, BN11 is a thing (drunk hydroflame tbh)
-------------------------------------------
@@ -39,6 +189,10 @@ v0.52.2 - 2021-07-15 Oh yeah, BN11 is a thing (drunk hydroflame tbh)
programs.
* Augmentation descriptions are now more concise and consistent.
** Misc. **
* nerf noodle bar
v0.52.1 - 2021-07-10 bugfixing (hydroflame & community)
-------------------------------------------
@@ -58,6 +212,7 @@ v0.52.1 - 2021-07-10 bugfixing (hydroflame & community)
* Plenty of typo/description fixed (@MageKing17)
* Cleanup description of singularity function on readthedocs (@PurePandemonium)
* Fix bug when autolinking a server while backdooring (@schroederIT)
* nerf noodle bar
v0.52.0 - 2021-06-13 Infiltration 2.0 (hydroflame & community)
--------------------------------------------------------------
@@ -107,6 +262,7 @@ v0.52.0 - 2021-06-13 Infiltration 2.0 (hydroflame & community)
* Fixed an issue where reputation could be transfered to new jobs when unfocused.
* Empty stack traces should no longer appear.
* Purchasing anything with Infinity money doesn't result in NaN.
* nerf noodle bar
v0.51.10 - 2021-05-31 Focus Mark, Focus! (hydroflame)
-----------------------------------------------------
@@ -150,6 +306,7 @@ v0.51.10 - 2021-05-31 Focus Mark, Focus! (hydroflame)
* Very large number will no longer appear as "$NaNt"
* Hash capacity now displays in the "big number" format.
* nerf noodle bar
v0.51.9 - 2021-05-17 offline progress and exports! (hydroflame & community)
---------------------------------------------------------------
@@ -203,6 +360,7 @@ v0.51.9 - 2021-05-17 offline progress and exports! (hydroflame & community)
* Updated several dependencies (big who cares, I know)
* ls no longer prints lingering newline.
* Money earned/spent by sleeves is now tracked under Character>Money
* nerf noodle bar
v0.51.8 - 2021-05-07 It was there all along (hydroflame & community)
@@ -260,6 +418,7 @@ v0.51.8 - 2021-05-07 It was there all along (hydroflame & community)
* Fix infiltration number formatting.
* script income transfers to parent on death. This helps keep track of
income for scripts that spawn short lived scripts.
* nerf noodle bar
v0.51.7 - 2021-04-28 n00dles (hydroflame & community)
-----------------------------------------
@@ -317,6 +476,7 @@ v0.51.7 - 2021-04-28 n00dles (hydroflame & community)
* Money amount under 1000 dont display 3 decimal anymore.
* Fix nextSourceFile flag miscalculation on the bitverse (for Bn12)
* Faction invite text says "Decide later"/"Join!" instead of "No"/"Yes"
* nerf noodle bar
v0.51.6 - 2021-04-28 Backdoor! (hydroflame & community)
@@ -367,6 +527,7 @@ v0.51.6 - 2021-04-28 Backdoor! (hydroflame & community)
* so many documentation and typos fixes (@Pimgd)
* A corruption visual effect has been added to location with servers that
have backdoor installed. (@dewint)
* nerf noodle bar
v0.51.5 - 2021-04-20 Flags! (hydroflame)
@@ -391,6 +552,7 @@ v0.51.5 - 2021-04-20 Flags! (hydroflame)
* Souce-File typo fix
* Fix 'while you were away' screen.
* Bladeburner team size can no longer be set to negative amounts.
* nerf noodle bar
v0.51.4 - 2021-04-19 Manual hacking is fun (hydroflame)
-------------------------------------------------------
@@ -426,6 +588,7 @@ v0.51.4 - 2021-04-19 Manual hacking is fun (hydroflame)
* The text editor now remembers the location of your cursor and restores it.
* skills are recalculated instantly.
* Fix typo in Operation Zero description.
* nerf noodle bar
v0.51.3 - 2021-04-16 Y'all broke it on the first day (hydroflame)
-----------------------------------------------------------------
@@ -467,6 +630,7 @@ v0.51.3 - 2021-04-16 Y'all broke it on the first day (hydroflame)
* Hacknet node names is easier to handle for screen readers.
* Money spent on classes is now tracked independently of work money.
* running coding contract from the terminal will display its name.
* nerf noodle bar
v0.51.2 - 2021-04-09 Vegas, Baby! (hydroflame)
----------------------------------------------
@@ -481,6 +645,7 @@ v0.51.2 - 2021-04-09 Vegas, Baby! (hydroflame)
* Link to discord added under options
* 'getMemberInformation' doc updated, oops
* tech vendor now handle max ram and cores.
* nerf noodle bar
v0.51.1 - 2021-04-06 Bugfixes because the author of the last patch sucks (it's hydroflame)
------------------------------------------------------------------------------------------
@@ -512,6 +677,7 @@ v0.51.1 - 2021-04-06 Bugfixes because the author of the last patch sucks (it's h
* 'fl1ght.exe' will no longer suggest the combat path. Related faction
requirements unchanged.
* nerf noodle bar
v0.51.0 - 2021-03-31 Formulas (hydroflame)
------------------------------------------
@@ -538,6 +704,10 @@ v0.51.0 - 2021-03-31 Formulas (hydroflame)
* Certain UI elements are now 'click-to-copy'
** Misc. **
* nerf noodle bar
v0.50.2 - 2021-03-25 Everyone asked for this one. (hydroflame)
--------------------------------------------------------------
@@ -555,6 +725,7 @@ v0.50.2 - 2021-03-25 Everyone asked for this one. (hydroflame)
* New shortcut, Alt + b, brings you to bladeburner
* New shortcut, Alt + g, brings you to gang
* nerf noodle bar
v0.50.1 - 2021-03-22 (hydroflame)
---------------------------------
@@ -577,6 +748,7 @@ v0.50.1 - 2021-03-22 (hydroflame)
**Misc.**
* Minor spacing in stats tables.
* nerf noodle bar
v0.50.0 - 2021-03-20 Intelligence (hydroflame)
----------------------------------------------
@@ -597,6 +769,7 @@ v0.50.0 - 2021-03-20 Intelligence (hydroflame)
* number formatting
* remove wiki button in Hacking Missions.
* Fix NaN displayed when very very large numbers are reached.
* nerf noodle bar
v0.49.2 - 2021-03-13 (hydroflame)
---------------------------------
@@ -634,6 +807,7 @@ v0.49.2 - 2021-03-13 (hydroflame)
**Misc.**
* Fix issue where the effective stats under Character>Stats were being calculated.
* nerf noodle bar
v0.49.0 - 2021-03-11 Source-File -1 (hydroflame)
------------------------------------------------
@@ -661,6 +835,7 @@ v0.49.0 - 2021-03-11 Source-File -1 (hydroflame)
* Minor formatting under Hacking>Active Scripts
* option menu colors now match the rest of the game, kinda.
* nerf noodle bar
v0.48.0 - ASCII - 2021-03-07 (hydroflame)
@@ -708,6 +883,7 @@ v0.48.0 - ASCII - 2021-03-07 (hydroflame)
has bought but not installed
* Character>Factions has a badge indicating how many factions have pending
invites.
* nerf noodle bar
v0.47.2 - 7/15/2019
-------------------

View File

@@ -66,7 +66,7 @@ documentation_title = '{0} Documentation'.format(project)
# The short X.Y version.
version = '0.52'
# The full version, including alpha/beta/rc tags.
release = '0.52.3'
release = '0.52.9'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View File

@@ -636,7 +636,7 @@ This tells me that I can reach :code:`CSEC` by going through :code:`iron-gym`::
from CSEC once you hit 50 hacking, you cannot actually pass their test
until your hacking is high enough to install a backdoor on their server.
After you are connected to the :code:`CSEC` server, you can hack it. Note that this
After you are connected to the :code:`CSEC` server, you can backdoor it. Note that this
server requires one open port in order to gain root access. We can open the SSH port
using the :code:`BruteSSH.exe` program we created earlier. In |Terminal|::

View File

@@ -0,0 +1,17 @@
tprint() Netscript Function
===========================
.. js:function:: tprintf(format, args...)
:RAM cost: 0 GB
:param format: Format of the string to be printed.
:param args: Values to be formatted
Prints a raw formatted string to the terminal.
Example:
.. code-block:: javascript
tprintf("Hello world!"); // Prints "Hello world!" to the terminal.
tprintf("Hello %s", "world!"); // Prints "Hello world!" to the terminal.

View File

@@ -12,7 +12,7 @@ getActionCountRemaining() Netscript Function
This function will return 'Infinity' for actions such as 'Training' and
'Field Analysis'.
This function will return 1 for BlackOps not yet completed regardless of
wether the player has the required rank to attempt the mission or not.
whether the player has the required rank to attempt the mission or not.
Example:

View File

@@ -12,4 +12,4 @@ getActionCurrentLevel() Netscript Function
.. code-block:: javascript
bladeburner.getActionCountRemaining("Contracts", "Tracking"); // returns: 7
bladeburner.getActionCurrentLevel("Contracts", "Tracking"); // returns: 9

View File

@@ -6,10 +6,10 @@ getActionEstimatedSuccessChance() Netscript Function
:RAM cost: 4 GB
:param string type: Type of action. See :ref:`bladeburner_action_types`
:param string name: Name of action. Must be an exact match
:returns: Estimated success chance in decimal
:returns: Array of 2 number, lower and upper bound of the action chance.
Example:
.. code-block:: javascript
bladeburner.getActionEstimatedSuccessChance("Contracts", "Tracking"); // returns: .3
bladeburner.getActionEstimatedSuccessChance("Contracts", "Tracking"); // returns: [.3, .6]

View File

@@ -1,12 +1,13 @@
growPercent() Netscript Function
=================================
.. js:function:: growPercent(server, threads, player)
.. js:function:: growPercent(server, threads, player, cores)
:RAM cost: 0 GB
:param server server: The server that receives the growth.
:param number threads: The number of thread that would be used.
:param player player: The player.
:param number cores: The amount of cores on the host computer.
:returns: The amount the server's money would be multiplied by with these
parameters.

View File

@@ -8,7 +8,7 @@ hashGainRate() Netscript Function
:param number ramUsed: ram used on the server.
:param number maxRam: max ram of the server.
:param number core: cores of the server.
:returns: Money per second that a server with those stats would gain per second.
:returns: Hashes per second that a server with those stats would gain.
If you are not in BitNode-5, then you must have Source-File 5-1 in order to
use this function. In addition, if you are not in BitNode-9, then you must

View File

@@ -13,30 +13,42 @@ getMemberInformation() Netscript Function
name: Name of this member.
task: Name of currently assigned task.
earnedRespect: Total amount of respect earned by this member.
hack: Hacking stat
str: Strength stat
def: Defense stat
dex: Dexterity stat
agi: Agility stat
cha: Charisma stat
hack_exp: Hacking experience
str_exp: Strength experience
def_exp: Defense experience
dex_exp: Dexterity experience
agi_exp: Agility experience
cha_exp: Charisma experience
hack_mult: Hacking multiplier from equipment. Decimal form
str_mult: Strength multiplier from equipment. Decimal form
def_mult: Defense multiplier from equipment. Decimal form
dex_mult: Dexterity multiplier from equipment. Decimal form
agi_mult: Agility multiplier from equipment. Decimal form
cha_mult: Charisma multiplier from equipment. Decimal form
hack_asc_mult: Hacking multiplier from ascension. Decimal form
str_asc_mult: Strength multiplier from ascension. Decimal form
def_asc_mult: Defense multiplier from ascension. Decimal form
dex_asc_mult: Dexterity multiplier from ascension. Decimal form
agi_asc_mult: Agility multiplier from ascension. Decimal form
cha_asc_mult: Charisma multiplier from ascension. Decimal form
hack_asc_points: Hacking ascension points.
str_asc_points: Strength ascension points.
def_asc_points: Defense ascension points.
dex_asc_points: Dexterity ascension points.
agi_asc_points: Agility ascension points.
cha_asc_points: Charisma ascension points.
upgrades: Array of names of all owned Non-Augmentation Equipment
augmentations: Array of names of all owned Augmentations
}

View File

@@ -19,6 +19,7 @@ This includes information such as function signatures, what they do, and their r
sleep() <basicfunctions/sleep>
print() <basicfunctions/print>
tprint() <basicfunctions/tprint>
tprintf() <basicfunctions/tprint>
clearLog() <basicfunctions/clearLog>
disableLog() <basicfunctions/disableLog>
enableLog() <basicfunctions/enableLog>

View File

@@ -123,11 +123,8 @@
<input id="script-editor-filename" type="text" maxlength="100" tabindex="1"/>
</div>
<div id="ace-editor"></div>
<form id="codemirror-form-wrapper"><textarea id="codemirror-editor"></textarea></form>
<div id="codemirror-vim-command-display-wrapper">
Key Buffer: <span id="codemirror-vim-command-display"></span>
</div>
<div id="monaco-editor"></div>
<div id="script-editor-buttons-wrapper"></div> <!-- Buttons below script editor -->
</div> <!-- End wrapper -->
@@ -173,6 +170,14 @@
<fieldset id="script-editor-option-flex4-fieldset"></fieldset>
</div> <!-- End script editor options panel -->
<!-- TODO(hydroflame): remove this once Monaco is implemented -->
<div id="ace-editor" style="display: none"></div>
<form id="codemirror-form-wrapper" style="display: none"><textarea id="codemirror-editor"></textarea></form>
<div id="codemirror-vim-command-display-wrapper" style="display: none">
Key Buffer: <span id="codemirror-vim-command-display"></span>
</div>
</div>
<!-- Terminal page -->
@@ -230,8 +235,10 @@
<div id="augmentations-container" class="generic-menupage-container"></div>
<!-- Milestones content -->
<div id="milestones-container" class="generic-menupage-container">
</div>
<div id="milestones-container" class="generic-menupage-container"></div>
<!-- Bladeburner -->
<div id="bladeburner-container" class="generic-menupage-container"></div>
<!-- Tutorial content -->
<div id="tutorial-container" class="generic-menupage-container">
@@ -275,16 +282,6 @@
<!-- React Component -->
</div>
<!-- Log Box -->
<div id="log-box-container">
<div id="log-box-content">
<button id="log-box-close" class="popup-box-button"> Close </button>
<button id="log-box-kill-script" class="popup-box-button">Kill Script</button>
<p id="log-box-text-header"> </p>
<p id="log-box-text"> </p>
</div>
</div>
<!-- Generic Yes/No Pop Up box -->
<div id="yes-no-box-container" class="popup-box-container">
<div id="yes-no-box-content" class="popup-box-content">
@@ -584,10 +581,12 @@
Copy Save data to Clipboard
</button>
<button id="debug-delete-scripts-link" class="a-link-button tooltip">
Delete all active scripts
Force kill all active scripts
<span class="tooltiptextleft">
Forcefully kill all active running scripts, in case there is a bug or some unexpected issue with the game. After
using this, save the game and then reload the page.
using this, save the game and then reload the page. This is different then normal kill in that normal kill
will tell the script to shut down while force kill just removes the references to it (and it should crash on it's own).
This will not remove the files on your computer. Just forcefully kill all running instance of all scripts.
</span>
</button>
<button id="debug-soft-reset" class="a-link-button tooltip">
@@ -596,6 +595,14 @@
Perform a soft reset. Resets everything as if you had just purchased an Augmentation.
</span>
</button>
<button id="debug-files" class="a-link-button tooltip">
Diagnose files
<span class="tooltiptextleft">
If your save file is extremely big you can use this button
to view a map of all the files on every server. Be careful
there might be spoilers.
</span>
</button>
</div>
</div>
</div>

9
jest.config.js Normal file
View File

@@ -0,0 +1,9 @@
module.exports = {
setupFiles: ["./jest.setup.js"],
moduleFileExtensions: ["ts", "tsx", "js", "jsx"],
transform: {
"^.+\\.(js|jsx|ts|tsx)$": "babel-jest",
},
// testMatch: ["**/?(*.)+(test).[jt]s?(x)"],
testEnvironment: "jsdom",
};

2
jest.setup.js Normal file
View File

@@ -0,0 +1,2 @@
import "regenerator-runtime/runtime";
global.$ = require("jquery");

16338
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,8 @@
},
"dependencies": {
"@material-ui/core": "^4.11.3",
"@monaco-editor/react": "^4.2.2",
"@types/js-beautify": "^1.13.2",
"@types/numeral": "0.0.25",
"@types/react": "^16.8.6",
"@types/react-dom": "^16.8.2",
@@ -33,6 +35,7 @@
"loader-runner": "^2.3.0",
"loader-utils": "^1.1.0",
"memory-fs": "~0.4.1",
"monaco-editor": "^0.27.0",
"node-sass": "^6.0.1",
"normalize.css": "^8.0.0",
"numeral": "2.0.6",
@@ -47,17 +50,18 @@
"description": "A cyberpunk-themed incremental game",
"devDependencies": {
"@babel/core": "^7.3.4",
"@babel/preset-env": "^7.15.0",
"@babel/preset-react": "^7.0.0",
"@types/chai": "^4.1.7",
"@babel/preset-typescript": "^7.15.0",
"@types/jest": "^27.0.1",
"@types/lodash": "^4.14.168",
"@types/mocha": "^5.2.7",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
"babel-jest": "^27.0.6",
"babel-loader": "^8.0.5",
"beautify-lint": "^1.0.3",
"benchmark": "^2.1.1",
"bundle-loader": "~0.5.0",
"chai": "^4.2.0",
"css-loader": "^0.28.11",
"es6-promise-polyfill": "^1.1.1",
"eslint": "^7.24.0",
@@ -66,6 +70,7 @@
"html-webpack-plugin": "^3.2.0",
"i18n-webpack-plugin": "^1.0.0",
"istanbul": "^0.4.5",
"jest": "^27.0.6",
"js-beautify": "^1.5.10",
"jsdom": "^15.0.0",
"jsdom-global": "^3.0.2",
@@ -75,10 +80,9 @@
"lodash": "^4.17.21",
"mini-css-extract-plugin": "^0.4.1",
"mkdirp": "^0.5.1",
"mocha": "^6.1.4",
"mochapack": "^1.1.1",
"null-loader": "^1.0.0",
"raw-loader": "~0.5.0",
"regenerator-runtime": "^0.13.9",
"sass-loader": "^7.0.3",
"script-loader": "~0.7.0",
"should": "^11.1.1",
@@ -121,10 +125,10 @@
"lint:jsts": "eslint --fix '*.{js,jsx,ts,tsx}' './src/**/*.{js,jsx,ts,tsx}' './test/**/*.{js,jsx,ts,tsx}' './utils/**/*.{js,jsx,ts,tsx}'",
"lint:style": "stylelint --fix ./css/*",
"preinstall": "node ./scripts/engines-check.js",
"test": "mochapack --webpack-config webpack.config-test.js -r jsdom-global/register ./test/index.js",
"test:container": "mochapack --webpack-config webpack.config-test.js --slow 2000 --timeout 10000 -r jsdom-global/register ./test/index.js",
"test": "jest",
"test:watch": "jest --watch",
"watch": "webpack --watch --mode production",
"watch:dev": "webpack --watch --mode development"
},
"version": "0.52.3"
"version": "0.52.9"
}

View File

@@ -55,7 +55,7 @@ interface IConstructorParams {
}
function generateStatsDescription(mults: IMap<number>, programs?: string[], startingMoney?: number): JSX.Element {
const f = (x: number, decimals: number = 0) => {
const f = (x: number, decimals = 0): string => {
// look, I don't know how to make a "smart decimals"
// todo, make it smarter
if(x === 1.0777-1) return "7.77%";

View File

@@ -18,8 +18,6 @@ import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
import { dialogBoxCreate } from "../../utils/DialogBox";
import { clearObject } from "../../utils/helpers/clearObject";
import { Money } from "../ui/React/Money";
import { CorruptableText } from "../ui/React/CorruptableText";
import { WHRNG } from "../Casino/RNG";

File diff suppressed because it is too large Load Diff

View File

@@ -194,6 +194,23 @@ export class Action implements IAction {
return 1;
}
getEstSuccessChance(inst: IBladeburner): number[] {
function clamp(x: number): number {
return Math.max(0, Math.min(x, 1));
}
const est = this.getSuccessChance(inst, {est: true});
const real = this.getSuccessChance(inst);
const diff = Math.abs(real-est);
let low = real-diff;
let high = real+diff;
const city = inst.getCurrentCity();
const r = city.pop / city.popEst;
if(r < 1) low *= r;
else high *= r;
return [clamp(low), clamp(high)];
}
/**
* @inst - Bladeburner Object
* @params - options:

View File

@@ -0,0 +1,28 @@
import { IActionIdentifier } from "./IActionIdentifier";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
interface IParams {
name?: string;
type?: number;
}
export class ActionIdentifier implements IActionIdentifier {
name = "";
type = -1;
constructor(params: IParams = {}) {
if (params.name) this.name = params.name;
if (params.type) this.type = params.type;
}
toJSON(): any {
return Generic_toJSON("ActionIdentifier", this);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
static fromJSON(value: any): ActionIdentifier {
return Generic_fromJSON(ActionIdentifier, value.data);
}
}
Reviver.constructors.ActionIdentifier = ActionIdentifier;

File diff suppressed because it is too large Load Diff

View File

@@ -4,14 +4,14 @@ import { getRandomInt } from "../../utils/helpers/getRandomInt";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
import { addOffset } from "../../utils/helpers/addOffset";
export class ChangePopulationByCountParams {
estChange = 0;
estOffset = 0;
interface IChangePopulationByCountParams {
estChange: number;
estOffset: number;
}
export class ChangePopulationByPercentageParams {
nonZero = false;
changeEstEqually = false;
interface IChangePopulationByPercentageParams {
nonZero: boolean;
changeEstEqually: boolean;
}
export class City {
@@ -113,7 +113,7 @@ export class City {
* estChange(int): How much the estimate should change by
* estOffset(int): Add offset to estimate (offset by percentage)
*/
changePopulationByCount(n: number, params: ChangePopulationByCountParams=new ChangePopulationByCountParams()): void {
changePopulationByCount(n: number, params: IChangePopulationByCountParams = {estChange: 0, estOffset: 0}): void {
if (isNaN(n)) {throw new Error("NaN passed into City.changePopulationByCount()");}
this.pop += n;
if (params.estChange && !isNaN(params.estChange)) {this.popEst += params.estChange;}
@@ -129,7 +129,7 @@ export class City {
* changeEstEqually(bool) - Change the population estimate by an equal amount
* nonZero (bool) - Set to true to ensure that population always changes by at least 1
*/
changePopulationByPercentage(p: number, params: ChangePopulationByPercentageParams=new ChangePopulationByPercentageParams()): number {
changePopulationByPercentage(p: number, params: IChangePopulationByPercentageParams={nonZero: false, changeEstEqually: false}): number {
if (isNaN(p)) {throw new Error("NaN passed into City.changePopulationByPercentage()");}
if (p === 0) {return 0;}
let change = Math.round(this.pop * (p/100));

View File

@@ -64,6 +64,7 @@ export interface IAction {
getActionTypeSkillSuccessBonus(inst: IBladeburner): number;
getChaosCompetencePenalty(inst: IBladeburner, params: ISuccessChanceParams): number;
getChaosDifficultyBonus(inst: IBladeburner): number;
getEstSuccessChance(inst: IBladeburner): number[];
getSuccessChance(inst: IBladeburner, params: ISuccessChanceParams): number;
getSuccessesNeededForNextLevel(baseSuccessesPerLevel: number): number;
setMaxLevel(baseSuccessesPerLevel: number): void;

View File

@@ -1,4 +1,4 @@
export interface IActionIdentifier {
name: string;
type: string;
type: number;
}

View File

@@ -1,20 +1,33 @@
import { IActionIdentifier } from "./IActionIdentifier";
import { City } from "./City";
import { Skill } from "./Skill";
import { IAction } from "./IAction";
import { IPlayer } from "../PersonObjects/IPlayer";
import { WorkerScript } from "../Netscript/WorkerScript";
export interface IBladeburner {
numHosp: number;
moneyLost: number;
rank: number;
maxRank: number;
skillPoints: number;
totalSkillPoints: number;
teamSize: number;
teamLost: number;
hpLost: number;
storedCycles: number;
randomEventCounter: number;
actionTimeToComplete: number;
actionTimeCurrent: number;
actionTimeOverflow: number;
action: IActionIdentifier;
cities: any;
city: string;
skills: any;
@@ -27,13 +40,66 @@ export interface IBladeburner {
blackops: any;
logging: any;
automateEnabled: boolean;
automateActionHigh: number;
automateActionHigh: IActionIdentifier;
automateThreshHigh: number;
automateActionLow: number;
automateActionLow: IActionIdentifier;
automateThreshLow: number;
consoleHistory: string[];
consoleLogs: string[];
getCurrentCity(): City;
calculateStaminaPenalty(): number;
startAction(player: IPlayer, action: IActionIdentifier): void;
upgradeSkill(skill: Skill): void;
executeConsoleCommands(player: IPlayer, command: string): void;
postToConsole(input: string, saveToLogs?: boolean): void;
log(input: string): void;
resetAction(): void;
clearConsole(): void;
prestige(): void;
storeCycles(numCycles?: number): void;
getTypeAndNameFromActionId(actionId: IActionIdentifier): {type: string; name: string};
getContractNamesNetscriptFn(): string[];
getOperationNamesNetscriptFn(): string[];
getBlackOpNamesNetscriptFn(): string[];
getGeneralActionNamesNetscriptFn(): string[];
getSkillNamesNetscriptFn(): string[];
startActionNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): boolean;
getActionTimeNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): number;
getActionEstimatedSuccessChanceNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): number[];
getActionCountRemainingNetscriptFn(type: string, name: string, workerScript: WorkerScript): number;
getSkillLevelNetscriptFn(skillName: string, workerScript: WorkerScript): number;
getSkillUpgradeCostNetscriptFn(skillName: string, workerScript: WorkerScript): number;
upgradeSkillNetscriptFn(skillName: string, workerScript: WorkerScript): boolean;
getTeamSizeNetscriptFn(type: string, name: string, workerScript: WorkerScript): number;
setTeamSizeNetscriptFn(type: string, name: string, size: number, workerScript: WorkerScript): number;
joinBladeburnerFactionNetscriptFn(workerScript: WorkerScript): boolean;
getActionIdFromTypeAndName(type: string, name: string): IActionIdentifier | null;
executeStartConsoleCommand(player: IPlayer, args: string[]): void;
executeSkillConsoleCommand(args: string[]): void;
executeLogConsoleCommand(args: string[]): void;
executeHelpConsoleCommand(args: string[]): void;
executeAutomateConsoleCommand(args: string[]): void;
parseCommandArguments(command: string): string[];
executeConsoleCommand(player: IPlayer, command: string): void;
triggerMigration(sourceCityName: string): void;
triggerPotentialMigration(sourceCityName: string, chance: number): void;
randomEvent(): void;
gainActionStats(player: IPlayer, action: IAction, success: boolean): void;
getDiplomacyEffectiveness(player: IPlayer): number;
getRecruitmentSuccessChance(player: IPlayer): number;
getRecruitmentTime(player: IPlayer): number;
resetSkillMultipliers(): void;
updateSkillMultipliers(): void;
completeOperation(success: boolean): void;
getActionObject(actionId: IActionIdentifier): IAction | null;
completeContract(success: boolean): void;
completeAction(player: IPlayer): void;
changeRank(player: IPlayer, change: number): void;
processAction(player: IPlayer, seconds: number): void;
calculateStaminaGainPerSecond(player: IPlayer): number;
calculateMaxStamina(player: IPlayer): void;
create(): void;
process(player: IPlayer): void;
}

View File

@@ -103,5 +103,28 @@ export class Skill {
calculateCost(currentLevel: number): number {
return Math.floor((this.baseCost + (currentLevel * this.costInc)) * BitNodeMultipliers.BladeburnerSkillCost);
}
getMultiplier(name: string): number {
if(name === "successChanceAll") return this.successChanceAll;
if(name === "successChanceStealth") return this.successChanceStealth;
if(name === "successChanceKill") return this.successChanceKill;
if(name === "successChanceContract") return this.successChanceContract;
if(name === "successChanceOperation") return this.successChanceOperation;
if(name === "successChanceEstimate") return this.successChanceEstimate;
if(name === "actionTime") return this.actionTime;
if(name === "effHack") return this.effHack;
if(name === "effStr") return this.effStr;
if(name === "effDef") return this.effDef;
if(name === "effDex") return this.effDex;
if(name === "effAgi") return this.effAgi;
if(name === "effCha") return this.effCha;
if(name === "stamina") return this.stamina;
if(name === "money") return this.money;
if(name === "expGain") return this.expGain;
return 0;
}
}

View File

@@ -1,5 +1,18 @@
// Action Identifier enum
export const ActionTypes = Object.freeze({
export const ActionTypes: {
[key: string]: number;
"Idle": number;
"Contract": number;
"Operation": number;
"BlackOp": number;
"BlackOperation": number;
"Training": number;
"Recruitment": number;
"FieldAnalysis": number;
"Field Analysis": number;
"Diplomacy": number;
"Hyperbolic Regeneration Chamber": number;
} = {
"Idle": 1,
"Contract": 2,
"Operation": 3,
@@ -11,4 +24,4 @@ export const ActionTypes = Object.freeze({
"Field Analysis": 7,
"Diplomacy": 8,
"Hyperbolic Regeneration Chamber": 9,
});
};

View File

@@ -1,4 +1,5 @@
export const ConsoleHelpText: {
[key: string]: string[];
helpList: string[];
automate: string[];
clear: string[];

View File

@@ -0,0 +1,14 @@
import * as React from "react";
export const stealthIcon = <svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="0 0 166 132" style={{fill:'#adff2f'}}>
<g>
<path d="M132.658-0.18l-24.321,24.321c-7.915-2.71-16.342-4.392-25.087-4.392c-45.84,0-83,46-83,46 s14.1,17.44,35.635,30.844L12.32,120.158l12.021,12.021L144.68,11.841L132.658-0.18z M52.033,80.445 c-2.104-4.458-3.283-9.438-3.283-14.695c0-19.054,15.446-34.5,34.5-34.5c5.258,0,10.237,1.179,14.695,3.284L52.033,80.445z" />
<path d="M134.865,37.656l-18.482,18.482c0.884,3.052,1.367,6.275,1.367,9.612c0,19.055-15.446,34.5-34.5,34.5 c-3.337,0-6.56-0.483-9.611-1.367l-10.124,10.124c6.326,1.725,12.934,2.743,19.735,2.743c45.84,0,83-46,83-46 S153.987,50.575,134.865,37.656z" />
</g>
</svg>
export const killIcon = <svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="-22 0 511 511.99561" style={{fill:'#adff2f'}}>
<path d="m.496094 466.242188 39.902344-39.902344 45.753906 45.753906-39.898438 39.902344zm0 0" />
<path d="m468.421875 89.832031-1.675781-89.832031-300.265625 300.265625 45.753906 45.753906zm0 0" />
<path d="m95.210938 316.785156 16.84375 16.847656h.003906l83.65625 83.65625 22.753906-22.753906-100.503906-100.503906zm0 0" />
<path d="m101.445312 365.300781-39.902343 39.902344 45.753906 45.753906 39.902344-39.902343-39.90625-39.902344zm0 0" />
</svg>

View File

@@ -0,0 +1,49 @@
import React, { useState, useEffect } from "react";
import { GeneralActionPage } from "./GeneralActionPage";
import { ContractPage } from "./ContractPage";
import { OperationPage } from "./OperationPage";
import { BlackOpPage } from "./BlackOpPage";
import { SkillPage } from "./SkillPage";
import { stealthIcon, killIcon } from "../data/Icons";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
}
export function AllPages(props: IProps): React.ReactElement {
const [page, setPage] = useState('General');
const setRerender = useState(false)[1];
useEffect(() => {
const id = setInterval(() => setRerender(old => !old), 1000);
return () => clearInterval(id);
}, []);
function Header(props: {name: string}): React.ReactElement {
return (<a
onClick={()=>setPage(props.name)}
className={page !== props.name ?
"bladeburner-nav-button" :
"bladeburner-nav-button-inactive"}>
{props.name}
</a>);
}
return (<>
<Header name={'General'} />
<Header name={'Contracts'} />
<Header name={'Operations'} />
<Header name={'BlackOps'} />
<Header name={'Skills'} />
<div style={{display:"block", margin:"4px", padding:"4px"}}>
{page === 'General' && <GeneralActionPage bladeburner={props.bladeburner} player={props.player} />}
{page === 'Contracts' && <ContractPage bladeburner={props.bladeburner} player={props.player} />}
{page === 'Operations' && <OperationPage bladeburner={props.bladeburner} player={props.player} />}
{page === 'BlackOps' && <BlackOpPage bladeburner={props.bladeburner} player={props.player} />}
{page === 'Skills' && <SkillPage bladeburner={props.bladeburner} />}
</div>
<span className="text">{stealthIcon} = This action requires stealth, {killIcon} = This action involves retirement</span>
</>);
}

View File

@@ -0,0 +1,89 @@
import React, { useState } from "react";
import {
formatNumber,
convertTimeMsToTimeElapsedString,
} from "../../../utils/StringHelperFunctions";
import { ActionTypes } from "../data/ActionTypes";
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
import { stealthIcon, killIcon } from "../data/Icons";
import { createPopup } from "../../ui/React/createPopup";
import { TeamSizePopup } from "./TeamSizePopup";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { SuccessChance } from "./SuccessChance";
import { CopyableText } from "../../ui/React/CopyableText";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
action: any;
}
export function BlackOpElem(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
const isCompleted = (props.bladeburner.blackops[props.action.name] != null);
if(isCompleted) {
return (
<h2 style={{display: 'block'}}>{props.action.name} (COMPLETED)</h2>);
}
const isActive = props.bladeburner.action.type === ActionTypes["BlackOperation"] && props.action.name === props.bladeburner.action.name;
const estimatedSuccessChance = props.action.getEstSuccessChance(props.bladeburner);
const actionTime = props.action.getActionTime(props.bladeburner);
const hasReqdRank = props.bladeburner.rank >= props.action.reqdRank;
const computedActionTimeCurrent = Math.min(props.bladeburner.actionTimeCurrent+props.bladeburner.actionTimeOverflow, props.bladeburner.actionTimeToComplete);
function onStart(): void {
props.bladeburner.action.type = ActionTypes.BlackOperation;
props.bladeburner.action.name = props.action.name;
props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender(old => !old);
}
function onTeam(): void {
const popupId = "bladeburner-operation-set-team-size-popup";
createPopup(popupId, TeamSizePopup, {
bladeburner: props.bladeburner,
action: props.action,
popupId: popupId,
});
}
return (<>
<h2 style={{display: 'inline-block'}}>
{isActive ?
<><CopyableText value={props.action.name} /> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)})</> :
<CopyableText value={props.action.name} />
}
</h2>
{isActive ?
<p style={{display: 'block'}}>{createProgressBarText({progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete})}</p> :
<>
<a
className={hasReqdRank ? "a-link-button" : "a-link-button-inactive"}
style={{margin:"3px", padding:"3px"}}
onClick={onStart}
>Start</a>
<a
onClick={onTeam}
style={{margin:"3px", padding:"3px"}}
className="a-link-button">
Set Team Size (Curr Size: {formatNumber(props.action.teamCount, 0)})
</a>
</>}
<br />
<br />
<p style={{display:"inline-block"}} dangerouslySetInnerHTML={{__html: props.action.desc}} />
<br />
<br />
<p style={{display:"block", color:hasReqdRank ? "white" : "red"}}>
Required Rank: {formatNumber(props.action.reqdRank, 0)}
</p>
<br />
<pre style={{display:"inline-block"}}>
Estimated Success Chance: <SuccessChance chance={estimatedSuccessChance} /> {props.action.isStealth?stealthIcon:<></>}{props.action.isKill?killIcon:<></>}
<br />
Time Required: {convertTimeMsToTimeElapsedString(actionTime*1000)}
</pre>
</>);
}

View File

@@ -0,0 +1,36 @@
import React from "react";
import { BlackOperations } from "../BlackOperations";
import { BlackOperation } from "../BlackOperation";
import { BlackOpElem } from "./BlackOpElem";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
}
export function BlackOpList(props: IProps): React.ReactElement {
let blackops: BlackOperation[] = [];
for (const blackopName in BlackOperations) {
if (BlackOperations.hasOwnProperty(blackopName)) {
blackops.push(BlackOperations[blackopName]);
}
}
blackops.sort(function(a, b) {
return (a.reqdRank - b.reqdRank);
});
blackops = blackops.filter((blackop: BlackOperation, i: number) => !(props.bladeburner.blackops[blackops[i].name] == null &&
i !== 0 &&
props.bladeburner.blackops[blackops[i-1].name] == null));
blackops = blackops.reverse();
return (<>
{blackops.map((blackop: BlackOperation) => <li key={blackop.name} className="bladeburner-action">
<BlackOpElem bladeburner={props.bladeburner} action={blackop} player={props.player} />
</li>,
)}
</>);
}

View File

@@ -0,0 +1,29 @@
import * as React from "react";
import { BlackOpList } from "./BlackOpList";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { CopyableText } from "../../ui/React/CopyableText";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
}
export function BlackOpPage(props: IProps): React.ReactElement {
return (<>
<p style={{display: 'block', margin: '4px', padding: '4px'}}>
Black Operations (Black Ops) are special, one-time covert operations.
Each Black Op must be unlocked successively by completing
the one before it.
<br />
<br />
<b>Your ultimate goal to climb through the ranks of Bladeburners is to complete
all of the Black Ops.</b>
<br />
<br />
Like normal operations, you may use a team for Black Ops. Failing
a black op will incur heavy HP and rank losses.
</p>
<BlackOpList bladeburner={props.bladeburner} player={props.player} />
</>);
}

View File

@@ -1,62 +0,0 @@
import { BlackOperations } from "../BlackOperations";
/*
if (DomElems.actionsAndSkillsList == null || DomElems.actionsAndSkillsDesc == null) {
throw new Error("Bladeburner.createBlackOpsContent called with either " +
"DomElems.actionsAndSkillsList or DomElems.actionsAndSkillsDesc = null");
}
DomElems.actionsAndSkillsDesc.innerHTML =
"Black Operations (Black Ops) are special, one-time covert operations. " +
"Each Black Op must be unlocked successively by completing " +
"the one before it.<br><br>" +
"<b>Your ultimate goal to climb through the ranks of Bladeburners is to complete " +
"all of the Black Ops.</b><br><br>" +
"Like normal operations, you may use a team for Black Ops. Failing " +
"a black op will incur heavy HP and rank losses.";
// Put Black Operations in sequence of required rank
var blackops = [];
for (var blackopName in BlackOperations) {
if (BlackOperations.hasOwnProperty(blackopName)) {
blackops.push(BlackOperations[blackopName]);
}
}
blackops.sort(function(a, b) {
return (a.reqdRank - b.reqdRank);
});
for (var i = blackops.length-1; i >= 0 ; --i) {
if (this.blackops[[blackops[i].name]] == null && i !== 0 && this.blackops[[blackops[i-1].name]] == null) {continue;} // If this one nor the next are completed then this isn't unlocked yet.
DomElems.blackops[blackops[i].name] = createElement("div", {
class:"bladeburner-action", name:blackops[i].name
});
DomElems.actionsAndSkillsList.appendChild(DomElems.blackops[blackops[i].name]);
}
*/
import * as React from "react";
export function BlackOperationsPage(): React.ReactElement {
// Put Black Operations in sequence of required rank
const blackops = [];
for (const name in BlackOperations) {
if (BlackOperations.hasOwnProperty(name)) {
blackops.push(BlackOperations[name]);
}
}
blackops.sort(function(a, b) {
return (a.reqdRank - b.reqdRank);
});
return (<div>
<p>
Black Operations (Black Ops) are special, one-time covert operations. Each Black Op must be unlocked successively by completing the one before it.<br /><br />
<b>Your ultimate goal to climb through the ranks of Bladeburners is to complete all of the Black Ops.</b><br /><br />
Like normal operations, you may use a team for Black Ops. Failing a black op will incur heavy HP and rank losses.</p>
{blackops.map(() => <div className="bladeburner-action">
</div>,
)}
</div>)
}

View File

@@ -0,0 +1,115 @@
import React, { useState, useRef, useEffect } from "react";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface ILineProps {
content: any;
}
function Line(props: ILineProps): React.ReactElement {
return (<tr>
<td className="bladeburner-console-line" style={{color: 'var(--my-font-color)', whiteSpace: 'pre-wrap'}}>{props.content}</td>
</tr>)
}
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
}
export function Console(props: IProps): React.ReactElement {
const lastRef = useRef<HTMLDivElement>(null);
const setRerender = useState(false)[1];
const [consoleHistoryIndex, setConsoleHistoryIndex] = useState(props.bladeburner.consoleHistory.length);
// TODO: Figure out how to actually make the scrolling work correctly.
function scrollToBottom(): void {
if(!lastRef.current) return;
lastRef.current.scrollTop = lastRef.current.scrollHeight;
}
function rerender(): void {
setRerender(old => !old);
}
useEffect(() => {
const id = setInterval(rerender, 1000);
const id2 = setInterval(scrollToBottom, 100);
return () => {
clearInterval(id);
clearInterval(id2);
};
}, []);
function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.keyCode === 13) {
event.preventDefault();
const command = event.currentTarget.value;
event.currentTarget.value = "";
if (command.length > 0) {
props.bladeburner.postToConsole("> " + command);
props.bladeburner.executeConsoleCommands(props.player, command);
setConsoleHistoryIndex(props.bladeburner.consoleHistory.length);
rerender();
}
}
const consoleHistory = props.bladeburner.consoleHistory;
if (event.keyCode === 38) { // up
let i = consoleHistoryIndex;
const len = consoleHistory.length;
if (len === 0) {return;}
if (i < 0 || i > len) {
setConsoleHistoryIndex(len);
}
if (i !== 0) {
i = i-1;
}
setConsoleHistoryIndex(i);
const prevCommand = consoleHistory[i];
event.currentTarget.value = prevCommand;
}
if (event.keyCode === 40) {
const i = consoleHistoryIndex;
const len = consoleHistory.length;
if (len == 0) {return;}
if (i < 0 || i > len) {
setConsoleHistoryIndex(len);
}
// Latest command, put nothing
if (i == len || i == len-1) {
setConsoleHistoryIndex(len);
event.currentTarget.value = "";
} else {
setConsoleHistoryIndex(consoleHistoryIndex+1);
const prevCommand = consoleHistory[consoleHistoryIndex+1];
event.currentTarget.value = prevCommand;
}
}
}
return (<div ref={lastRef} className="bladeburner-console-div">
<table className="bladeburner-console-table">
<tbody>
{/*
TODO: optimize this.
using `i` as a key here isn't great because it'll re-render everything
everytime the console reaches max length.
*/}
{props.bladeburner.consoleLogs.map((log: any, i: number) => <Line key={i} content={log} />)}
<tr key="input" id="bladeburner-console-input-row" className="bladeburner-console-input-row">
<td className="bladeburner-console-input-cell">
<pre>{"> "}</pre><input autoFocus className="bladeburner-console-input" tabIndex={1} type="text" onKeyDown={handleKeyDown} />
</td>
</tr>
</tbody>
</table>
</div>);
}

View File

@@ -0,0 +1,119 @@
import React, { useState } from "react";
import { ActionTypes } from "../data/ActionTypes";
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
import {
formatNumber,
convertTimeMsToTimeElapsedString,
} from "../../../utils/StringHelperFunctions";
import { stealthIcon, killIcon } from "../data/Icons";
import { BladeburnerConstants } from "../data/Constants";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { SuccessChance } from "./SuccessChance";
import { CopyableText } from "../../ui/React/CopyableText";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
action: any;
}
export function ContractElem(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
const isActive = props.bladeburner.action.type === ActionTypes["Contract"] && props.action.name === props.bladeburner.action.name;
const estimatedSuccessChance = props.action.getEstSuccessChance(props.bladeburner);
const successChance = props.action.getSuccessChance(props.bladeburner);
const computedActionTimeCurrent = Math.min(props.bladeburner.actionTimeCurrent+props.bladeburner.actionTimeOverflow, props.bladeburner.actionTimeToComplete);
const maxLevel = (props.action.level >= props.action.maxLevel);
const actionTime = props.action.getActionTime(props.bladeburner);
const autolevelCheckboxId = `bladeburner-${props.action.name}-autolevel-checkbox`;
function onStart(): void {
props.bladeburner.action.type = ActionTypes.Contract;
props.bladeburner.action.name = props.action.name;
props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender(old => !old);
}
function increaseLevel(): void {
++props.action.level;
if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender(old => !old);
}
function decreaseLevel(): void {
--props.action.level;
if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender(old => !old);
}
function onAutolevel(event: React.ChangeEvent<HTMLInputElement>): void {
props.action.autoLevel = event.target.checked;
setRerender(old => !old);
}
return (<>
<h2 style={{display: 'inline-block'}}>
{isActive ?
<><CopyableText value={props.action.name} /> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)})</> :
<CopyableText value={props.action.name} />
}
</h2>
{isActive ?
<p style={{display: 'block'}}>{createProgressBarText({progress:computedActionTimeCurrent / props.bladeburner.actionTimeToComplete})}</p> :
<>
<a
onClick={onStart}
className="a-link-button"
style={{margin:"3px", padding:"3px"}}>
Start
</a>
</>}
<br />
<br />
<pre className="tooltip" style={{display:"inline-block"}}>
<span className="tooltiptext">
{props.action.getSuccessesNeededForNextLevel(BladeburnerConstants.ContractSuccessesPerLevel)} successes needed for next level
</span>
Level: {props.action.level} / {props.action.maxLevel}
</pre>
<a
onClick={increaseLevel}
style={{padding:"2px", margin:"2px"}}
className={`tooltip ${maxLevel ? "a-link-button-inactive" : "a-link-button"}`}>
{isActive && (<span className="tooltiptext">WARNING: changing the level will restart the Operation</span>)}
</a>
<a
onClick={decreaseLevel}
style={{padding:"2px", margin:"2px"}}
className={`tooltip ${props.action.level <= 1 ? "a-link-button-inactive" : "a-link-button"}`}>
{isActive && (<span className="tooltiptext">WARNING: changing the level will restart the Operation</span>)}
</a>
<br />
<br />
<pre style={{display: 'inline-block'}}>
<span dangerouslySetInnerHTML={{__html: props.action.desc}} />
<br /><br />
Estimated success chance: <SuccessChance chance={estimatedSuccessChance} /> {props.action.isStealth?stealthIcon:<></>}{props.action.isKill?killIcon:<></>}<br />
Time Required: {convertTimeMsToTimeElapsedString(actionTime*1000)}<br />
Contracts remaining: {Math.floor(props.action.count)}<br />
Successes: {props.action.successes}<br />
Failures: {props.action.failures}
</pre>
<br />
<label
className="tooltip"
style={{color: 'white'}}
htmlFor={autolevelCheckboxId}>
Autolevel:
<span className="tooltiptext">Automatically increase operation level when possible</span>
</label>
<input
type="checkbox"
id={autolevelCheckboxId}
checked={props.action.autoLevel}
onChange={onAutolevel}/>
</>);
}

View File

@@ -0,0 +1,20 @@
import React from "react";
import { ContractElem } from "./ContractElem";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
}
export function ContractList(props: IProps): React.ReactElement {
const names = Object.keys(props.bladeburner.contracts);
const contracts = props.bladeburner.contracts;
return (<>
{names.map((name: string) => <li key={name} className="bladeburner-action">
<ContractElem bladeburner={props.bladeburner} action={contracts[name]} player={props.player} />
</li>,
)}
</>);
}

View File

@@ -0,0 +1,23 @@
import * as React from "react";
import { ContractList } from "./ContractList";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
}
export function ContractPage(props: IProps): React.ReactElement {
return (<>
<p style={{display: 'block', margin: '4px', padding: '4px'}}>
Complete contracts in order to increase your Bladeburner rank and earn money.
Failing a contract will cause you to lose HP, which can lead to hospitalization.
<br />
<br />
You can unlock higher-level contracts by successfully completing them.
Higher-level contracts are more difficult, but grant more rank, experience, and money.
</p>
<ContractList bladeburner={props.bladeburner} player={props.player} />
</>);
}

View File

@@ -0,0 +1,69 @@
import React, { useState } from "react";
import { ActionTypes } from "../data/ActionTypes";
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
import {
formatNumber,
convertTimeMsToTimeElapsedString,
} from "../../../utils/StringHelperFunctions";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { CopyableText } from "../../ui/React/CopyableText";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
action: any;
}
export function GeneralActionElem(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
const isActive = props.action.name === props.bladeburner.action.name;
const computedActionTimeCurrent = Math.min(props.bladeburner.actionTimeCurrent+props.bladeburner.actionTimeOverflow, props.bladeburner.actionTimeToComplete);
const actionTime = (function(): number{
switch(props.action.name) {
case "Training":
case "Field Analysis":
return 30;
case "Diplomacy":
case "Hyperbolic Regeneration Chamber":
return 60;
case "Recruitment":
return props.bladeburner.getRecruitmentTime(props.player);
}
return -1; // dead code
})();
const successChance = props.action.name === "Recruitment" ? Math.max(0, Math.min(props.bladeburner.getRecruitmentSuccessChance(props.player), 1)) : -1;
function onStart(): void {
props.bladeburner.action.type = ActionTypes[(props.action.name as string)];
props.bladeburner.action.name = props.action.name;
props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender(old => !old);
}
return (<>
<h2 style={{display: 'inline-block'}}>
{isActive ?
<><CopyableText value={props.action.name} /> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)})</> :
<CopyableText value={props.action.name} />
}
</h2>
{isActive ?
<p style={{display: 'block'}}>{createProgressBarText({progress:computedActionTimeCurrent / props.bladeburner.actionTimeToComplete})}</p> :
<>
<a
onClick={onStart}
className="a-link-button"
style={{margin:"3px", padding:"3px"}}>
Start
</a>
</>}
<br />
<br />
<pre style={{display: 'inline-block'}} dangerouslySetInnerHTML={{__html: props.action.desc}}></pre><br /><br />
<pre style={{display: 'inline-block'}}>
Time Required: {convertTimeMsToTimeElapsedString(actionTime*1000)}
{successChance !== -1 && <><br />Estimated success chance: {formatNumber(successChance*100, 1)}%</>}
</pre>
</>);
}

View File

@@ -0,0 +1,26 @@
import React from "react";
import { GeneralActionElem } from "./GeneralActionElem";
import { Action } from "../Action";
import { GeneralActions } from "../GeneralActions";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
}
export function GeneralActionList(props: IProps): React.ReactElement {
const actions: Action[] = [];
for (const name in GeneralActions) {
if (GeneralActions.hasOwnProperty(name)) {
actions.push(GeneralActions[name]);
}
}
return (<>
{actions.map((action: Action) => <li key={action.name} className="bladeburner-action">
<GeneralActionElem bladeburner={props.bladeburner} action={action} player={props.player} />
</li>,
)}
</>);
}

View File

@@ -0,0 +1,19 @@
import * as React from "react";
import { GeneralActionList } from "./GeneralActionList";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
}
export function GeneralActionPage(props: IProps): React.ReactElement {
return (<>
<p style={{display: 'block', margin: '4px', padding: '4px'}}>
These are generic actions that will assist you in your Bladeburner
duties. They will not affect your Bladeburner rank in any way.
</p>
<GeneralActionList bladeburner={props.bladeburner} player={props.player} />
</>);
}

View File

@@ -0,0 +1,135 @@
import React, { useState } from "react";
import { ActionTypes } from "../data/ActionTypes";
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
import {
formatNumber,
convertTimeMsToTimeElapsedString,
} from "../../../utils/StringHelperFunctions";
import { stealthIcon, killIcon } from "../data/Icons";
import { BladeburnerConstants } from "../data/Constants";
import { createPopup } from "../../ui/React/createPopup";
import { TeamSizePopup } from "./TeamSizePopup";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { SuccessChance } from "./SuccessChance";
import { CopyableText } from "../../ui/React/CopyableText";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
action: any;
}
export function OperationElem(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
const isActive = props.bladeburner.action.type === ActionTypes["Operation"] && props.action.name === props.bladeburner.action.name;
const estimatedSuccessChance = props.action.getEstSuccessChance(props.bladeburner);
const computedActionTimeCurrent = Math.min(props.bladeburner.actionTimeCurrent+props.bladeburner.actionTimeOverflow,props.bladeburner.actionTimeToComplete);
const maxLevel = (props.action.level >= props.action.maxLevel);
const actionTime = props.action.getActionTime(props.bladeburner);
const autolevelCheckboxId = `bladeburner-${props.action.name}-autolevel-checkbox`;
function onStart(): void {
props.bladeburner.action.type = ActionTypes.Operation;
props.bladeburner.action.name = props.action.name;
props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender(old => !old);
}
function onTeam(): void {
const popupId = "bladeburner-operation-set-team-size-popup";
createPopup(popupId, TeamSizePopup, {
bladeburner: props.bladeburner,
action: props.action,
popupId: popupId,
});
}
function increaseLevel(): void {
++props.action.level;
if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender(old => !old);
}
function decreaseLevel(): void {
--props.action.level;
if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender(old => !old);
}
function onAutolevel(event: React.ChangeEvent<HTMLInputElement>): void {
props.action.autoLevel = event.target.checked;
setRerender(old => !old);
}
return (<>
<h2 style={{display: 'inline-block'}}>
{isActive ?
<><CopyableText value={props.action.name} /> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)})</> :
<CopyableText value={props.action.name} />
}
</h2>
{isActive ?
<p style={{display: 'block'}}>{createProgressBarText({progress:computedActionTimeCurrent / props.bladeburner.actionTimeToComplete})}</p> :
<>
<a
onClick={onStart}
className="a-link-button"
style={{margin:"3px", padding:"3px"}}>
Start
</a>
<a
onClick={onTeam}
style={{margin:"3px", padding:"3px"}}
className="a-link-button">
Set Team Size (Curr Size: {formatNumber(props.action.teamCount, 0)})
</a>
</>}
<br />
<br />
<pre className="tooltip" style={{display:"inline-block"}}>
<span className="tooltiptext">
{props.action.getSuccessesNeededForNextLevel(BladeburnerConstants.OperationSuccessesPerLevel)} successes needed for next level
</span>
Level: {props.action.level} / {props.action.maxLevel}
</pre>
<a
onClick={increaseLevel}
style={{padding:"2px", margin:"2px"}}
className={`tooltip ${maxLevel ? "a-link-button-inactive" : "a-link-button"}`}>
{isActive && (<span className="tooltiptext">WARNING: changing the level will restart the Operation</span>)}
</a>
<a
onClick={decreaseLevel}
style={{padding:"2px", margin:"2px"}}
className={`tooltip ${props.action.level <= 1 ? "a-link-button-inactive" : "a-link-button"}`}>
{isActive && (<span className="tooltiptext">WARNING: changing the level will restart the Operation</span>)}
</a>
<br />
<br />
<pre style={{display:"inline-block"}}>
<span dangerouslySetInnerHTML={{__html: props.action.desc}} />
<br /><br />
Estimated success chance: <SuccessChance chance={estimatedSuccessChance} /> {props.action.isStealth?stealthIcon:<></>}{props.action.isKill?killIcon:<></>}<br />
Time Required: {convertTimeMsToTimeElapsedString(actionTime*1000)}<br />
Operations remaining: {Math.floor(props.action.count)}<br />
Successes: {props.action.successes}<br />
Failures: {props.action.failures}
</pre>
<br />
<label
className="tooltip"
style={{color: 'white'}}
htmlFor={autolevelCheckboxId}>
Autolevel:
<span className="tooltiptext">Automatically increase operation level when possible</span>
</label>
<input
type="checkbox"
id={autolevelCheckboxId}
checked={props.action.autoLevel}
onChange={onAutolevel}/>
</>);
}

View File

@@ -0,0 +1,20 @@
import React from "react";
import { OperationElem } from "./OperationElem";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
}
export function OperationList(props: IProps): React.ReactElement {
const names = Object.keys(props.bladeburner.operations);
const operations = props.bladeburner.operations;
return (<>
{names.map((name: string) => <li key={name} className="bladeburner-action">
<OperationElem bladeburner={props.bladeburner} action={operations[name]} player={props.player} />
</li>,
)}
</>);
}

View File

@@ -0,0 +1,34 @@
import * as React from "react";
import { OperationList } from "./OperationList";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
bladeburner: IBladeburner;
player: IPlayer;
}
export function OperationPage(props: IProps): React.ReactElement {
return (<>
<p style={{display: 'block', margin: '4px', padding: '4px'}}>
Carry out operations for the Bladeburner division.
Failing an operation will reduce your Bladeburner rank. It will also
cause you to lose HP, which can lead to hospitalization. In general,
operations are harder and more punishing than contracts,
but are also more rewarding.
<br />
<br />
Operations can affect the chaos level and Synthoid population of your
current city. The exact effects vary between different Operations.
<br />
<br />
For operations, you can use a team. You must first recruit team members.
Having a larger team will improves your chances of success.
<br />
<br />
You can unlock higher-level operations by successfully completing them.
Higher-level operations are more difficult, but grant more rank and experience.
</p>
<OperationList bladeburner={props.bladeburner} player={props.player} />
</>);
}

View File

@@ -0,0 +1,28 @@
import React from "react";
import { Stats } from "./Stats";
import { Console } from "./Console";
import { AllPages } from "./AllPages";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import { IBladeburner } from "../IBladeburner";
interface IProps {
bladeburner: IBladeburner;
engine: IEngine;
player: IPlayer;
}
export function Root(props: IProps): React.ReactElement {
return (<div id="bladeburner-container">
<div style={{height:"60%", display:"block", position:"relative"}}>
<div style={{height: '100%', width:"30%", display:"inline-block", border:"1px solid white"}}>
<Stats bladeburner={props.bladeburner} player={props.player} engine={props.engine} />
</div>
<Console bladeburner={props.bladeburner} player={props.player} />
</div>
<div style={{width:"70%", display:"block", border:"1px solid white", marginTop:"6px", padding: "6px", position:"relative"}}>
<AllPages bladeburner={props.bladeburner} player={props.player} />
</div>
</div>);
}

View File

@@ -0,0 +1,48 @@
import React from "react";
import { CopyableText } from "../../ui/React/CopyableText";
import { formatNumber } from "../../../utils/StringHelperFunctions";
import { IBladeburner } from "../IBladeburner";
interface IProps {
skill: any;
bladeburner: IBladeburner;
onUpgrade: () => void;
}
export function SkillElem(props: IProps): React.ReactElement {
const skillName = props.skill.name;
let currentLevel = 0;
if (props.bladeburner.skills[skillName] && !isNaN(props.bladeburner.skills[skillName])) {
currentLevel = props.bladeburner.skills[skillName];
}
const pointCost = props.skill.calculateCost(currentLevel);
const canLevel = props.bladeburner.skillPoints >= pointCost;
const maxLvl = props.skill.maxLvl ? currentLevel >= props.skill.maxLvl : false;
function onClick(): void {
if (props.bladeburner.skillPoints < pointCost) return;
props.bladeburner.skillPoints -= pointCost;
props.bladeburner.upgradeSkill(props.skill);
props.onUpgrade();
}
return (<>
<h2 style={{display: 'inline-block'}}>
<CopyableText value={props.skill.name} />
</h2>
<a
onClick={onClick}
style={{display: "inline-block", margin: "3px", padding: "3px"}}
className={canLevel && !maxLvl ? "a-link-button" : "a-link-button-inactive"}>
Level
</a>
<br />
<br />
<p style={{display: 'block'}}>Level: {currentLevel}</p>
{maxLvl ?
<p style={{color:"red", display:"block"}}>MAX LEVEL</p> :
<p style={{display:"block"}}>Skill Points required: {formatNumber(pointCost, 0)}</p>}
<p style={{display:"inline-block"}} dangerouslySetInnerHTML={{__html: props.skill.desc}} />
</>);
}

View File

@@ -0,0 +1,18 @@
import * as React from "react";
import { SkillElem } from "./SkillElem";
import { Skills } from "../Skills";
import { IBladeburner } from "../IBladeburner";
interface IProps {
bladeburner: IBladeburner;
onUpgrade: () => void;
}
export function SkillList(props: IProps): React.ReactElement {
return (<>
{Object.keys(Skills).map((skill: string) => <li key={skill} className="bladeburner-action">
<SkillElem bladeburner={props.bladeburner} skill={Skills[skill]} onUpgrade={props.onUpgrade} />
</li>,
)}
</>);
}

View File

@@ -0,0 +1,70 @@
import React, { useState } from "react";
import { SkillList } from "./SkillList";
import { BladeburnerConstants } from "../data/Constants";
import { formatNumber } from "../../../utils/StringHelperFunctions";
import { IBladeburner } from "../IBladeburner";
interface IProps {
bladeburner: IBladeburner;
}
export function SkillPage(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
const mults = props.bladeburner.skillMultipliers;
function valid(mult: any): boolean {
return mult && mult !== 1
}
return (<>
<p>
<strong>Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}</strong>
</p>
<p>
You will gain one skill point every {BladeburnerConstants.RanksPerSkillPoint} ranks.
<br />
<br />
Note that when upgrading a skill, the benefit for that skill is additive.
However, the effects of different skills with each other is multiplicative.
<br />
</p>
<br />
{valid(mults["successChanceAll"]) && <p>Total Success Chance: x{formatNumber(mults["successChanceAll"], 3)}</p>}
{valid(mults["successChanceStealth"]) && <p>Stealth Success Chance: x{formatNumber(mults["successChanceStealth"], 3)}</p>}
{valid(mults["successChanceKill"]) && <p>Retirement Success Chance: x{formatNumber(mults["successChanceKill"], 3)}</p>}
{valid(mults["successChanceContract"]) && <p>Contract Success Chance: x{formatNumber(mults["successChanceContract"], 3)}</p>}
{valid(mults["successChanceOperation"]) && <p>Operation Success Chance: x{formatNumber(mults["successChanceOperation"], 3)}</p>}
{valid(mults["successChanceEstimate"]) && <p>Synthoid Data Estimate: x{formatNumber(mults["successChanceEstimate"], 3)}</p>}
{valid(mults["actionTime"]) && <p>Action Time: x{formatNumber(mults["actionTime"], 3)}</p>}
{valid(mults["effHack"]) && <p>Hacking Skill: x{formatNumber(mults["effHack"], 3)}</p>}
{valid(mults["effStr"]) && <p>Strength: x{formatNumber(mults["effStr"], 3)}</p>}
{valid(mults["effDef"]) && <p>Defense: x{formatNumber(mults["effDef"], 3)}</p>}
{valid(mults["effDex"]) && <p>Dexterity: x{formatNumber(mults["effDex"], 3)}</p>}
{valid(mults["effAgi"]) && <p>Agility: x{formatNumber(mults["effAgi"], 3)}</p>}
{valid(mults["effCha"]) && <p>Charisma: x{formatNumber(mults["effCha"], 3)}</p>}
{valid(mults["effInt"]) && <p>Intelligence: x{formatNumber(mults["effInt"], 3)}</p>}
{valid(mults["stamina"]) && <p>Stamina: x{formatNumber(mults["stamina"], 3)}</p>}
{valid(mults["money"]) && <p>Contract Money: x{formatNumber(mults["money"], 3)}</p>}
{valid(mults["expGain"]) && <p>Exp Gain: x{formatNumber(mults["expGain"], 3)}</p>}
<br />
<SkillList bladeburner={props.bladeburner} onUpgrade={() => setRerender(old => !old)} />
</>);
}
/*
var multKeys = Object.keys(this.skillMultipliers);
for (var i = 0; i < multKeys.length; ++i) {
var mult = this.skillMultipliers[multKeys[i]];
if (mult && mult !== 1) {
mult = formatNumber(mult, 3);
switch(multKeys[i]) {
}
}
}
*/

View File

@@ -0,0 +1,139 @@
import React, { useState, useEffect } from "react";
import {
formatNumber,
convertTimeMsToTimeElapsedString,
} from "../../../utils/StringHelperFunctions";
import { BladeburnerConstants } from "../data/Constants";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import { Money } from "../../ui/React/Money";
import { StatsTable } from "../../ui/React/StatsTable";
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { createPopup } from "../../ui/React/createPopup";
import { Factions } from "../../Faction/Factions";
import {
joinFaction,
displayFactionContent,
} from "../../Faction/FactionHelpers";
import { IBladeburner } from "../IBladeburner"
import { TravelPopup } from "./TravelPopup";
interface IProps {
bladeburner: IBladeburner;
engine: IEngine;
player: IPlayer;
}
export function Stats(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
useEffect(() => {
const id = setInterval(() => setRerender(old => !old), 1000);
return () => clearInterval(id);
}, []);
function openStaminaHelp(): void {
dialogBoxCreate("Performing actions will use up your stamina.<br><br>" +
"Your max stamina is determined primarily by your agility stat.<br><br>" +
"Your stamina gain rate is determined by both your agility and your " +
"max stamina. Higher max stamina leads to a higher gain rate.<br><br>" +
"Once your " +
"stamina falls below 50% of its max value, it begins to negatively " +
"affect the success rate of your contracts/operations. This penalty " +
"is shown in the overview panel. If the penalty is 15%, then this means " +
"your success rate would be multipled by 85% (100 - 15).<br><br>" +
"Your max stamina and stamina gain rate can also be increased by " +
"training, or through skills and Augmentation upgrades.");
}
function openPopulationHelp(): void {
dialogBoxCreate("The success rate of your contracts/operations depends on " +
"the population of Synthoids in your current city. " +
"The success rate that is shown to you is only an estimate, " +
"and it is based on your Synthoid population estimate.<br><br>" +
"Therefore, it is important that this Synthoid population estimate " +
"is accurate so that you have a better idea of your " +
"success rate for contracts/operations. Certain " +
"actions will increase the accuracy of your population " +
"estimate.<br><br>" +
"The Synthoid populations of cities can change due to your " +
"actions or random events. If random events occur, they will " +
"be logged in the Bladeburner Console.");
}
function openTravel(): void {
const popupId = "bladeburner-travel-popup";
createPopup(popupId, TravelPopup, {
bladeburner: props.bladeburner,
popupId: popupId,
});
}
function openFaction(): void {
const faction = Factions["Bladeburners"];
if (faction.isMember) {
props.engine.loadFactionContent();
displayFactionContent("Bladeburners");
} else {
if (props.bladeburner.rank >= BladeburnerConstants.RankNeededForFaction) {
joinFaction(faction);
dialogBoxCreate("Congratulations! You were accepted into the Bladeburners faction");
} else {
dialogBoxCreate("You need a rank of 25 to join the Bladeburners Faction!")
}
}
}
return (<>
<p className="tooltip" style={{display: 'inline-block'}}>
Rank: {formatNumber(props.bladeburner.rank, 2)}
<span className="tooltiptext">Your rank within the Bladeburner division.</span>
</p><br />
<p>Stamina: {formatNumber(props.bladeburner.stamina, 3)} / {formatNumber(props.bladeburner.maxStamina, 3)}</p>
<div className="help-tip" onClick={openStaminaHelp}>?</div><br />
<p>Stamina Penalty: {formatNumber((1-props.bladeburner.calculateStaminaPenalty())*100, 1)}%</p><br />
<p>Team Size: {formatNumber(props.bladeburner.teamSize, 0)}</p>
<p>Team Members Lost: {formatNumber(props.bladeburner.teamLost, 0)}</p><br />
<p>Num Times Hospitalized: {props.bladeburner.numHosp}</p>
<p>Money Lost From Hospitalizations: {Money(props.bladeburner.moneyLost)}</p><br />
<p>Current City: {props.bladeburner.city}</p>
<p className="tooltip" style={{display: 'inline-block'}}>
Est. Synthoid Population: {numeralWrapper.formatPopulation(props.bladeburner.getCurrentCity().popEst)}
<span className="tooltiptext">This is your Bladeburner division's estimate of how many Synthoids exist in your current city.</span>
</p>
<div className="help-tip" onClick={openPopulationHelp}>?</div><br />
<p className="tooltip" style={{display: 'inline-block'}}>
Est. Synthoid Communities: {formatNumber(props.bladeburner.getCurrentCity().comms, 0)}
<span className="tooltiptext">This is your Bladeburner divison's estimate of how many Synthoid communities exist in your current city.</span>
</p><br />
<p className="tooltip" style={{display: 'inline-block'}}>
City Chaos: {formatNumber(props.bladeburner.getCurrentCity().chaos)}
<span className="tooltiptext">The city's chaos level due to tensions and conflicts between humans and Synthoids. Having too high of a chaos level can make contracts and operations harder.</span>
</p><br />
<br />
<p className="tooltip" style={{display: 'inline-block'}}>
Bonus time: {convertTimeMsToTimeElapsedString(props.bladeburner.storedCycles/BladeburnerConstants.CyclesPerSecond*1000)}<br />
<span className="tooltiptext">
You gain bonus time while offline or when the game is inactive (e.g. when the tab is throttled by browser).
Bonus time makes the Bladeburner mechanic progress faster, up to 5x the normal speed.
</span>
</p>
<p>Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}</p><br />
{StatsTable([
["Aug. Success Chance mult: ", formatNumber(props.player.bladeburner_success_chance_mult*100, 1) + "%"],
["Aug. Max Stamina mult: ", formatNumber(props.player.bladeburner_max_stamina_mult*100, 1) + "%"],
["Aug. Stamina Gain mult: ", formatNumber(props.player.bladeburner_stamina_gain_mult*100, 1) + "%"],
["Aug. Field Analysis mult: ", formatNumber(props.player.bladeburner_analysis_mult*100, 1) + "%"],
])}
<br />
<a onClick={openTravel} className="a-link-button" style={{display:"inline-block"}}>Travel</a>
<a onClick={openFaction} className="a-link-button tooltip" style={{display:"inline-block"}}>
<span className="tooltiptext">Apply to the Bladeburner Faction, or go to the faction page if you are already a member</span>
Faction
</a>
<br />
<br />
</>);
}

View File

@@ -0,0 +1,14 @@
import React from "react";
import { formatNumber } from "../../../utils/StringHelperFunctions";
interface IProps {
chance: number[];
}
export function SuccessChance(props: IProps): React.ReactElement {
if(props.chance[0] === props.chance[1]) {
return (<>{formatNumber(props.chance[0]*100, 1)}%</>);
}
return (<>{formatNumber(props.chance[0]*100, 1)}% ~ {formatNumber(props.chance[1]*100, 1)}%</>);
}

View File

@@ -0,0 +1,37 @@
import React, { useState } from "react";
import { removePopup } from "../../ui/React/createPopup";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { Action } from "../Action";
import { IBladeburner } from "../IBladeburner";
interface IProps {
bladeburner: IBladeburner;
action: Action;
popupId: string;
}
export function TeamSizePopup(props: IProps): React.ReactElement {
const [teamSize, setTeamSize] = useState<number | undefined>();
function confirmTeamSize(): void {
if(teamSize === undefined) return;
const num = Math.round(teamSize);
if (isNaN(num) || num < 0) {
dialogBoxCreate("Invalid value entered for number of Team Members (must be numeric, positive)")
} else {
props.action.teamCount = num;
}
removePopup(props.popupId);
}
return (<>
<p>
Enter the amount of team members you would like to take on this
Op. If you do not have the specified number of team members,
then as many as possible will be used. Note that team members may
be lost during operations.
</p>
<input autoFocus type="number" placeholder= "Team size" className= "text-input" onChange={event => setTeamSize(parseFloat(event.target.value))} />
<a className="a-link-button" onClick={confirmTeamSize}>Confirm</a>
</>);
}

View File

@@ -0,0 +1,32 @@
import React from "react";
import { removePopup } from "../../ui/React/createPopup";
import { BladeburnerConstants } from "../data/Constants";
import { IBladeburner } from "../IBladeburner";
interface IProps {
bladeburner: IBladeburner;
popupId: string;
}
export function TravelPopup(props: IProps): React.ReactElement {
function travel(city: string): void {
props.bladeburner.city = city;
removePopup(props.popupId);
}
return (<>
<p>
Travel to a different city for your Bladeburner
activities. This does not cost any money. The city you are
in for your Bladeburner duties does not affect
your location in the game otherwise.
</p>
{BladeburnerConstants.CityNames.map(city => {
// Reusing this css class...it adds a border and makes it
// so that background color changes when you hover
return <div key={city} className="cmpy-mgmt-find-employee-option"
onClick={() => travel(city)}>
{city}
</div>})}
</>);
}

View File

@@ -147,7 +147,7 @@ export class Roulette extends Game<IProps, IState> {
componentDidMount(): void {
this.interval = setInterval(this.step, 50);
this.interval = window.setInterval(this.step, 50);
}
step(): void {

View File

@@ -87,7 +87,7 @@ export class SlotMachine extends Game<IProps, IState> {
}
componentDidMount(): void {
this.interval = setInterval(this.step, 50);
this.interval = window.setInterval(this.step, 50);
}
step(): void {

View File

@@ -6,7 +6,7 @@
import { IMap } from "./types";
export const CONSTANTS: IMap<any> = {
Version: "0.52.3",
Version: "0.52.9",
// Speed (in ms) at which the main loop is updated
_idleSpeed: 200,
@@ -228,26 +228,40 @@ export const CONSTANTS: IMap<any> = {
TotalNumBitNodes: 24,
LatestUpdate: `
v0.52.3 - 2021-07-15 Gangs were OP (hydroflame)
v0.52.9 - 2021-07-27 Less lag! (hydroflame & community)
-------------------------------------------
** Gang **
** Active Scripts page **
* Significant rework. Ascension is now based on exp gained.
* All upgrades give exp bonuses.
* Maximum gang members reduced to 12.
* Respect required to recruit sharply increased.
* Rewritten in React, the UI should be smoother and less laggy now.
* Now less laggy, has pagination.
** Infiltration **
** File diagnostic **
* Now isTrusted protected.
* Added a popup found under options that shows the files you own and how
large they are. This help find bugs and leftover massive logs files.
** Corporation **
* Added safeguard against a very specific bug that causes NaN money. I'm
still not sure what the root cause is but it should prevent corp from
breaking.
** Netscript **
* tprintf is a new function that doesn't print the filename.
** Misc. **
* Many UI element are now "noselect" protected.
* Fixed an issue where you could join the same faction twice via script and
UI simultaneously.
* Factions list screen converted to React.
* Infiltration kills you if you try to automate it. (@threehams)
* Fix beautify button not working
* Added bladeburner_analysis_mult to getPlayer() (@brusby)
* Fixed joining bladeburner via netscript functions. (@omuretsu)
* All bladeburner actions are click-to-copy
* nerf noodle bar
`,
/*
*/
}

View File

@@ -119,7 +119,7 @@ function Industry(params={}) {
};
this.name = params.name ? params.name : 0;
this.type = params.type ? params.type : 0;
this.type = params.type ? params.type : Industries.Agriculture;
this.sciResearch = new Material({name: "Scientific Research"});
this.researched = {}; // Object of acquired Research. Keys = research name
@@ -958,7 +958,6 @@ Industry.prototype.processProducts = function(marketCycles=1, corporation) {
const mgmtProd = office.employeeProd[EmployeePositions.Management];
const opProd = office.employeeProd[EmployeePositions.Operations];
const total = engrProd + mgmtProd + opProd;
if (total <= 0) { break; }
// Management is a multiplier for the production from Engineers
@@ -1113,8 +1112,13 @@ Industry.prototype.processProduct = function(marketCycles=1, product, corporatio
} else if (product.marketTa1) {
sCost = product.pCost + markupLimit;
} else if (isString(product.sCost)) {
if(product.mku === 0) {
console.error(`mku is zero, reverting to 1 to avoid Infinity`);
product.mku = 1;
}
sCost = product.sCost.replace(/MP/g, product.pCost + product.rat / product.mku);
sCost = eval(sCost);
} else {
sCost = product.sCost;
}
@@ -1397,11 +1401,12 @@ Industry.prototype.createResearchBox = function() {
researchTree.research(allResearch[i]);
this.researched[allResearch[i]] = true;
const researchBox = this.createResearchBox();
dialogBoxCreate(`Researched ${allResearch[i]}. It may take a market cycle ` +
`(~${SecsPerMarketCycle} seconds) before the effects of ` +
`the Research apply.`);
return this.createResearchBox();
return researchBox;
} else {
dialogBoxCreate(`You do not have enough Scientific Research for ${research.name}`);
}
@@ -1439,11 +1444,11 @@ Industry.prototype.createResearchBox = function() {
}
Industry.prototype.toJSON = function() {
return Generic_toJSON("Industry", this);
return Generic_toJSON("Industry", this);
}
Industry.fromJSON = function(value) {
return Generic_fromJSON(Industry, value.data);
return Generic_fromJSON(Industry, value.data);
}
Reviver.constructors.Industry = Industry;
@@ -1600,11 +1605,11 @@ Employee.prototype.createUI = function(panel, corporation, industry) {
}
Employee.prototype.toJSON = function() {
return Generic_toJSON("Employee", this);
return Generic_toJSON("Employee", this);
}
Employee.fromJSON = function(value) {
return Generic_fromJSON(Employee, value.data);
return Generic_fromJSON(Employee, value.data);
}
Reviver.constructors.Employee = Employee;
@@ -1899,11 +1904,11 @@ OfficeSpace.prototype.unassignEmployeeFromJob = function(job) {
}
OfficeSpace.prototype.toJSON = function() {
return Generic_toJSON("OfficeSpace", this);
return Generic_toJSON("OfficeSpace", this);
}
OfficeSpace.fromJSON = function(value) {
return Generic_fromJSON(OfficeSpace, value.data);
return Generic_fromJSON(OfficeSpace, value.data);
}
Reviver.constructors.OfficeSpace = OfficeSpace;
@@ -1941,6 +1946,14 @@ function Corporation(params={}) {
this.state = new CorporationState();
}
Corporation.prototype.addFunds = function(amt) {
if(!isFinite(amt)) {
console.error('Trying to add invalid amount of funds. Report to a developper.');
return;
}
this.funds = this.funds.plus(amt);
}
Corporation.prototype.getState = function() {
return this.state.getState();
}
@@ -1980,7 +1993,7 @@ Corporation.prototype.process = function() {
});
var profit = this.revenue.minus(this.expenses);
const cycleProfit = profit.times(marketCycles * SecsPerMarketCycle);
if (isNaN(this.funds)) {
if (isNaN(this.funds) || this.funds === Infinity || this.funds === -Infinity) {
dialogBoxCreate("There was an error calculating your Corporations funds and they got reset to 0. " +
"This is a bug. Please report to game developer.<br><br>" +
"(Your funds have been set to $150b for the inconvenience)");
@@ -1999,10 +2012,10 @@ Corporation.prototype.process = function() {
const profit = this.numShares * dividendsPerShare * (1 - (this.dividendTaxPercentage / 100));
Player.gainMoney(profit);
Player.recordMoneySource(profit, "corporation");
this.funds = this.funds.plus(retainedEarnings);
this.addFunds(retainedEarnings);
}
} else {
this.funds = this.funds.plus(cycleProfit);
this.addFunds(cycleProfit);
}
this.updateSharePrice();
@@ -2069,7 +2082,7 @@ Corporation.prototype.getInvestment = function() {
noBtn.innerHML = "Reject";
yesBtn.addEventListener("click", () => {
++this.fundingRound;
this.funds = this.funds.plus(funding);
this.addFunds(funding);
this.numShares -= investShares;
this.rerender();
return yesNoBoxClose();
@@ -2123,7 +2136,7 @@ Corporation.prototype.goPublic = function() {
this.sharePrice = initialSharePrice;
this.issuedShares = numShares;
this.numShares -= numShares;
this.funds = this.funds.plus(numShares * initialSharePrice);
this.addFunds(numShares * initialSharePrice);
this.rerender();
removeElementById(goPublicPopupId);
dialogBoxCreate(`You took your ${this.name} public and earned ` +
@@ -2376,11 +2389,11 @@ Corporation.prototype.clearUI = function() {
}
Corporation.prototype.toJSON = function() {
return Generic_toJSON("Corporation", this);
return Generic_toJSON("Corporation", this);
}
Corporation.fromJSON = function(value) {
return Generic_fromJSON(Corporation, value.data);
return Generic_fromJSON(Corporation, value.data);
}
Reviver.constructors.Corporation = Corporation;

View File

@@ -187,6 +187,11 @@ export class Product {
this.calculateRating(industry);
const advMult = 1 + (Math.pow(this.advCost, 0.1) / 100);
this.mku = 100 / (advMult * Math.pow((this.qlt + 0.001), 0.65) * (busRatio + mgmtRatio));
// I actually don't understand well enough to know if this is right.
// I'm adding this to prevent a crash.
if(this.mku === 0) this.mku = 1;
this.dmd = industry.awareness === 0 ? 20 : Math.min(100, advMult * (100 * (industry.popularity / industry.awareness)));
this.cmp = getRandomInt(0, 70);

View File

@@ -14,7 +14,7 @@ import { AllServers } from "./Server/AllServers";
import { GetServerByHostname } from "./Server/ServerHelpers";
import { hackWorldDaemon } from "./RedPill";
import { StockMarket } from "./StockMarket/StockMarket";
import { Bladeburner } from "./Bladeburner";
import { Bladeburner } from "./Bladeburner/Bladeburner";
import { Stock } from "./StockMarket/Stock";
import { Engine } from "./engine";
import { saveObject } from "./SaveObject";
@@ -502,7 +502,7 @@ class DevMenuComponent extends Component {
modifyBladeburnerRank(modify) {
return function(rank) {
if (Player.bladeburner) {
Player.bladeburner.changeRank(rank*modify);
Player.bladeburner.changeRank(Player, rank*modify);
}
}
}
@@ -514,7 +514,7 @@ class DevMenuComponent extends Component {
addTonsBladeburnerRank() {
if (Player.bladeburner) {
Player.bladeburner.changeRank(tonsP);
Player.bladeburner.changeRank(Player, tonsP);
}
}

View File

@@ -0,0 +1,68 @@
import React from "react";
import { AllServers } from "../Server/AllServers";
import { Script } from "../Script/Script";
import { TextFile } from "../TextFile";
import { Accordion } from "../ui/React/Accordion";
import { numeralWrapper } from "../ui/numeralFormat";
interface IServerProps {
ip: string;
}
export function ServerAccordion(props: IServerProps): React.ReactElement {
const server = AllServers[props.ip];
let totalSize = 0;
for(const f of server.scripts) {
totalSize += f.code.length;
}
for(const f of server.textFiles) {
totalSize += f.text.length;
}
if(totalSize === 0) {
return <></>
}
interface File {
name: string;
size: number;
}
const files: File[] = [];
for(const f of server.scripts) {
files.push({name: f.filename, size: f.code.length});
}
for(const f of server.textFiles) {
files.push({name: f.fn, size: f.text.length});
}
files.sort((a: File, b: File): number => b.size-a.size);
return <Accordion
headerContent={<>{server.hostname} ({numeralWrapper.formatBigNumber(totalSize)}b)</>}
panelContent={<ul>
{files.map((file: File) => <li key={file.name}>{file.name}: {numeralWrapper.formatBigNumber(file.size)}b</li>) }
</ul>}
/>
}
interface IProps {}
export function FileDiagnosticPopup(props: IProps): React.ReactElement {
const ips: string[] = [];
for(const ip of Object.keys(AllServers)) {
ips.push(ip);
}
return (<>
<p>
Welcome to the file diagnostic! If your save file is really big it's
likely because you have too many text/scripts. This tool can help you
narrow down where they are.
</p>
{ips.map((ip: string) => <ServerAccordion key={ip} ip={ip} />)}
</>);
}

View File

@@ -28,8 +28,7 @@ export function FactionList(props: IProps): React.ReactElement {
<p>Lists all factions you have joined</p>
<br />
<ul>
{props.player.factions.map((faction: string) =>
<li key={faction}><a
{props.player.factions.map((faction: string) => <li key={faction}><a
className="a-link-button"
onClick={() => openFaction(faction)}
style={{padding:"4px", margin:"4px", display:"inline-block"}}>{faction}
@@ -39,8 +38,7 @@ export function FactionList(props: IProps): React.ReactElement {
<h1>Outstanding Faction Invitations</h1>
<p style={{width: '70%'}}>Lists factions you have been invited to. You can accept these faction invitations at any time.</p>
<ul>
{props.player.factionInvitations.map((faction: string) =>
<li key={faction} style={{padding:"6px", margin:"6px"}}>
{props.player.factionInvitations.map((faction: string) => <li key={faction} style={{padding:"6px", margin:"6px"}}>
<p style={{display:"inline", margin:"4px", padding:"4px"}}>{faction}</p>
<a
className="a-link-button"

1
src/Fconf/Fconf.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export declare function parseFconfSettings(config: string): void;

View File

@@ -8,8 +8,6 @@
import { Faction } from "../Faction/Faction";
import { Factions } from "../Faction/Factions";
import { numeralWrapper } from "../ui/numeralFormat";
import { dialogBoxCreate } from "../../utils/DialogBox";
import {
Reviver,
@@ -122,7 +120,7 @@ export class Gang {
moneyGains += (this.members[i].calculateMoneyGain(this));
const wantedLevelGain = this.members[i].calculateWantedLevelGain(this);
wantedLevelGains += wantedLevelGain;
if(wantedLevelGain < 0) justice++; // this member is lowering wanted.
if(this.members[i].getTask().baseWanted < 0) justice++; // this member is lowering wanted.
}
this.respectGainRate = respectGains;
this.wantedGainRate = wantedLevelGains;
@@ -323,7 +321,7 @@ export class Gang {
this.members[i].task !== "Territory Warfare") continue;
memberTotal += this.members[i].calculatePower();
}
return (0.015 * this.getTerritory() * memberTotal);
return (0.015 * Math.max(0.002, this.getTerritory()) * memberTotal);
}

View File

@@ -4,7 +4,6 @@ import { GangMemberUpgrade } from "./GangMemberUpgrade";
import { GangMemberUpgrades } from "./GangMemberUpgrades";
import { IAscensionResult } from "./IAscensionResult";
import { IPlayer } from "../PersonObjects/IPlayer";
import { GangConstants } from "./data/Constants";
import { AllGangs } from "./AllGangs";
import { IGang } from "./IGang";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
@@ -122,7 +121,7 @@ export class GangMember {
(task.chaWeight/100) * this.cha;
statWeight -= (4 * task.difficulty);
if (statWeight <= 0) return 0;
const territoryMult = Math.pow(AllGangs[gang.facName].territory * 100, task.territory.respect) / 100;
const territoryMult = Math.max(0.005, Math.pow(AllGangs[gang.facName].territory * 100, task.territory.respect) / 100);
if (isNaN(territoryMult) || territoryMult <= 0) return 0;
const respectMult = gang.getWantedPenalty();
return 11 * task.baseRespect * statWeight * territoryMult * respectMult;
@@ -139,7 +138,7 @@ export class GangMember {
(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;
const territoryMult = Math.max(0.005, Math.pow(AllGangs[gang.facName].territory * 100, task.territory.wanted) / 100);
if (isNaN(territoryMult) || territoryMult <= 0) return 0;
if (task.baseWanted < 0) {
return 0.4 * task.baseWanted * statWeight * territoryMult;
@@ -160,9 +159,10 @@ export class GangMember {
(task.dexWeight/100) * this.dex +
(task.agiWeight/100) * this.agi +
(task.chaWeight/100) * this.cha;
statWeight -= (3.2 * task.difficulty);
if (statWeight <= 0) return 0;
const territoryMult = Math.pow(AllGangs[gang.facName].territory * 100, task.territory.money) / 100;
const territoryMult = Math.max(0.005, Math.pow(AllGangs[gang.facName].territory * 100, task.territory.money) / 100);
if (isNaN(territoryMult) || territoryMult <= 0) return 0;
const respectMult = gang.getWantedPenalty();
return 5 * task.baseMoney * statWeight * territoryMult * respectMult;

View File

@@ -42,6 +42,6 @@ export function TaskSelector(props: IProps): React.ReactElement {
<option key={0} value={"---"}>---</option>
{tasks.map((task: string, i: number) => <option key={i+1} value={task}>{task}</option>)}
</select>
<div>{StatsTable(data, null)}</div>
<div>{StatsTable(data)}</div>
</>);
}

View File

@@ -542,7 +542,7 @@ export function purchaseHashUpgrade(upgName, upgTarget) {
case "Exchange for Bladeburner Rank": {
// This will throw if player isnt in Bladeburner
try {
Player.bladeburner.changeRank(upg.value);
Player.bladeburner.changeRank(Player, upg.value);
} catch(e) {
Player.hashManager.refundUpgrade(upgName);
return false;

View File

@@ -8,6 +8,7 @@ export interface IConstructorParams {
hasTargetServer?: boolean;
name: string;
value: number;
effectText?: (level: number) => JSX.Element | null;
}
export class HashUpgrade {
@@ -45,6 +46,7 @@ export class HashUpgrade {
constructor(p: IConstructorParams) {
if (p.cost != null) { this.cost = p.cost; }
if (p.effectText != null) { this.effectText = p.effectText; }
this.costPerLevel = p.costPerLevel;
this.desc = p.desc;
@@ -53,6 +55,9 @@ export class HashUpgrade {
this.value = p.value;
}
// Functions that returns the UI element to display the effect of this upgrade.
effectText: (level: number) => JSX.Element | null = () => null;
getCost(levels: number): number {
if (typeof this.cost === "number") { return this.cost; }

View File

@@ -1,5 +1,8 @@
// Metadata used to construct all Hash Upgrades
import React from "react";
import { IConstructorParams } from "../HashUpgrade";
import { numeralWrapper } from "../../ui/numeralFormat";
import { Money } from "../../ui/React/Money";
export const HashUpgradesMetadata: IConstructorParams[] = [
{
@@ -7,12 +10,14 @@ export const HashUpgradesMetadata: IConstructorParams[] = [
costPerLevel: 4,
desc: "Sell hashes for $1m",
name: "Sell for Money",
effectText: (level: number): JSX.Element | null => (<>Sold for {Money(1e6*level)}</>),
value: 1e6,
},
{
costPerLevel: 100,
desc: "Sell hashes for $1b in Corporation funds",
name: "Sell for Corporation Funds",
effectText: (level: number): JSX.Element | null => (<>Sold for {Money(1e9*level)} Corporation funds.</>),
value: 1e9,
},
{
@@ -38,6 +43,7 @@ export const HashUpgradesMetadata: IConstructorParams[] = [
desc: "Use hashes to improve the experience earned when studying at a university by 20%. " +
"This effect persists until you install Augmentations",
name: "Improve Studying",
//effectText: (level: number) => JSX.Element | null = <>Improves studying by ${level*20}%</>,
value: 20, // Improves studying by value%
},
{
@@ -45,30 +51,35 @@ export const HashUpgradesMetadata: IConstructorParams[] = [
desc: "Use hashes to improve the experience earned when training at the gym by 20%. This effect " +
"persists until you install Augmentations",
name: "Improve Gym Training",
effectText: (level: number): JSX.Element | null => (<>Improves training by ${level*20}%</>),
value: 20, // Improves training by value%
},
{
costPerLevel: 200,
desc: "Exchange hashes for 1k Scientific Research in all of your Corporation's Industries",
name: "Exchange for Corporation Research",
effectText: (level: number): JSX.Element | null => (<>Acquired a total of {level}k Scientific Research in your industries.</>),
value: 1000,
},
{
costPerLevel: 250,
desc: "Exchange hashes for 100 Bladeburner Rank",
name: "Exchange for Bladeburner Rank",
effectText: (level: number): JSX.Element | null => (<>Acquired a total of {numeralWrapper.format(100*level, '0a')} Bladeburner rank</>),
value: 100,
},
{
costPerLevel: 250,
desc: "Exchanges hashes for 10 Bladeburner Skill Points",
name: "Exchange for Bladeburner SP",
effectText: (level: number): JSX.Element | null => (<>Acquired a total of {numeralWrapper.format(10*level, '0a')} Bladeburner Skill Points</>),
value: 10,
},
{
costPerLevel: 200,
desc: "Generate a random Coding Contract somewhere on the network",
name: "Generate Coding Contract",
effectText: (level: number): JSX.Element | null => (<>Generated {level} contracts.</>),
value: 1,
},
]

View File

@@ -11,7 +11,6 @@ import { Player } from "../../Player";
import { numeralWrapper } from "../../ui/numeralFormat";
import { PopupCloseButton } from "../../ui/React/PopupCloseButton";
import { ServerDropdown,
ServerType } from "../../ui/React/ServerDropdown"
@@ -24,7 +23,7 @@ class HashUpgrade extends React.Component {
super(props);
this.state = {
selectedServer: "foodnstuff",
selectedServer: "ecorp",
}
this.changeTargetServer = this.changeTargetServer.bind(this);
@@ -54,6 +53,8 @@ class HashUpgrade extends React.Component {
const hashManager = this.props.hashManager;
const upg = this.props.upg;
const cost = hashManager.getUpgradeCost(upg.name);
const level = hashManager.upgrades[upg.name];
const effect = upg.effectText(level);
// Purchase button
const canPurchase = hashManager.hashes >= cost;
@@ -63,11 +64,13 @@ class HashUpgrade extends React.Component {
return (
<div className={"bladeburner-action"}>
<CopyableText value={upg.name} />
<p>Cost: {Hashes(cost)}</p>
<p>Cost: {Hashes(cost)}, Bought: {level} times</p>
<p>{upg.desc}</p>
<button className={btnClass} onClick={this.purchase}>
Purchase
</button>
{level > 0 && effect && <p>{effect}</p>}
{
upg.hasTargetServer &&
<ServerDropdown
@@ -119,7 +122,6 @@ export class HashUpgradePopup extends React.Component {
return (
<div>
<PopupCloseButton popup={this.props.popupId} text={"Close"} />
<p>Spend your hashes on a variety of different upgrades</p>
<p>Hashes: {numeralWrapper.formatHashes(this.state.totalHashes)}</p>
{upgradeElems}

View File

@@ -13,4 +13,5 @@ export interface IEngine {
loadMissionContent: () => void;
loadResleevingContent: () => void;
loadStockMarketContent: () => void;
loadTerminalContent: () => void;
}

View File

@@ -46,7 +46,7 @@ export function BackwardGame(props: IMinigameProps): React.ReactElement {
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1 className={"noselect"}>Type it backward</h1>
<KeyHandler onKeyDown={press} />
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
<Grid item xs={6}>
<p style={{transform: 'scaleX(-1)'}}>{answer}</p>

View File

@@ -78,7 +78,7 @@ export function BracketGame(props: IMinigameProps): React.ReactElement {
<Grid item xs={12}>
<h1 className={"noselect"}>Close the brackets</h1>
<p style={{fontSize: '5em'}}>{`${left}${right}`}<BlinkingCursor /></p>
<KeyHandler onKeyDown={press} />
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
</Grid>)
}

View File

@@ -51,7 +51,7 @@ export function BribeGame(props: IMinigameProps): React.ReactElement {
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1>Say something nice about the guard.</h1>
<KeyHandler onKeyDown={press} />
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
<Grid item xs={6}>
<h2 style={{fontSize: "2em"}}></h2>

View File

@@ -47,7 +47,7 @@ export function CheatCodeGame(props: IMinigameProps): React.ReactElement {
<Grid item xs={12}>
<h1 className={"noselect"}>Enter the Code!</h1>
<p style={{fontSize: '5em'}}>{code[index]}</p>
<KeyHandler onKeyDown={press} />
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
</Grid>)
}

View File

@@ -86,7 +86,7 @@ export function Cyberpunk2077Game(props: IMinigameProps): React.ReactElement {
return <span key={`${x}${y}`} style={{fontSize: fontSize, color: 'blue'}}>{cell}&nbsp;</span>
return <span key={`${x}${y}`} style={{fontSize: fontSize}}>{cell}&nbsp;</span>
})}</pre><br /></div>)}
<KeyHandler onKeyDown={press} />
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
</Grid>)
}

View File

@@ -85,10 +85,13 @@ export function Game(props: IProps): React.ReactElement {
})
}
function failure(): void {
function failure(options?: { automated: boolean }): void {
setStage(Stage.Countdown);
pushResult(false);
if(props.Player.takeDamage(props.StartingDifficulty*3)) {
// Kill the player immediately if they use automation, so
// it's clear they're not meant to
const damage = options?.automated ? props.Player.hp : props.StartingDifficulty*3;
if(props.Player.takeDamage(damage)) {
const menu = document.getElementById("mainmenu-container");
if(menu === null) throw new Error("mainmenu-container not found");
menu.style.visibility = "visible";

View File

@@ -1,5 +1,8 @@
export interface IMinigameProps {
onSuccess: () => void;
onFailure: () => void;
onFailure: (options?: {
/** Failed due to using untrusted events (automation) */
automated: boolean;
}) => void;
difficulty: number;
}

View File

@@ -14,13 +14,6 @@ interface IProps {
cancel: () => void;
}
function diffStr(d: number): string {
if(d<=0.5) return "trivial";
if(d<=1.5) return "normal";
if(d<=2.5) return "hard";
return "impossible";
}
function arrowPart(color: string, length: number): JSX.Element {
let arrow = "";
if(length <= 0) length = 0;

View File

@@ -2,6 +2,7 @@ import React, { useEffect } from 'react';
interface IProps {
onKeyDown: (event: React.KeyboardEvent<HTMLElement>) => void;
onFailure: (options?: { automated: boolean }) => void;
}
export function KeyHandler(props: IProps): React.ReactElement {
@@ -9,7 +10,12 @@ export function KeyHandler(props: IProps): React.ReactElement {
useEffect(() => elem.focus());
function onKeyDown(event: React.KeyboardEvent<HTMLElement>): void {
if(!event.isTrusted) return;
console.log("isTrusted?", event.isTrusted)
if(!event.isTrusted) {
console.log("untrusted event!")
props.onFailure({ automated: true });
return;
}
props.onKeyDown(event);
}

View File

@@ -94,7 +94,7 @@ export function MinesweeperGame(props: IMinigameProps): React.ReactElement {
return <span key={x}>[&nbsp;]&nbsp;</span>
}
})}</pre><br /></div>)}
<KeyHandler onKeyDown={press} />
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
</Grid>)
}

View File

@@ -39,9 +39,9 @@ export function SlashGame(props: IMinigameProps): React.ReactElement {
useEffect(() => {
let id2 = -1;
const id = setTimeout(() => {
const id = window.setTimeout(() => {
setGuarding(false);
id2 = setTimeout(()=>setGuarding(true), difficulty.window)
id2 = window.setTimeout(()=>setGuarding(true), difficulty.window)
}, Math.random()*3250+1500);
return () => {
clearInterval(id);
@@ -54,7 +54,7 @@ export function SlashGame(props: IMinigameProps): React.ReactElement {
<Grid item xs={12}>
<h1 className={"noselect"}>Slash when his guard is down!</h1>
<p style={{fontSize: '5em'}}>{guarding ? "!Guarding!" : "!ATTACKING!"}</p>
<KeyHandler onKeyDown={press} />
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
</Grid>)
}

View File

@@ -111,7 +111,7 @@ export function WireCuttingGame(props: IMinigameProps): React.ReactElement {
})}
</pre>
</div>)}
<KeyHandler onKeyDown={press} />
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
</Grid>)
}

3
src/InteractiveTutorial.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
export declare function iTutorialNextStep(): void;
export declare const ITutorial: {isRunning: boolean, currStep: number};
export declare const iTutorialSteps: {[key: string]: number};

View File

@@ -204,7 +204,7 @@ export const LocationsMetadata: IConstructorParams[] = [
startingSecurityLevel: 2.5,
},
name: LocationName.NewTokyoNoodleBar,
types: [LocationType.Company],
types: [LocationType.Company, LocationType.Special],
},
{
city: CityName.NewTokyo,

View File

@@ -107,6 +107,8 @@ export class CompanyLocation extends React.Component<IProps, IState> {
employedHere: false,
}
this.props.p.location = props.locName;
this.checkIfEmployedHere(false);
}

View File

@@ -45,6 +45,7 @@ export class SpecialLocation extends React.Component<IProps, IState> {
this.btnStyle = { display: "block" };
this.renderNoodleBar = this.renderNoodleBar.bind(this);
this.createCorporationPopup = this.createCorporationPopup.bind(this);
this.handleBladeburner = this.handleBladeburner.bind(this);
this.handleResleeving = this.handleResleeving.bind(this);
@@ -107,6 +108,18 @@ export class SpecialLocation extends React.Component<IProps, IState> {
)
}
renderNoodleBar(): React.ReactNode {
function EatNoodles(): void {
dialogBoxCreate(<>You ate some delicious noodles and feel refreshed.</>)
}
return (<StdButton
onClick={EatNoodles}
style={this.btnStyle}
text={'Eat noodles'}
/>)
}
renderCreateCorporation(): React.ReactNode {
if (!this.props.p.canAccessCorporation()) {
return <>
@@ -145,6 +158,9 @@ export class SpecialLocation extends React.Component<IProps, IState> {
case LocationName.Sector12NSA: {
return this.renderBladeburner();
}
case LocationName.NewTokyoNoodleBar: {
return this.renderNoodleBar();
}
default:
console.error(`Location ${this.props.loc.name} doesn't have any special properties`);
break;

View File

@@ -7,7 +7,7 @@ import { Player } from "../Player";
import { redPillFlag } from "../RedPill";
import { GetServerByHostname } from "../Server/ServerHelpers";
import { Settings } from "../Settings/Settings";
import { dialogBoxCreate, dialogBoxOpened} from "../../utils/DialogBox";
import { dialogBoxCreate} from "../../utils/DialogBox";
import { Reviver } from "../../utils/JSONReviver";
//Sends message to player, including a pop up
@@ -59,12 +59,10 @@ function checkForMessagesToSend() {
}
if (redpill && redpillOwned && Player.sourceFiles.length === 0 && !redPillFlag && !inMission) {
if (!dialogBoxOpened) {
sendMessage(redpill, true);
}
sendMessage(redpill, true);
} else if (redpill && redpillOwned) {
//If player has already destroyed a BitNode, message is not forced
if (!redPillFlag && !inMission && !dialogBoxOpened) {
if (!redPillFlag && !inMission) {
sendMessage(redpill);
}
} else if (jumper0 && !jumper0.recvd && Player.hacking_skill >= 25) {

View File

@@ -36,7 +36,7 @@ export const Milestones: Milestone[] = [
},
},
{
title: "Install all the Augmentations from CSEC",
title: "Install all the Augmentations from CyberSec",
fulfilled: (p: IPlayer): boolean => {
return allFactionAugs(p, Factions["CyberSec"]);
},

2
src/NetscriptFunctions.d.ts vendored Normal file
View File

@@ -0,0 +1,2 @@
import { WorkerScript } from "./Netscript/WorkerScript";
export declare function NetscriptFunctions(workerScript: WorkerScript): any;

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