Compare commits

..

86 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
hydroflame
27ee65f524 Merge pull request #1051 from danielyxie/dev
hotfix 0 territory being softlocked.
2021-08-17 17:47:58 -04:00
hydroflame
1d0f193c34 Merge pull request #1050 from danielyxie/dev
hotfix blocked in Gang
2021-08-17 17:14:11 -04:00
hydroflame
08908c87ea Merge pull request #1048 from danielyxie/dev
Hotfix weird bladeburner ui bug
2021-08-15 17:14:38 -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
101 changed files with 14510 additions and 13893 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([856,0]),o()}({793:function(n,t,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){},856:function(n,t,o){"use strict";o.r(t);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),o(793)}});
!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,133 @@
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)
-------------------------------------------

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.4'
release = '0.52.9'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

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

@@ -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 -->
@@ -277,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">
@@ -586,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">
@@ -598,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.4"
"version": "0.52.9"
}

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

@@ -606,13 +606,11 @@ export class Bladeburner implements IBladeburner {
const flag = args[1];
if (flag.toLowerCase() === "status") {
this.postToConsole("Automation: " + (this.automateEnabled ? "enabled" : "disabled"));
if (this.automateEnabled) {
this.postToConsole("When your stamina drops to " + formatNumber(this.automateThreshLow, 0) +
", you will automatically switch to " + this.automateActionLow.name +
". When your stamina recovers to " +
formatNumber(this.automateThreshHigh, 0) + ", you will automatically " +
"switch to " + this.automateActionHigh.name + ".");
}
this.postToConsole("When your stamina drops to " + formatNumber(this.automateThreshLow, 0) +
", you will automatically switch to " + this.automateActionLow.name +
". When your stamina recovers to " +
formatNumber(this.automateThreshHigh, 0) + ", you will automatically " +
"switch to " + this.automateActionHigh.name + ".");
} else if (flag.toLowerCase().includes("en")) {
if (!(this.automateActionLow instanceof ActionIdentifier) ||
@@ -1788,18 +1786,18 @@ export class Bladeburner implements IBladeburner {
}
}
getActionEstimatedSuccessChanceNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): number {
getActionEstimatedSuccessChanceNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): number[] {
const errorLogText = `Invalid action: type='${type}' name='${name}'`
const actionId = this.getActionIdFromTypeAndName(type, name);
if (actionId == null) {
workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText);
return -1;
return [-1, -1];
}
const actionObj = this.getActionObject(actionId);
if (actionObj == null) {
workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText);
return -1;
return [-1, -1];
}
switch (actionId.type) {
@@ -1807,16 +1805,17 @@ export class Bladeburner implements IBladeburner {
case ActionTypes["Operation"]:
case ActionTypes["BlackOp"]:
case ActionTypes["BlackOperation"]:
return actionObj.getSuccessChance(this, {est:true});
return actionObj.getEstSuccessChance(this);
case ActionTypes["Training"]:
case ActionTypes["Field Analysis"]:
case ActionTypes["FieldAnalysis"]:
return 1;
return [1, 1];
case ActionTypes["Recruitment"]:
return this.getRecruitmentSuccessChance(player);
const recChance = this.getRecruitmentSuccessChance(player);
return [recChance, recChance];
default:
workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText);
return -1;
return [-1, -1];
}
}

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

@@ -67,7 +67,7 @@ export interface IBladeburner {
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;
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;

View File

@@ -44,6 +44,6 @@ export function AllPages(props: IProps): React.ReactElement {
{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>
<span className="text">{stealthIcon} = This action requires stealth, {killIcon} = This action involves retirement</span>
</>);
}

View File

@@ -10,6 +10,8 @@ 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;
@@ -26,7 +28,7 @@ export function BlackOpElem(props: IProps): React.ReactElement {
}
const isActive = props.bladeburner.action.type === ActionTypes["BlackOperation"] && props.action.name === props.bladeburner.action.name;
const estimatedSuccessChance = props.action.getSuccessChance(props.bladeburner, {est:true});
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);
@@ -50,8 +52,8 @@ export function BlackOpElem(props: IProps): React.ReactElement {
return (<>
<h2 style={{display: 'inline-block'}}>
{isActive ?
<>{props.action.name} (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)})</> :
<>{props.action.name}</>
<><CopyableText value={props.action.name} /> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)})</> :
<CopyableText value={props.action.name} />
}
</h2>
{isActive ?
@@ -78,10 +80,10 @@ export function BlackOpElem(props: IProps): React.ReactElement {
Required Rank: {formatNumber(props.action.reqdRank, 0)}
</p>
<br />
<p style={{display:"inline-block"}}>
Estimated Success Chance: {formatNumber(estimatedSuccessChance*100, 1)}% {props.action.isStealth?stealthIcon:<></>}{props.action.isKill?killIcon:<></>}
<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)}
</p>
</pre>
</>);
}

View File

@@ -2,6 +2,7 @@ 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;

View File

@@ -26,8 +26,8 @@ export function Console(props: IProps): React.ReactElement {
// TODO: Figure out how to actually make the scrolling work correctly.
function scrollToBottom(): void {
if(lastRef.current)
lastRef.current.scrollTop = lastRef.current.scrollHeight;
if(!lastRef.current) return;
lastRef.current.scrollTop = lastRef.current.scrollHeight;
}
function rerender(): void {

View File

@@ -9,6 +9,8 @@ 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;
@@ -19,7 +21,8 @@ interface IProps {
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.getSuccessChance(props.bladeburner, {est:true});
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);
@@ -52,8 +55,8 @@ export function ContractElem(props: IProps): React.ReactElement {
return (<>
<h2 style={{display: 'inline-block'}}>
{isActive ?
<>{props.action.name} (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)})</> :
<>{props.action.name}</>
<><CopyableText value={props.action.name} /> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)})</> :
<CopyableText value={props.action.name} />
}
</h2>
{isActive ?
@@ -93,7 +96,7 @@ export function ContractElem(props: IProps): React.ReactElement {
<pre style={{display: 'inline-block'}}>
<span dangerouslySetInnerHTML={{__html: props.action.desc}} />
<br /><br />
Estimated success chance: {formatNumber(estimatedSuccessChance*100, 1)}% {props.action.isStealth?stealthIcon:<></>}${props.action.isKill?killIcon:<></>}<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 />
@@ -114,26 +117,3 @@ Failures: {props.action.failures}
onChange={onAutolevel}/>
</>);
}
/*
// Autolevel Checkbox
el.appendChild(createElement("br"));
var autolevelCheckboxId = "bladeburner-" + action.name + "-autolevel-checkbox";
el.appendChild(createElement("label", {
for:autolevelCheckboxId, innerText:"Autolevel: ",color:"white",
tooltip:"Automatically increase contract level when possible",
}));
const checkboxInput = createElement("input", {
type:"checkbox",
id: autolevelCheckboxId,
checked: action.autoLevel,
changeListener: () => {
action.autoLevel = checkboxInput.checked;
},
});
el.appendChild(checkboxInput);
*/

View File

@@ -1,9 +1,13 @@
import React, { useState } from "react";
import { ActionTypes } from "../data/ActionTypes";
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
import { formatNumber } from "../../../utils/StringHelperFunctions";
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;
@@ -15,6 +19,20 @@ 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)];
@@ -26,8 +44,8 @@ export function GeneralActionElem(props: IProps): React.ReactElement {
return (<>
<h2 style={{display: 'inline-block'}}>
{isActive ?
<>{props.action.name} (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)})</> :
<>{props.action.name}</>
<><CopyableText value={props.action.name} /> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)})</> :
<CopyableText value={props.action.name} />
}
</h2>
{isActive ?
@@ -42,6 +60,10 @@ export function GeneralActionElem(props: IProps): React.ReactElement {
</>}
<br />
<br />
<pre style={{display: 'inline-block'}} dangerouslySetInnerHTML={{__html: props.action.desc}}></pre>
<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

@@ -11,6 +11,8 @@ 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;
@@ -21,7 +23,7 @@ interface IProps {
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.getSuccessChance(props.bladeburner, {est:true});
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);
@@ -63,8 +65,8 @@ export function OperationElem(props: IProps): React.ReactElement {
return (<>
<h2 style={{display: 'inline-block'}}>
{isActive ?
<>{props.action.name} (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)})</> :
<>{props.action.name}</>
<><CopyableText value={props.action.name} /> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)})</> :
<CopyableText value={props.action.name} />
}
</h2>
{isActive ?
@@ -110,7 +112,7 @@ export function OperationElem(props: IProps): React.ReactElement {
<pre style={{display:"inline-block"}}>
<span dangerouslySetInnerHTML={{__html: props.action.desc}} />
<br /><br />
Estimated success chance: {formatNumber(estimatedSuccessChance*100, 1)}% {props.action.isStealth?stealthIcon:<></>}{props.action.isKill?killIcon:<></>}<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 />

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

@@ -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.4",
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.4 - 2021-07-19 Bladeburner in React (hydroflame)
v0.52.9 - 2021-07-27 Less lag! (hydroflame & community)
-------------------------------------------
** Bladeburner **
** Active Scripts page **
* The entire UI was rebuild in React. It should be more responsive
* Now less laggy, has pagination.
** Hacknet **
** File diagnostic **
* Displays how many time each hash upgrade was bought.
* Displays cummulative effect of the upgrade.
* Removed "Close" button from hash upgrade menu.
* 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. **
* 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)
* 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

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

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

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

View File

@@ -44,8 +44,6 @@ export class HashUpgrade {
// The meaning varies between different upgrades
value = 0;
effectText: (level: number) => JSX.Element | null = () => null;
constructor(p: IConstructorParams) {
if (p.cost != null) { this.cost = p.cost; }
if (p.effectText != null) { this.effectText = p.effectText; }
@@ -57,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

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

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

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

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

View File

@@ -138,7 +138,7 @@ import {
getStockMarket4SDataCost,
getStockMarket4STixApiCost,
} from "./StockMarket/StockMarketCosts";
import { isValidFilePath } from "./Terminal/DirectoryHelpers";
import { isValidFilePath, removeLeadingSlash } from "./Terminal/DirectoryHelpers";
import { TextFile, getTextFile, createTextFile } from "./TextFile";
import {
@@ -853,6 +853,8 @@ function NetscriptFunctions(workerScript) {
throw makeRuntimeErrorMsg("grow", `Invalid IP/hostname: ${ip}.`);
}
const host = getServer(workerScript.serverIp);
// No root access or skill level too low
const canHack = netscriptCanGrow(server);
if (!canHack.res) {
@@ -865,7 +867,7 @@ function NetscriptFunctions(workerScript) {
if (workerScript.env.stopFlag) {return Promise.reject(workerScript);}
const moneyBefore = server.moneyAvailable <= 0 ? 1 : server.moneyAvailable;
server.moneyAvailable += (1 * threads); // It can be grown even if it has no money
processSingleServerGrowth(server, threads, Player);
processSingleServerGrowth(server, threads, Player, host.cpuCores);
const moneyAfter = server.moneyAvailable;
workerScript.scriptRef.recordGrow(server.ip, threads);
var expGain = calculateHackingExpGain(server, Player) * threads;
@@ -896,7 +898,7 @@ function NetscriptFunctions(workerScript) {
if (ip === undefined) {
throw makeRuntimeErrorMsg("weaken", "Takes 1 argument.");
}
var server = getServer(ip);
const server = getServer(ip);
if (server == null) {
throw makeRuntimeErrorMsg("weaken", `Invalid IP/hostname: ${ip}`);
}
@@ -907,13 +909,15 @@ function NetscriptFunctions(workerScript) {
throw makeRuntimeErrorMsg("weaken", canHack.msg);
}
var weakenTime = calculateWeakenTime(server, Player);
const weakenTime = calculateWeakenTime(server, Player);
workerScript.log("weaken", `Executing on '${server.hostname}' in ${convertTimeMsToTimeElapsedString(weakenTime*1000, true)} (t=${numeralWrapper.formatThreads(threads)})`);
return netscriptDelay(weakenTime * 1000, workerScript).then(function() {
if (workerScript.env.stopFlag) {return Promise.reject(workerScript);}
server.weaken(CONSTANTS.ServerWeakenAmount * threads);
if (workerScript.env.stopFlag) return Promise.reject(workerScript);
const host = getServer(workerScript.serverIp);
const coreBonus = 1+(host.cpuCores-1)/16;
server.weaken(CONSTANTS.ServerWeakenAmount * threads * coreBonus);
workerScript.scriptRef.recordWeaken(server.ip, threads);
var expGain = calculateHackingExpGain(server, Player) * threads;
const expGain = calculateHackingExpGain(server, Player) * threads;
workerScript.log("weaken", `'${server.hostname}' security level weakened to ${server.hackDifficulty}. Gained ${numeralWrapper.formatExp(expGain)} hacking exp (t=${numeralWrapper.formatThreads(threads)})`);
workerScript.scriptRef.onlineExpGained += expGain;
Player.gainHackingExp(expGain);
@@ -932,6 +936,9 @@ function NetscriptFunctions(workerScript) {
}
post(`${workerScript.scriptRef.filename}: ${argsToString(arguments)}`);
},
tprintf: function(format, ...args) {
post(vsprintf(format, args));
},
clearLog: function() {
workerScript.scriptRef.clearLog();
},
@@ -2145,11 +2152,15 @@ function NetscriptFunctions(workerScript) {
}
return port.write(data);
} else if (isString(port)) { // Write to script or text file
const fn = port;
let fn = port;
if (!isValidFilePath(fn)) {
throw makeRuntimeErrorMsg("write", `Invalid filepath: ${fn}`);
}
if(fn.lastIndexOf("/") === 0) {
fn = removeLeadingSlash(fn);
}
// Coerce 'data' to be a string
try {
data = String(data);
@@ -3021,6 +3032,7 @@ function NetscriptFunctions(workerScript) {
has4SDataTixApi: Player.has4SDataTixApi,
bladeburner_max_stamina_mult: Player.bladeburner_max_stamina_mult,
bladeburner_stamina_gain_mult: Player.bladeburner_stamina_gain_mult,
bladeburner_analysis_mult: Player.bladeburner_analysis_mult,
bladeburner_success_chance_mult: Player.bladeburner_success_chance_mult,
bitNodeN: Player.bitNodeN,
totalPlaytime: Player.totalPlaytime,
@@ -3671,24 +3683,35 @@ function NetscriptFunctions(workerScript) {
dex: member.dex,
agi: member.agi,
cha: member.cha,
hack_exp: member.hack_exp,
str_exp: member.str_exp,
def_exp: member.def_exp,
dex_exp: member.dex_exp,
agi_exp: member.agi_exp,
cha_exp: member.cha_exp,
hack_mult: member.hack_mult,
str_mult: member.str_mult,
def_mult: member.def_mult,
dex_mult: member.dex_mult,
agi_mult: member.agi_mult,
cha_mult: member.cha_mult,
hack_asc_mult: member.hack_asc_mult,
str_asc_mult: member.str_asc_mult,
def_asc_mult: member.def_asc_mult,
dex_asc_mult: member.dex_asc_mult,
agi_asc_mult: member.agi_asc_mult,
cha_asc_mult: member.cha_asc_mult,
hack_asc_mult: member.calculateAscensionMult(member.hack_asc_points),
str_asc_mult: member.calculateAscensionMult(member.str_asc_points),
def_asc_mult: member.calculateAscensionMult(member.def_asc_points),
dex_asc_mult: member.calculateAscensionMult(member.dex_asc_points),
agi_asc_mult: member.calculateAscensionMult(member.agi_asc_points),
cha_asc_mult: member.calculateAscensionMult(member.cha_asc_points),
hack_asc_points: member.hack_asc_points,
str_asc_points: member.str_asc_points,
def_asc_points: member.def_asc_points,
dex_asc_points: member.dex_asc_points,
agi_asc_points: member.agi_asc_points,
cha_asc_points: member.cha_asc_points,
upgrades: member.upgrades.slice(),
augmentations: member.augmentations.slice(),
}
@@ -3855,7 +3878,7 @@ function NetscriptFunctions(workerScript) {
updateDynamicRam("startAction", getRamCost("bladeburner", "startAction"));
checkBladeburnerAccess("startAction");
try {
return Player.bladeburner.startActionNetscriptFn(type, name, workerScript);
return Player.bladeburner.startActionNetscriptFn(Player, type, name, workerScript);
} catch(e) {
throw makeRuntimeErrorMsg("bladeburner.startAction", e);
}
@@ -3874,7 +3897,7 @@ function NetscriptFunctions(workerScript) {
updateDynamicRam("getActionTime", getRamCost("bladeburner", "getActionTime"));
checkBladeburnerAccess("getActionTime");
try {
return Player.bladeburner.getActionTimeNetscriptFn(type, name, workerScript);
return Player.bladeburner.getActionTimeNetscriptFn(Player, type, name, workerScript);
} catch(e) {
throw makeRuntimeErrorMsg("bladeburner.getActionTime", e);
}
@@ -3883,7 +3906,7 @@ function NetscriptFunctions(workerScript) {
updateDynamicRam("getActionEstimatedSuccessChance", getRamCost("bladeburner", "getActionEstimatedSuccessChance"));
checkBladeburnerAccess("getActionEstimatedSuccessChance");
try {
return Player.bladeburner.getActionEstimatedSuccessChanceNetscriptFn(type, name, workerScript);
return Player.bladeburner.getActionEstimatedSuccessChanceNetscriptFn(Player, type, name, workerScript);
} catch(e) {
throw makeRuntimeErrorMsg("bladeburner.getActionEstimatedSuccessChance", e);
}
@@ -4046,7 +4069,7 @@ function NetscriptFunctions(workerScript) {
return true; // Already member
} else if (Player.strength >= 100 && Player.defense >= 100 &&
Player.dexterity >= 100 && Player.agility >= 100) {
Player.bladeburner = new Bladeburner();
Player.bladeburner = new Bladeburner(Player);
workerScript.log("joinBladeburnerDivision", "You have been accepted into the Bladeburner division");
const worldHeader = document.getElementById("world-menu-header");
@@ -4375,9 +4398,9 @@ function NetscriptFunctions(workerScript) {
checkFormulasAccess("basic.hackPercent", 5);
return calculatePercentMoneyHacked(server, player);
},
growPercent: function(server, threads, player) {
growPercent: function(server, threads, player, cores = 1) {
checkFormulasAccess("basic.growPercent", 5);
return calculateServerGrowth(server, threads, player);
return calculateServerGrowth(server, threads, player, cores);
},
hackTime: function(server, player) {
checkFormulasAccess("basic.hackTime", 5);
@@ -4492,17 +4515,19 @@ function NetscriptFunctions(workerScript) {
} else if(Array.isArray(d[1])) {
t = [String];
}
args['--'+d[0]] = t
const numDashes = d[0].length > 1 ? 2 : 1;
args['-'.repeat(numDashes)+d[0]] = t
}
const ret = libarg(args, {argv: workerScript.args});
for(const d of data) {
if(!ret.hasOwnProperty('--'+d[0])) ret[d[0]] = d[1];
if(!ret.hasOwnProperty('--'+d[0]) || !ret.hasOwnProperty('-'+d[0])) ret[d[0]] = d[1];
}
for(const key of Object.keys(ret)) {
if(!key.startsWith('--')) continue;
if(!key.startsWith('-')) continue;
const value = ret[key];
delete ret[key];
ret[key.slice(2)] = value;
const numDashes = key.length === 2 ? 1 : 2;
ret[key.slice(numDashes)] = value;
}
return ret;
},
@@ -4525,4 +4550,4 @@ function NetscriptFunctions(workerScript) {
return functions;
} // End NetscriptFunction()
export { NetscriptFunctions };
export { NetscriptFunctions };

View File

@@ -1742,6 +1742,7 @@ export function applyForJob(entryPosType, sing=false) {
}
this.jobs[company.name] = pos.name;
this.companyName = this.location;
document.getElementById("world-menu-header").click();
document.getElementById("world-menu-header").click();

View File

@@ -13,8 +13,6 @@ import {
ITutorial,
} from "../InteractiveTutorial";
import { Player } from "../Player";
import { AceEditor } from "../ScriptEditor/Ace";
import { CodeMirrorEditor } from "../ScriptEditor/CodeMirror";
import { CursorPositions } from "../ScriptEditor/CursorPositions";
import { AllServers } from "../Server/AllServers";
import { processSingleServerGrowth } from "../Server/ServerHelpers";
@@ -30,267 +28,6 @@ import { dialogBoxCreate } from "../../utils/DialogBox";
import { compareArrays } from "../../utils/helpers/compareArrays";
import { createElement } from "../../utils/uiHelpers/createElement";
var scriptEditorRamText = null;
export function scriptEditorInit() {
// Wrapper container that holds all the buttons below the script editor
const wrapper = document.getElementById("script-editor-buttons-wrapper");
if (wrapper == null) {
console.error("Could not find 'script-editor-buttons-wrapper'");
return false;
}
// Beautify button
const beautifyButton = createElement("button", {
class: "std-button",
display: "inline-block",
innerText: "Beautify",
clickListener:()=>{
let editor = getCurrentEditor();
if (editor != null) {
editor.beautifyScript();
}
return false;
},
});
// Text that displays RAM calculation
scriptEditorRamText = createElement("p", {
display:"inline-block", margin:"10px", id:"script-editor-status-text",
});
// Link to Netscript documentation
const documentationButton = createElement("a", {
class: "std-button",
display: "inline-block",
href:"https://bitburner.readthedocs.io/en/latest/index.html",
innerText:"Netscript Documentation",
target:"_blank",
});
// Save and Close button
const closeButton = createElement("button", {
class: "std-button",
display: "inline-block",
innerText: "Save & Close (Ctrl/Cmd + b)",
clickListener:()=>{
saveAndCloseScriptEditor();
return false;
},
});
// Add all buttons to the UI
wrapper.appendChild(beautifyButton);
wrapper.appendChild(closeButton);
wrapper.appendChild(scriptEditorRamText);
wrapper.appendChild(documentationButton);
// Initialize editors
const initParams = {
saveAndCloseFn: saveAndCloseScriptEditor,
quitFn: Engine.loadTerminalContent,
}
AceEditor.init(initParams);
CodeMirrorEditor.init(initParams);
// Setup the selector for which Editor to use
const editorSelector = document.getElementById("script-editor-option-editor");
if (editorSelector == null) {
console.error(`Could not find DOM Element for editor selector (id=script-editor-option-editor)`);
return false;
}
for (let i = 0; i < editorSelector.options.length; ++i) {
if (editorSelector.options[i].value === Settings.Editor) {
editorSelector.selectedIndex = i;
break;
}
}
editorSelector.onchange = () => {
const opt = editorSelector.value;
switch (opt) {
case EditorSetting.Ace: {
const codeMirrorCode = CodeMirrorEditor.getCode();
const codeMirrorFn = CodeMirrorEditor.getFilename();
AceEditor.create();
CodeMirrorEditor.setInvisible();
AceEditor.openScript(codeMirrorFn, codeMirrorCode);
break;
}
case EditorSetting.CodeMirror: {
const aceCode = AceEditor.getCode();
const aceFn = AceEditor.getFilename();
CodeMirrorEditor.create();
AceEditor.setInvisible();
CodeMirrorEditor.openScript(aceFn, aceCode);
break;
}
default:
console.error(`Unrecognized Editor Setting: ${opt}`);
return;
}
Settings.Editor = opt;
}
editorSelector.onchange(); // Trigger the onchange event handler
}
export function getCurrentEditor() {
switch (Settings.Editor) {
case EditorSetting.Ace:
return AceEditor;
case EditorSetting.CodeMirror:
return CodeMirrorEditor;
default:
throw new Error(`Invalid Editor Setting: ${Settings.Editor}`);
return null;
}
}
//Updates RAM usage in script
export async function updateScriptEditorContent() {
var filename = document.getElementById("script-editor-filename").value;
if (!isScriptFilename(filename)) {
scriptEditorRamText.innerText = "RAM: N/A";
return;
}
let code;
try {
code = getCurrentEditor().getCode();
} catch(e) {
scriptEditorRamText.innerText = "RAM: ERROR";
return;
}
var codeCopy = code.repeat(1);
var ramUsage = await calculateRamUsage(codeCopy, Player.getCurrentServer().scripts);
if (ramUsage > 0) {
scriptEditorRamText.innerText = "RAM: " + numeralWrapper.formatRAM(ramUsage);
} else {
switch (ramUsage) {
case RamCalculationErrorCode.ImportError:
scriptEditorRamText.innerText = "RAM: Import Error";
break;
case RamCalculationErrorCode.URLImportError:
scriptEditorRamText.innerText = "RAM: HTTP Import Error";
break;
case RamCalculationErrorCode.SyntaxError:
default:
scriptEditorRamText.innerText = "RAM: Syntax Error";
break;
}
}
}
//Define key commands in script editor (ctrl o to save + close, etc.)
$(document).keydown(function(e) {
if (Settings.DisableHotkeys === true) {return;}
if (routing.isOn(Page.ScriptEditor)) {
//Ctrl + b
if (e.keyCode == 66 && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
saveAndCloseScriptEditor();
}
}
});
function saveAndCloseScriptEditor() {
var filename = document.getElementById("script-editor-filename").value;
let code, cursor;
try {
code = getCurrentEditor().getCode();
cursor = getCurrentEditor().getCursor();
CursorPositions.saveCursor(filename, cursor);
} catch(e) {
dialogBoxCreate("Something went wrong when trying to save (getCurrentEditor().getCode() or getCurrentEditor().getCursor()). Please report to game developer with details");
return;
}
if (ITutorial.isRunning && ITutorial.currStep === iTutorialSteps.TerminalTypeScript) {
//Make sure filename + code properly follow tutorial
if (filename !== "n00dles.script") {
dialogBoxCreate("Leave the script name as 'n00dles'!");
return;
}
code = code.replace(/\s/g, "");
if (code.indexOf("while(true){hack('n00dles');}") == -1) {
dialogBoxCreate("Please copy and paste the code from the tutorial!");
return;
}
//Save the script
let s = Player.getCurrentServer();
for (var i = 0; i < s.scripts.length; i++) {
if (filename == s.scripts[i].filename) {
s.scripts[i].saveScript(getCurrentEditor().getCode(), Player.currentServer, Player.getCurrentServer().scripts);
Engine.loadTerminalContent();
return iTutorialNextStep();
}
}
// If the current script does NOT exist, create a new one
let script = new Script();
script.saveScript(getCurrentEditor().getCode(), Player.currentServer, Player.getCurrentServer().scripts);
s.scripts.push(script);
return iTutorialNextStep();
}
if (filename == "") {
dialogBoxCreate("You must specify a filename!");
return;
}
if (filename !== ".fconf" && !isValidFilePath(filename)) {
dialogBoxCreate("Script filename can contain only alphanumerics, hyphens, and underscores, and must end with an extension.");
return;
}
var s = Player.getCurrentServer();
if (filename === ".fconf") {
try {
parseFconfSettings(code);
} catch(e) {
dialogBoxCreate(`Invalid .fconf file: ${e}`);
return;
}
} else if (isScriptFilename(filename)) {
//If the current script already exists on the server, overwrite it
for (let i = 0; i < s.scripts.length; i++) {
if (filename == s.scripts[i].filename) {
s.scripts[i].saveScript(getCurrentEditor().getCode(), Player.currentServer, Player.getCurrentServer().scripts);
Engine.loadTerminalContent();
return;
}
}
//If the current script does NOT exist, create a new one
const script = new Script();
script.saveScript(getCurrentEditor().getCode(), Player.currentServer, Player.getCurrentServer().scripts);
s.scripts.push(script);
} else if (filename.endsWith(".txt")) {
for (let i = 0; i < s.textFiles.length; ++i) {
if (s.textFiles[i].fn === filename) {
s.textFiles[i].write(code);
Engine.loadTerminalContent();
return;
}
}
const textFile = new TextFile(filename, code);
s.textFiles.push(textFile);
} else {
dialogBoxCreate("Invalid filename. Must be either a script (.script) or " +
" or text file (.txt)")
return;
}
Engine.loadTerminalContent();
}
export function scriptCalculateOfflineProduction(runningScriptObj) {
//The Player object stores the last update time from when we were online
const thisUpdate = new Date().getTime();
@@ -313,7 +50,8 @@ export function scriptCalculateOfflineProduction(runningScriptObj) {
if (serv == null) {continue;}
const timesGrown = Math.round(0.5 * runningScriptObj.dataMap[ip][2] / runningScriptObj.onlineRunningTime * timePassed);
runningScriptObj.log(`Called on ${serv.hostname} ${timesGrown} times while offline`);
const growth = processSingleServerGrowth(serv, timesGrown, Player);
const host = AllServers[runningScriptObj.server];
const growth = processSingleServerGrowth(serv, timesGrown, Player, host.cpuCores);
runningScriptObj.log(`'${serv.hostname}' grown by ${numeralWrapper.format(growth * 100 - 100, '0.000000%')} while offline`);
}
}
@@ -333,9 +71,11 @@ export function scriptCalculateOfflineProduction(runningScriptObj) {
if (runningScriptObj.dataMap[ip][3] == 0 || runningScriptObj.dataMap[ip][3] == null) {continue;}
const serv = AllServers[ip];
if (serv == null) {continue;}
const host = AllServers[runningScriptObj.server];
const timesWeakened = Math.round(0.5 * runningScriptObj.dataMap[ip][3] / runningScriptObj.onlineRunningTime * timePassed);
runningScriptObj.log(`Called weaken() on ${serv.hostname} ${timesWeakened} times while offline`);
serv.weaken(CONSTANTS.ServerWeakenAmount * timesWeakened);
const coreBonus = 1+(host.cpuCores-1)/16;
serv.weaken(CONSTANTS.ServerWeakenAmount * timesWeakened * coreBonus);
}
}
}

View File

@@ -1,329 +0,0 @@
import { ScriptEditor } from "./ScriptEditor";
import ace from 'brace';
import 'brace/mode/javascript';
import './AceNetscriptMode';
import 'brace/theme/chaos';
import 'brace/theme/chrome';
import 'brace/theme/monokai';
import 'brace/theme/solarized_dark';
import 'brace/theme/solarized_light';
import 'brace/theme/terminal';
import 'brace/theme/twilight';
import 'brace/theme/xcode';
import "brace/keybinding/vim";
import "brace/keybinding/emacs";
import "brace/ext/language_tools";
import { NetscriptFunctions } from "../NetscriptFunctions";
import { Settings } from "../Settings/Settings";
import { AceKeybindingSetting } from "../Settings/SettingEnums";
import { clearEventListeners } from "../../utils/uiHelpers/clearEventListeners";
import { createElement } from "../../utils/uiHelpers/createElement";
import { createOptionElement } from "../../utils/uiHelpers/createOptionElement";
import { removeChildrenFromElement } from "../../utils/uiHelpers/removeChildrenFromElement";
// Wrapper for Ace editor
const Keybindings = {
ace: null,
vim: "ace/keyboard/vim",
emacs: "ace/keyboard/emacs",
};
function validateInitializationParamters(params) {
if (params.saveAndCloseFn == null) { return false; } // Save & close button function
if (params.quitFn == null) { return false; } // Quitting editor, aka Engine.loadTerminalContent
return true;
}
class AceEditorWrapper extends ScriptEditor {
constructor() {
super();
this.vimCommandDisplayWrapper = null;
}
init(params) {
if (this.editor != null) {
console.error(`AceEditor.init() called when it's already initialized`);
return false;
}
// Validate/Sanitize input
if (!validateInitializationParamters(params)) {
console.error(`'params' argument passed into initAceEditor() does not have proper properties`);
return false;
}
// Store the filename input
this.filenameInput = document.getElementById("script-editor-filename");
if (this.filenameInput == null) {
console.error(`Could not get Script Editor filename element (id=script-editor-filename)`);
return false;
}
// Initialize ACE Script editor
this.editor = ace.edit('ace-editor');
this.editor.getSession().setMode('ace/mode/netscript');
this.editor.setTheme('ace/theme/monokai');
const editorElement = document.getElementById('ace-editor');
if (editorElement == null) { return false; }
editorElement.style.fontSize = '16px';
this.editor.setOption("showPrintMargin", false);
// Configure some of the VIM keybindings
ace.config.loadModule('ace/keyboard/vim', function(module) {
var VimApi = module.CodeMirror.Vim;
VimApi.defineEx('write', 'w', function(/*cm, input*/) {
params.saveAndCloseFn();
});
VimApi.defineEx('quit', 'q', function(/*cm, input*/) {
params.quitFn();
});
VimApi.defineEx('xwritequit', 'x', function(/*cm, input*/) {
params.saveAndCloseFn();
});
VimApi.defineEx('wqwritequit', 'wq', function(/*cm, input*/) {
params.saveAndCloseFn();
});
});
// Store a reference to the VIM command display
this.vimCommandDisplayWrapper = document.getElementById("codemirror-vim-command-display-wrapper");
if (this.vimCommandDisplayWrapper == null) {
console.error(`Could not get Vim Command Display element (id=codemirror-vim-command-display-wrapper)`);
return false;
}
//Function autocompleter
this.editor.setOption("enableBasicAutocompletion", true);
var autocompleter = {
getCompletions: function(editor, session, pos, prefix, callback) {
if (prefix.length === 0) {callback(null, []); return;}
var words = [];
var fns = NetscriptFunctions(null);
for (let name in fns) {
if (fns.hasOwnProperty(name)) {
words.push({
name: name,
value: name,
});
//Get functions from namespaces
const namespaces = ["bladeburner", "hacknet", "codingcontract", "gang", "sleeve"];
if (namespaces.includes(name)) {
let namespace = fns[name];
if (typeof namespace !== "object") {continue;}
let namespaceFns = Object.keys(namespace);
for (let i = 0; i < namespaceFns.length; ++i) {
words.push({
name: namespaceFns[i],
value: namespaceFns[i],
});
}
}
}
}
callback(null, words);
},
}
this.editor.completers = [autocompleter];
return true;
}
initialized() {
return (this.editor != null);
}
// Create the configurable Options for this Editor
create() {
function safeGetElementById(id, whatFor="") {
const elem = document.getElementById(id);
if (elem == null) {
throw new Error(`Could not find ${whatFor} DOM element(id=${id})`);
}
return elem;
}
function safeClearEventListeners(id, whatFor="") {
const elem = clearEventListeners(id);
if (elem == null) {
throw new Error(`Could not find ${whatFor} DOM element(id=${id})`);
}
return elem;
}
try {
// Set editor to visible
const elem = document.getElementById("ace-editor");
if (elem instanceof HTMLElement) {
elem.style.display = "block";
}
// Make sure the Vim command display from CodeMirror is invisible
if (this.vimCommandDisplayWrapper instanceof HTMLElement) {
this.vimCommandDisplayWrapper.style.display = "none";
}
// Theme
const themeDropdown = safeClearEventListeners("script-editor-option-theme", "Theme Selector");
removeChildrenFromElement(themeDropdown);
themeDropdown.add(createOptionElement("Chaos"));
themeDropdown.add(createOptionElement("Chrome"));
themeDropdown.add(createOptionElement("Monokai"));
themeDropdown.add(createOptionElement("Solarized Dark", "Solarized_Dark"));
themeDropdown.add(createOptionElement("Solarized Light", "Solarized_Light"));
themeDropdown.add(createOptionElement("Terminal"));
themeDropdown.add(createOptionElement("Twilight"));
themeDropdown.add(createOptionElement("XCode"));
if (Settings.EditorTheme) {
var initialIndex = 2;
for (var i = 0; i < themeDropdown.options.length; ++i) {
if (themeDropdown.options[i].value === Settings.EditorTheme) {
initialIndex = i;
break;
}
}
themeDropdown.selectedIndex = initialIndex;
} else {
themeDropdown.selectedIndex = 2;
}
themeDropdown.onchange = () => {
const val = themeDropdown.value;
Settings.EditorTheme = val;
const themePath = "ace/theme/" + val.toLowerCase();
this.editor.setTheme(themePath);
};
themeDropdown.onchange();
// Keybinding
const keybindingDropdown = safeClearEventListeners("script-editor-option-keybinding", "Keybinding Selector");
removeChildrenFromElement(keybindingDropdown);
keybindingDropdown.add(createOptionElement("Ace", AceKeybindingSetting.Ace));
keybindingDropdown.add(createOptionElement("Vim", AceKeybindingSetting.Vim));
keybindingDropdown.add(createOptionElement("Emacs", AceKeybindingSetting.Emacs));
if (Settings.EditorKeybinding) {
// Sanitize the Keybinding setting
if (!(Object.values(AceKeybindingSetting).includes(Settings.EditorKeybinding))) {
Settings.EditorKeybinding = AceKeybindingSetting.Ace;
}
var initialIndex = 0;
for (var i = 0; i < keybindingDropdown.options.length; ++i) {
if (keybindingDropdown.options[i].value === Settings.EditorKeybinding) {
initialIndex = i;
break;
}
}
keybindingDropdown.selectedIndex = initialIndex;
} else {
keybindingDropdown.selectedIndex = 0;
}
keybindingDropdown.onchange = () => {
var val = keybindingDropdown.value;
Settings.EditorKeybinding = val;
this.editor.setKeyboardHandler(Keybindings[val.toLowerCase()]);
};
keybindingDropdown.onchange();
// Highlight Active line
const highlightActiveChkBox = safeClearEventListeners("script-editor-option-highlightactiveline", "Active Line Checkbox");
highlightActiveChkBox.checked = Settings.EditorHighlightActiveLine;
highlightActiveChkBox.onchange = () => {
Settings.EditorHighlightActiveLine = highlightActiveChkBox.checked;
this.editor.setHighlightActiveLine(highlightActiveChkBox.checked);
};
// Show Invisibles
const showInvisiblesChkBox = safeClearEventListeners("script-editor-option-showinvisibles", "Show Invisible Checkbox");
showInvisiblesChkBox.checked = Settings.EditorShowInvisibles;
showInvisiblesChkBox.onchange = () => {
Settings.EditorShowInvisibles = showInvisiblesChkBox.checked;
this.editor.setShowInvisibles(showInvisiblesChkBox.checked);
};
// Use Soft Tab
const softTabChkBox = safeClearEventListeners("script-editor-option-usesofttab", "Soft Tab Checkbox");
softTabChkBox.checked = Settings.EditorUseSoftTab;
softTabChkBox.onchange = () => {
Settings.EditorUseSoftTab = softTabChkBox.checked;
this.editor.getSession().setUseSoftTabs(softTabChkBox.checked);
};
// Some helper functions for dealing with flexible options
function resetFlexibleOption(id) {
const fieldset = safeGetElementById(id);
removeChildrenFromElement(fieldset);
fieldset.style.display = "block";
return fieldset;
}
function removeFlexibleOption(id) {
// This doesn't really remove it, just sets it to invisible
const fieldset = resetFlexibleOption(id);
fieldset.style.display = "none";
return fieldset;
}
// Jshint Maxerr (Flex 1)
const flex1Fieldset = resetFlexibleOption("script-editor-option-flex1-fieldset");
const flex1Id = "script-editor-option-maxerr";
const flex1ValueLabel = createElement("em", { innerText: "200" });
flex1Fieldset.appendChild(createElement("label", {
for: flex1Id,
innerText: "Max Error Count",
}));
const flex1Input = createElement("input", {
id: flex1Id,
max: "1000",
min: "50",
name: flex1Id,
step: "1",
type: "range",
value: "200",
changeListener: () => {
this.editor.getSession().$worker.send("changeOptions", [{maxerr:flex1Input.value}]);
flex1ValueLabel.innerText = flex1Input.value;
},
});
flex1Fieldset.appendChild(flex1Input);
flex1Fieldset.appendChild(flex1ValueLabel);
// Nothing for Flex Options 2-4
removeFlexibleOption("script-editor-option-flex2-fieldset");
removeFlexibleOption("script-editor-option-flex3-fieldset");
removeFlexibleOption("script-editor-option-flex4-fieldset");
} catch(e) {
console.error(`Exception caught: ${e}`);
return false;
}
}
isFocused() {
if (this.editor == null) { return false; }
return this.editor.isFocused();
}
// Sets the editor to be invisible. Does not require this class to be initialized
setInvisible() {
const elem = document.getElementById("ace-editor");
if (elem instanceof HTMLElement) {
elem.style.display = "none";
}
}
getCursor() {
return this.editor.getCursorPosition();
}
setCursor(pos) {
this.editor.gotoLine(pos.row+1, pos.column);
}
}
export const AceEditor = new AceEditorWrapper();

View File

@@ -1,807 +0,0 @@
//This file should be copied into brace/mode/netscript.js
import { NetscriptFunctions } from '../NetscriptFunctions';
ace.define("ace/mode/doc_comment_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"], function(acequire, exports) {
"use strict";
var oop = acequire("../lib/oop");
var TextHighlightRules = acequire("./text_highlight_rules").TextHighlightRules;
var DocCommentHighlightRules = function() {
this.$rules = {
"start" : [ {
token : "comment.doc.tag",
regex : "@[\\w\\d_]+", // TODO: fix email addresses
},
DocCommentHighlightRules.getTagRule(),
{
defaultToken : "comment.doc",
caseInsensitive: true,
}],
};
};
oop.inherits(DocCommentHighlightRules, TextHighlightRules);
DocCommentHighlightRules.getTagRule = function() {
return {
token : "comment.doc.tag.storage.type",
regex : "\\b(?:TODO|FIXME|XXX|HACK)\\b",
};
}
DocCommentHighlightRules.getStartRule = function(start) {
return {
token : "comment.doc", // doc comment
regex : "\\/\\*(?=\\*)",
next : start,
};
};
DocCommentHighlightRules.getEndRule = function (start) {
return {
token : "comment.doc", // closing comment
regex : "\\*\\/",
next : start,
};
};
exports.DocCommentHighlightRules = DocCommentHighlightRules;
});
ace.define("ace/mode/netscript_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/doc_comment_highlight_rules","ace/mode/text_highlight_rules"], function(acequire, exports) {
"use strict";
var oop = acequire("../lib/oop");
var DocCommentHighlightRules = acequire("./doc_comment_highlight_rules").DocCommentHighlightRules;
var TextHighlightRules = acequire("./text_highlight_rules").TextHighlightRules;
var identifierRe = "[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*";
let functions = (function(){
function recursiveKeywords(namespace) {
let keywords = [];
for(const elem of Object.keys(namespace)) {
keywords.push(elem);
if(typeof namespace[elem] == 'object') {
keywords = keywords.concat(recursiveKeywords(namespace[elem]));
}
}
return keywords;
}
const ns = NetscriptFunctions(null);
// reverse is important so that both clearLog and clear are highlighted.
return recursiveKeywords(ns).sort().reverse().join("|");
})();
var NetscriptHighlightRules = function(options) {
var keywordMapper = this.createKeywordMapper({
"variable.language":
"Array|Boolean|Date|Function|Iterator|Number|Object|RegExp|String|Proxy|" + // Constructors
"Namespace|QName|XML|XMLList|" + // E4X
"ArrayBuffer|Float32Array|Float64Array|Int16Array|Int32Array|Int8Array|" +
"Uint16Array|Uint32Array|Uint8Array|Uint8ClampedArray|" +
"Error|EvalError|InternalError|RangeError|ReferenceError|StopIteration|" + // Errors
"SyntaxError|TypeError|URIError|" +
"decodeURI|decodeURIComponent|encodeURI|encodeURIComponent|eval|isFinite|" + // Non-constructor functions
"isNaN|parseFloat|parseInt|" +
"ns|" + functions +"|" +
"JSON|Math|" + // Other
"this|arguments|prototype|window|document" , // Pseudo
"keyword":
"const|yield|import|get|set|async|await|" +
"break|case|catch|continue|default|delete|do|else|finally|for|function|" +
"if|in|of|instanceof|new|return|switch|throw|try|typeof|let|var|while|with|debugger|" +
"__parent__|__count__|escape|unescape|with|__proto__|" +
"class|enum|extends|super|export|implements|private|public|interface|package|protected|static",
"storage.type":
"const|let|var|function",
"constant.language":
"null|Infinity|NaN|undefined",
"support.function":
"alert",
"constant.language.boolean": "true|false",
}, "identifier");
var kwBeforeRe = "case|do|else|finally|in|instanceof|return|throw|try|typeof|yield|void";
var escapedRe = "\\\\(?:x[0-9a-fA-F]{2}|" + // hex
"u[0-9a-fA-F]{4}|" + // unicode
"u{[0-9a-fA-F]{1,6}}|" + // es6 unicode
"[0-2][0-7]{0,2}|" + // oct
"3[0-7][0-7]?|" + // oct
"[4-7][0-7]?|" + //oct
".)";
this.$rules = {
"no_regex" : [
DocCommentHighlightRules.getStartRule("doc-start"),
comments("no_regex"),
{
token : "string",
regex : "'(?=.)",
next : "qstring",
}, {
token : "string",
regex : '"(?=.)',
next : "qqstring",
}, {
token : "constant.numeric", // hex
regex : /0(?:[xX][0-9a-fA-F]+|[bB][01]+)\b/,
}, {
token : "constant.numeric", // float
regex : /[+-]?\d[\d_]*(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?\b/,
}, {
token : [
"storage.type", "punctuation.operator", "support.function",
"punctuation.operator", "entity.name.function", "text","keyword.operator",
],
regex : "(" + identifierRe + ")(\\.)(prototype)(\\.)(" + identifierRe +")(\\s*)(=)",
next: "function_arguments",
}, {
token : [
"storage.type", "punctuation.operator", "entity.name.function", "text",
"keyword.operator", "text", "storage.type", "text", "paren.lparen",
],
regex : "(" + identifierRe + ")(\\.)(" + identifierRe +")(\\s*)(=)(\\s*)(function)(\\s*)(\\()",
next: "function_arguments",
}, {
token : [
"entity.name.function", "text", "keyword.operator", "text", "storage.type",
"text", "paren.lparen",
],
regex : "(" + identifierRe +")(\\s*)(=)(\\s*)(function)(\\s*)(\\()",
next: "function_arguments",
}, {
token : [
"storage.type", "punctuation.operator", "entity.name.function", "text",
"keyword.operator", "text",
"storage.type", "text", "entity.name.function", "text", "paren.lparen",
],
regex : "(" + identifierRe + ")(\\.)(" + identifierRe +")(\\s*)(=)(\\s*)(function)(\\s+)(\\w+)(\\s*)(\\()",
next: "function_arguments",
}, {
token : [
"storage.type", "text", "entity.name.function", "text", "paren.lparen",
],
regex : "(function)(\\s+)(" + identifierRe + ")(\\s*)(\\()",
next: "function_arguments",
}, {
token : [
"entity.name.function", "text", "punctuation.operator",
"text", "storage.type", "text", "paren.lparen",
],
regex : "(" + identifierRe + ")(\\s*)(:)(\\s*)(function)(\\s*)(\\()",
next: "function_arguments",
}, {
token : [
"text", "text", "storage.type", "text", "paren.lparen",
],
regex : "(:)(\\s*)(function)(\\s*)(\\()",
next: "function_arguments",
}, {
token : "keyword",
regex : "(?:" + kwBeforeRe + ")\\b",
next : "start",
}, {
token : ["support.constant"],
regex : /that\b/,
}, {
token : ["storage.type", "punctuation.operator", "support.function.firebug"],
regex : /(console)(\.)(warn|info|log|error|time|trace|timeEnd|assert)\b/,
}, {
token : keywordMapper,
regex : identifierRe,
}, {
token : "punctuation.operator",
regex : /[.](?![.])/,
next : "property",
}, {
token : "keyword.operator",
regex : /--|\+\+|\.{3}|===|==|=|!=|!==|<+=?|>+=?|!|&&|\|\||\?:|[!$%&*+\-~\/^]=?/,
next : "start",
}, {
token : "punctuation.operator",
regex : /[?:,;.]/,
next : "start",
}, {
token : "paren.lparen",
regex : /[\[({]/,
next : "start",
}, {
token : "paren.rparen",
regex : /[\])}]/,
}, {
token: "comment",
regex: /^#!.*$/,
},
],
property: [{
token : "text",
regex : "\\s+",
}, {
token : [
"storage.type", "punctuation.operator", "entity.name.function", "text",
"keyword.operator", "text",
"storage.type", "text", "entity.name.function", "text", "paren.lparen",
],
regex : "(" + identifierRe + ")(\\.)(" + identifierRe +")(\\s*)(=)(\\s*)(function)(?:(\\s+)(\\w+))?(\\s*)(\\()",
next: "function_arguments",
}, {
token : "punctuation.operator",
regex : /[.](?![.])/,
}, {
token : "support.function",
regex : "/|" + functions + "|/",
}, {
token : "support.function",
regex : /(s(?:h(?:ift|ow(?:Mod(?:elessDialog|alDialog)|Help))|croll(?:X|By(?:Pages|Lines)?|Y|To)?|t(?:op|rike)|i(?:n|zeToContent|debar|gnText)|ort|u(?:p|b(?:str(?:ing)?)?)|pli(?:ce|t)|e(?:nd|t(?:Re(?:sizable|questHeader)|M(?:i(?:nutes|lliseconds)|onth)|Seconds|Ho(?:tKeys|urs)|Year|Cursor|Time(?:out)?|Interval|ZOptions|Date|UTC(?:M(?:i(?:nutes|lliseconds)|onth)|Seconds|Hours|Date|FullYear)|FullYear|Active)|arch)|qrt|lice|avePreferences|mall)|h(?:ome|andleEvent)|navigate|c(?:har(?:CodeAt|At)|o(?:s|n(?:cat|textual|firm)|mpile)|eil|lear(?:Timeout|Interval)?|a(?:ptureEvents|ll)|reate(?:StyleSheet|Popup|EventObject))|t(?:o(?:GMTString|S(?:tring|ource)|U(?:TCString|pperCase)|Lo(?:caleString|werCase))|est|a(?:n|int(?:Enabled)?))|i(?:s(?:NaN|Finite)|ndexOf|talics)|d(?:isableExternalCapture|ump|etachEvent)|u(?:n(?:shift|taint|escape|watch)|pdateCommands)|j(?:oin|avaEnabled)|p(?:o(?:p|w)|ush|lugins.refresh|a(?:ddings|rse(?:Int|Float)?)|r(?:int|ompt|eference))|e(?:scape|nableExternalCapture|val|lementFromPoint|x(?:p|ec(?:Script|Command)?))|valueOf|UTC|queryCommand(?:State|Indeterm|Enabled|Value)|f(?:i(?:nd|le(?:ModifiedDate|Size|CreatedDate|UpdatedDate)|xed)|o(?:nt(?:size|color)|rward)|loor|romCharCode)|watch|l(?:ink|o(?:ad|g)|astIndexOf)|a(?:sin|nchor|cos|t(?:tachEvent|ob|an(?:2)?)|pply|lert|b(?:s|ort))|r(?:ou(?:nd|teEvents)|e(?:size(?:By|To)|calc|turnValue|place|verse|l(?:oad|ease(?:Capture|Events)))|andom)|g(?:o|et(?:ResponseHeader|M(?:i(?:nutes|lliseconds)|onth)|Se(?:conds|lection)|Hours|Year|Time(?:zoneOffset)?|Da(?:y|te)|UTC(?:M(?:i(?:nutes|lliseconds)|onth)|Seconds|Hours|Da(?:y|te)|FullYear)|FullYear|A(?:ttention|llResponseHeaders)))|m(?:in|ove(?:B(?:y|elow)|To(?:Absolute)?|Above)|ergeAttributes|a(?:tch|rgins|x))|b(?:toa|ig|o(?:ld|rderWidths)|link|ack))\b(?=\()/,
}, {
token : "support.function.dom",
regex : /(s(?:ub(?:stringData|mit)|plitText|e(?:t(?:NamedItem|Attribute(?:Node)?)|lect))|has(?:ChildNodes|Feature)|namedItem|c(?:l(?:ick|o(?:se|neNode))|reate(?:C(?:omment|DATASection|aption)|T(?:Head|extNode|Foot)|DocumentFragment|ProcessingInstruction|E(?:ntityReference|lement)|Attribute))|tabIndex|i(?:nsert(?:Row|Before|Cell|Data)|tem)|open|delete(?:Row|C(?:ell|aption)|T(?:Head|Foot)|Data)|focus|write(?:ln)?|a(?:dd|ppend(?:Child|Data))|re(?:set|place(?:Child|Data)|move(?:NamedItem|Child|Attribute(?:Node)?)?)|get(?:NamedItem|Element(?:sBy(?:Name|TagName|ClassName)|ById)|Attribute(?:Node)?)|blur)\b(?=\()/,
}, {
token : "support.constant",
regex : /(s(?:ystemLanguage|cr(?:ipts|ollbars|een(?:X|Y|Top|Left))|t(?:yle(?:Sheets)?|atus(?:Text|bar)?)|ibling(?:Below|Above)|ource|uffixes|e(?:curity(?:Policy)?|l(?:ection|f)))|h(?:istory|ost(?:name)?|as(?:h|Focus))|y|X(?:MLDocument|SLDocument)|n(?:ext|ame(?:space(?:s|URI)|Prop))|M(?:IN_VALUE|AX_VALUE)|c(?:haracterSet|o(?:n(?:structor|trollers)|okieEnabled|lorDepth|mp(?:onents|lete))|urrent|puClass|l(?:i(?:p(?:boardData)?|entInformation)|osed|asses)|alle(?:e|r)|rypto)|t(?:o(?:olbar|p)|ext(?:Transform|Indent|Decoration|Align)|ags)|SQRT(?:1_2|2)|i(?:n(?:ner(?:Height|Width)|put)|ds|gnoreCase)|zIndex|o(?:scpu|n(?:readystatechange|Line)|uter(?:Height|Width)|p(?:sProfile|ener)|ffscreenBuffering)|NEGATIVE_INFINITY|d(?:i(?:splay|alog(?:Height|Top|Width|Left|Arguments)|rectories)|e(?:scription|fault(?:Status|Ch(?:ecked|arset)|View)))|u(?:ser(?:Profile|Language|Agent)|n(?:iqueID|defined)|pdateInterval)|_content|p(?:ixelDepth|ort|ersonalbar|kcs11|l(?:ugins|atform)|a(?:thname|dding(?:Right|Bottom|Top|Left)|rent(?:Window|Layer)?|ge(?:X(?:Offset)?|Y(?:Offset)?))|r(?:o(?:to(?:col|type)|duct(?:Sub)?|mpter)|e(?:vious|fix)))|e(?:n(?:coding|abledPlugin)|x(?:ternal|pando)|mbeds)|v(?:isibility|endor(?:Sub)?|Linkcolor)|URLUnencoded|P(?:I|OSITIVE_INFINITY)|f(?:ilename|o(?:nt(?:Size|Family|Weight)|rmName)|rame(?:s|Element)|gColor)|E|whiteSpace|l(?:i(?:stStyleType|n(?:eHeight|kColor))|o(?:ca(?:tion(?:bar)?|lName)|wsrc)|e(?:ngth|ft(?:Context)?)|a(?:st(?:M(?:odified|atch)|Index|Paren)|yer(?:s|X)|nguage))|a(?:pp(?:MinorVersion|Name|Co(?:deName|re)|Version)|vail(?:Height|Top|Width|Left)|ll|r(?:ity|guments)|Linkcolor|bove)|r(?:ight(?:Context)?|e(?:sponse(?:XML|Text)|adyState))|global|x|m(?:imeTypes|ultiline|enubar|argin(?:Right|Bottom|Top|Left))|L(?:N(?:10|2)|OG(?:10E|2E))|b(?:o(?:ttom|rder(?:Width|RightWidth|BottomWidth|Style|Color|TopWidth|LeftWidth))|ufferDepth|elow|ackground(?:Color|Image)))\b/,
}, {
token : "identifier",
regex : identifierRe,
}, {
regex: "",
token: "empty",
next: "no_regex",
},
],
"start": [
DocCommentHighlightRules.getStartRule("doc-start"),
comments("start"),
{
token: "string.regexp",
regex: "\\/",
next: "regex",
}, {
token : "text",
regex : "\\s+|^$",
next : "start",
}, {
token: "empty",
regex: "",
next: "no_regex",
},
],
"regex": [
{
token: "regexp.keyword.operator",
regex: "\\\\(?:u[\\da-fA-F]{4}|x[\\da-fA-F]{2}|.)",
}, {
token: "string.regexp",
regex: "/[sxngimy]*",
next: "no_regex",
}, {
token : "invalid",
regex: /\{\d+\b,?\d*\}[+*]|[+*$^?][+*]|[$^][?]|\?{3,}/,
}, {
token : "constant.language.escape",
regex: /\(\?[:=!]|\)|\{\d+\b,?\d*\}|[+*]\?|[()$^+*?.]/,
}, {
token : "constant.language.delimiter",
regex: /\|/,
}, {
token: "constant.language.escape",
regex: /\[\^?/,
next: "regex_character_class",
}, {
token: "empty",
regex: "$",
next: "no_regex",
}, {
defaultToken: "string.regexp",
},
],
"regex_character_class": [
{
token: "regexp.charclass.keyword.operator",
regex: "\\\\(?:u[\\da-fA-F]{4}|x[\\da-fA-F]{2}|.)",
}, {
token: "constant.language.escape",
regex: "]",
next: "regex",
}, {
token: "constant.language.escape",
regex: "-",
}, {
token: "empty",
regex: "$",
next: "no_regex",
}, {
defaultToken: "string.regexp.charachterclass",
},
],
"function_arguments": [
{
token: "variable.parameter",
regex: identifierRe,
}, {
token: "punctuation.operator",
regex: "[, ]+",
}, {
token: "punctuation.operator",
regex: "$",
}, {
token: "empty",
regex: "",
next: "no_regex",
},
],
"qqstring" : [
{
token : "constant.language.escape",
regex : escapedRe,
}, {
token : "string",
regex : "\\\\$",
next : "qqstring",
}, {
token : "string",
regex : '"|$',
next : "no_regex",
}, {
defaultToken: "string",
},
],
"qstring" : [
{
token : "constant.language.escape",
regex : escapedRe,
}, {
token : "string",
regex : "\\\\$",
next : "qstring",
}, {
token : "string",
regex : "'|$",
next : "no_regex",
}, {
defaultToken: "string",
},
],
};
if (!options || !options.noES6) {
this.$rules.no_regex.unshift({
regex: "[{}]", onMatch: function(val, state, stack) {
this.next = val == "{" ? this.nextState : "";
if (val == "{" && stack.length) {
stack.unshift("start", state);
}
else if (val == "}" && stack.length) {
stack.shift();
this.next = stack.shift();
if (this.next.indexOf("string") != -1 || this.next.indexOf("jsx") != -1)
return "paren.quasi.end";
}
return val == "{" ? "paren.lparen" : "paren.rparen";
},
nextState: "start",
}, {
token : "string.quasi.start",
regex : /`/,
push : [{
token : "constant.language.escape",
regex : escapedRe,
}, {
token : "paren.quasi.start",
regex : /\${/,
push : "start",
}, {
token : "string.quasi.end",
regex : /`/,
next : "pop",
}, {
defaultToken: "string.quasi",
}],
});
if (!options || options.jsx != false)
JSX.call(this);
}
this.embedRules(DocCommentHighlightRules, "doc-",
[ DocCommentHighlightRules.getEndRule("no_regex") ]);
this.normalizeRules();
};
oop.inherits(NetscriptHighlightRules, TextHighlightRules);
function JSX() {
var tagRegex = identifierRe.replace("\\d", "\\d\\-");
var jsxTag = {
onMatch : function(val, state, stack) {
var offset = val.charAt(1) == "/" ? 2 : 1;
if (offset == 1) {
if (state != this.nextState)
stack.unshift(this.next, this.nextState, 0);
else
stack.unshift(this.next);
stack[2]++;
} else if (offset == 2) {
if (state == this.nextState) {
stack[1]--;
if (!stack[1] || stack[1] < 0) {
stack.shift();
stack.shift();
}
}
}
return [{
type: "meta.tag.punctuation." + (offset == 1 ? "" : "end-") + "tag-open.xml",
value: val.slice(0, offset),
}, {
type: "meta.tag.tag-name.xml",
value: val.substr(offset),
}];
},
regex : "</?" + tagRegex + "",
next: "jsxAttributes",
nextState: "jsx",
};
this.$rules.start.unshift(jsxTag);
var jsxJsRule = {
regex: "{",
token: "paren.quasi.start",
push: "start",
};
this.$rules.jsx = [
jsxJsRule,
jsxTag,
{include : "reference"},
{defaultToken: "string"},
];
this.$rules.jsxAttributes = [{
token : "meta.tag.punctuation.tag-close.xml",
regex : "/?>",
onMatch : function(value, currentState, stack) {
if (currentState == stack[0])
stack.shift();
if (value.length == 2) {
if (stack[0] == this.nextState)
stack[1]--;
if (!stack[1] || stack[1] < 0) {
stack.splice(0, 2);
}
}
this.next = stack[0] || "start";
return [{type: this.token, value: value}];
},
nextState: "jsx",
},
jsxJsRule,
comments("jsxAttributes"),
{
token : "entity.other.attribute-name.xml",
regex : tagRegex,
}, {
token : "keyword.operator.attribute-equals.xml",
regex : "=",
}, {
token : "text.tag-whitespace.xml",
regex : "\\s+",
}, {
token : "string.attribute-value.xml",
regex : "'",
stateName : "jsx_attr_q",
push : [
{token : "string.attribute-value.xml", regex: "'", next: "pop"},
{include : "reference"},
{defaultToken : "string.attribute-value.xml"},
],
}, {
token : "string.attribute-value.xml",
regex : '"',
stateName : "jsx_attr_qq",
push : [
{token : "string.attribute-value.xml", regex: '"', next: "pop"},
{include : "reference"},
{defaultToken : "string.attribute-value.xml"},
],
},
jsxTag,
];
this.$rules.reference = [{
token : "constant.language.escape.reference.xml",
regex : "(?:&#[0-9]+;)|(?:&#x[0-9a-fA-F]+;)|(?:&[a-zA-Z0-9_:\\.-]+;)",
}];
}
function comments(next) {
return [
{
token : "comment", // multi line comment
regex : /\/\*/,
next: [
DocCommentHighlightRules.getTagRule(),
{token : "comment", regex : "\\*\\/", next : next || "pop"},
{defaultToken : "comment", caseInsensitive: true},
],
}, {
token : "comment",
regex : "\\/\\/",
next: [
DocCommentHighlightRules.getTagRule(),
{token : "comment", regex : "$|^", next : next || "pop"},
{defaultToken : "comment", caseInsensitive: true},
],
},
];
}
exports.NetscriptHighlightRules = NetscriptHighlightRules;
});
ace.define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"], function(acequire, exports) {
"use strict";
var Range = acequire("../range").Range;
var MatchingBraceOutdent = function() {
// do nothing.
};
(function() {
this.checkOutdent = function(line, input) {
if (! /^\s+$/.test(line))
return false;
return /^\s*\}/.test(input);
};
this.autoOutdent = function(doc, row) {
var line = doc.getLine(row);
var match = line.match(/^(\s*\})/);
if (!match) return 0;
var column = match[1].length;
var openBracePos = doc.findMatchingBracket({row: row, column: column});
if (!openBracePos || openBracePos.row == row) return 0;
var indent = this.$getIndent(doc.getLine(openBracePos.row));
doc.replace(new Range(row, 0, row, column-1), indent);
};
this.$getIndent = function(line) {
return line.match(/^\s*/)[0];
};
}).call(MatchingBraceOutdent.prototype);
exports.MatchingBraceOutdent = MatchingBraceOutdent;
});
ace.define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"], function(acequire, exports) {
"use strict";
var oop = acequire("../../lib/oop");
var Range = acequire("../../range").Range;
var BaseFoldMode = acequire("./fold_mode").FoldMode;
var FoldMode = exports.FoldMode = function(commentRegex) {
if (commentRegex) {
this.foldingStartMarker = new RegExp(
this.foldingStartMarker.source.replace(/\|[^|]*?$/, "|" + commentRegex.start),
);
this.foldingStopMarker = new RegExp(
this.foldingStopMarker.source.replace(/\|[^|]*?$/, "|" + commentRegex.end),
);
}
};
oop.inherits(FoldMode, BaseFoldMode);
(function() {
this.foldingStartMarker = /(\{|\[)[^\}\]]*$|^\s*(\/\*)/;
this.foldingStopMarker = /^[^\[\{]*(\}|\])|^[\s\*]*(\*\/)/;
this.singleLineBlockCommentRe= /^\s*(\/\*).*\*\/\s*$/;
this.tripleStarBlockCommentRe = /^\s*(\/\*\*\*).*\*\/\s*$/;
this.startRegionRe = /^\s*(\/\*|\/\/)#?region\b/;
this._getFoldWidgetBase = this.getFoldWidget;
this.getFoldWidget = function(session, foldStyle, row) {
var line = session.getLine(row);
if (this.singleLineBlockCommentRe.test(line)) {
if (!this.startRegionRe.test(line) && !this.tripleStarBlockCommentRe.test(line))
return "";
}
var fw = this._getFoldWidgetBase(session, foldStyle, row);
if (!fw && this.startRegionRe.test(line))
return "start"; // lineCommentRegionStart
return fw;
};
this.getFoldWidgetRange = function(session, foldStyle, row, forceMultiline) {
var line = session.getLine(row);
if (this.startRegionRe.test(line))
return this.getCommentRegionBlock(session, line, row);
var match = line.match(this.foldingStartMarker);
if (match) {
var i = match.index;
if (match[1])
return this.openingBracketBlock(session, match[1], row, i);
var range = session.getCommentFoldRange(row, i + match[0].length, 1);
if (range && !range.isMultiLine()) {
if (forceMultiline) {
range = this.getSectionRange(session, row);
} else if (foldStyle != "all")
range = null;
}
return range;
}
if (foldStyle === "markbegin")
return;
var match = line.match(this.foldingStopMarker);
if (match) {
var i = match.index + match[0].length;
if (match[1])
return this.closingBracketBlock(session, match[1], row, i);
return session.getCommentFoldRange(row, i, -1);
}
};
this.getSectionRange = function(session, row) {
var line = session.getLine(row);
var startIndent = line.search(/\S/);
var startRow = row;
var startColumn = line.length;
row = row + 1;
var endRow = row;
var maxRow = session.getLength();
while (++row < maxRow) {
line = session.getLine(row);
var indent = line.search(/\S/);
if (indent === -1)
continue;
if (startIndent > indent)
break;
var subRange = this.getFoldWidgetRange(session, "all", row);
if (subRange) {
if (subRange.start.row <= startRow) {
break;
} else if (subRange.isMultiLine()) {
row = subRange.end.row;
} else if (startIndent == indent) {
break;
}
}
endRow = row;
}
return new Range(startRow, startColumn, endRow, session.getLine(endRow).length);
};
this.getCommentRegionBlock = function(session, line, row) {
var startColumn = line.search(/\s*$/);
var maxRow = session.getLength();
var startRow = row;
var re = /^\s*(?:\/\*|\/\/|--)#?(end)?region\b/;
var depth = 1;
while (++row < maxRow) {
line = session.getLine(row);
var m = re.exec(line);
if (!m) continue;
if (m[1]) depth--;
else depth++;
if (!depth) break;
}
var endRow = row;
if (endRow > startRow) {
return new Range(startRow, startColumn, endRow, line.length);
}
};
}).call(FoldMode.prototype);
});
ace.define("ace/mode/netscript",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/netscript_highlight_rules","ace/mode/matching_brace_outdent","ace/worker/worker_client","ace/mode/behaviour/cstyle","ace/mode/folding/cstyle"], function(acequire, exports) {
"use strict";
var oop = acequire("../lib/oop");
var TextMode = acequire("./text").Mode;
var NetscriptHighlightRules = acequire("./netscript_highlight_rules").NetscriptHighlightRules;
var MatchingBraceOutdent = acequire("./matching_brace_outdent").MatchingBraceOutdent;
var WorkerClient = acequire("../worker/worker_client").WorkerClient;
var CstyleBehaviour = acequire("./behaviour/cstyle").CstyleBehaviour;
var CStyleFoldMode = acequire("./folding/cstyle").FoldMode;
var Mode = function() {
this.HighlightRules = NetscriptHighlightRules;
this.$outdent = new MatchingBraceOutdent();
this.$behaviour = new CstyleBehaviour();
this.foldingRules = new CStyleFoldMode();
};
oop.inherits(Mode, TextMode);
(function() {
this.lineCommentStart = "//";
this.blockComment = {start: "/*", end: "*/"};
this.getNextLineIndent = function(state, line, tab) {
var indent = this.$getIndent(line);
var tokenizedLine = this.getTokenizer().getLineTokens(line, state);
var tokens = tokenizedLine.tokens;
var endState = tokenizedLine.state;
if (tokens.length && tokens[tokens.length-1].type == "comment") {
return indent;
}
if (state == "start" || state == "no_regex") {
var match = line.match(/^.*(?:\bcase\b.*:|[\{\(\[])\s*$/);
if (match) {
indent += tab;
}
} else if (state == "doc-start") {
if (endState == "start" || endState == "no_regex") {
return "";
}
var match = line.match(/^\s*(\/?)\*/);
if (match) {
if (match[1]) {
indent += " ";
}
indent += "* ";
}
}
return indent;
};
this.checkOutdent = function(state, line, input) {
return this.$outdent.checkOutdent(line, input);
};
this.autoOutdent = function(state, doc, row) {
this.$outdent.autoOutdent(doc, row);
};
this.createWorker = function(session) {
var worker = new WorkerClient(["ace"], require("brace/worker/javascript"), "JavaScriptWorker");
worker.attachToDocument(session.getDocument());
worker.on("annotate", function(results) {
session.setAnnotations(results.data);
});
worker.on("terminate", function() {
session.clearAnnotations();
});
return worker;
};
this.$id = "ace/mode/netscript";
}).call(Mode.prototype);
exports.Mode = Mode;
});

View File

@@ -1,596 +0,0 @@
// Wrapper for CodeMirror editor
// https://github.com/codemirror/codemirror
import { ScriptEditor } from "./ScriptEditor";
import 'codemirror/lib/codemirror.css';
import 'codemirror/theme/monokai.css';
import 'codemirror/theme/3024-day.css';
import 'codemirror/theme/3024-night.css';
import 'codemirror/theme/abcdef.css';
import 'codemirror/theme/ambiance-mobile.css';
import 'codemirror/theme/ambiance.css';
import 'codemirror/theme/base16-dark.css';
import 'codemirror/theme/base16-light.css';
import 'codemirror/theme/bespin.css';
import 'codemirror/theme/blackboard.css';
import 'codemirror/theme/cobalt.css';
import 'codemirror/theme/colorforth.css';
import 'codemirror/theme/darcula.css';
import 'codemirror/theme/dracula.css';
import 'codemirror/theme/duotone-dark.css';
import 'codemirror/theme/duotone-light.css';
import 'codemirror/theme/eclipse.css';
import 'codemirror/theme/elegant.css';
import 'codemirror/theme/erlang-dark.css';
import 'codemirror/theme/gruvbox-dark.css';
import 'codemirror/theme/hopscotch.css';
import 'codemirror/theme/icecoder.css';
import 'codemirror/theme/idea.css';
import 'codemirror/theme/isotope.css';
import 'codemirror/theme/lesser-dark.css';
import 'codemirror/theme/liquibyte.css';
import 'codemirror/theme/lucario.css';
import 'codemirror/theme/material.css';
import 'codemirror/theme/mbo.css';
import 'codemirror/theme/mdn-like.css';
import 'codemirror/theme/midnight.css';
import 'codemirror/theme/neat.css';
import 'codemirror/theme/neo.css';
import 'codemirror/theme/night.css';
import 'codemirror/theme/oceanic-next.css';
import 'codemirror/theme/panda-syntax.css';
import 'codemirror/theme/paraiso-dark.css';
import 'codemirror/theme/paraiso-light.css';
import 'codemirror/theme/pastel-on-dark.css';
import 'codemirror/theme/railscasts.css';
import 'codemirror/theme/rubyblue.css';
import 'codemirror/theme/seti.css';
import 'codemirror/theme/shadowfox.css';
import 'codemirror/theme/solarized.css';
import 'codemirror/theme/ssms.css';
import 'codemirror/theme/the-matrix.css';
import 'codemirror/theme/tomorrow-night-bright.css';
import 'codemirror/theme/tomorrow-night-eighties.css';
import 'codemirror/theme/ttcn.css';
import 'codemirror/theme/twilight.css';
import 'codemirror/theme/vibrant-ink.css';
import 'codemirror/theme/xq-dark.css';
import 'codemirror/theme/xq-light.css';
import 'codemirror/theme/yeti.css';
import 'codemirror/theme/zenburn.css';
import CodeMirror from "codemirror/lib/codemirror.js";
import "codemirror/mode/javascript/javascript.js";
import "./CodeMirrorNetscriptMode";
import 'codemirror/keymap/sublime.js';
import 'codemirror/keymap/vim.js';
import 'codemirror/keymap/emacs.js';
import 'codemirror/addon/comment/continuecomment.js';
import 'codemirror/addon/dialog/dialog.css';
import 'codemirror/addon/dialog/dialog.js';
import 'codemirror/addon/edit/closebrackets.js';
import 'codemirror/addon/edit/matchbrackets.js';
import 'codemirror/addon/fold/foldcode.js';
import 'codemirror/addon/fold/foldgutter.js';
import 'codemirror/addon/fold/foldgutter.css';
import 'codemirror/addon/fold/brace-fold.js';
import 'codemirror/addon/fold/indent-fold.js';
import 'codemirror/addon/fold/comment-fold.js';
import 'codemirror/addon/hint/javascript-hint.js';
import 'codemirror/addon/hint/show-hint.js';
import 'codemirror/addon/hint/show-hint.css';
import 'codemirror/addon/lint/lint.js';
import 'codemirror/addon/lint/lint.css';
import 'codemirror/addon/search/match-highlighter.js';
import 'codemirror/addon/selection/active-line.js';
import { JSHINT } from 'jshint';
import './CodeMirrorNetscriptLint.js';
import { NetscriptFunctions } from "../NetscriptFunctions";
import { CodeMirrorKeybindingSetting,
CodeMirrorThemeSetting } from "../Settings/SettingEnums";
import { Settings } from "../Settings/Settings";
import { clearEventListeners } from "../../utils/uiHelpers/clearEventListeners";
import { createElement } from "../../utils/uiHelpers/createElement";
import { createOptionElement } from "../../utils/uiHelpers/createOptionElement";
import { removeChildrenFromElement } from "../../utils/uiHelpers/removeChildrenFromElement";
(function() {
window.JSHINT = JSHINT;
})();
// Max number of invisibles to be shown in a group if the "Show Invisibles" option
// is marked
const MaxInvisibles = 20;
function validateInitializationParamters(params) {
if (params.saveAndCloseFn == null) { return false; } // Save & close button function
if (params.quitFn == null) { return false; } // Quitting editor, aka Engine.loadTerminalContent
return true;
}
class CodeMirrorEditorWrapper extends ScriptEditor {
constructor() {
super();
this.vimCommandDisplay = null;
this.vimCommandDisplayWrapper = null;
this.tabsStyleElement = null;
}
init(params) {
if (this.editor != null) {
console.error(`CodeMirrorEditor.init() called when it's already initialized`);
return false;
}
// Validate/Sanitize input
if (!validateInitializationParamters(params)) {
console.error(`'params' argument passed into CodeMirrorEditor.init() does not have proper properties`);
return false;
}
// Store the filename input
this.filenameInput = document.getElementById("script-editor-filename");
if (this.filenameInput == null) {
console.error(`Could not get Script Editor filename element (id=script-editor-filename)`);
return false;
}
// Add styling for the "Show Invisibles" option for spaces
const classBase = '.CodeMirror .cm-whitespace-';
const spaceChar = '·';
const style = document.createElement('style');
style.setAttribute('data-name', 'js-show-invisibles');
let rules = '';
let spaceChars = '';
for (let i = 1; i <= MaxInvisibles; ++i) {
spaceChars += spaceChar;
const rule = classBase + i + '::before { content: "' + spaceChars + '";}\n';
rules += rule;
}
style.textContent = rules;
document.head.appendChild(style);
// Add an element for the "Show Invisible" option for tabs
this.tabsStyleElement = document.createElement('style');
document.head.appendChild(this.tabsStyleElement);
// Store a reference to the VIM command display
this.vimCommandDisplay = document.getElementById("codemirror-vim-command-display");
this.vimCommandDisplayWrapper = document.getElementById("codemirror-vim-command-display-wrapper");
// Define a "Save" command for CodeMirror so shortcuts like Ctrl + s
// will save in-game
CodeMirror.commands.save = function() { params.saveAndCloseFn(); }
// Add Netscript Functions to the autocompleter
const netscriptFns = [];
var fnsObj = NetscriptFunctions(null);
for (let name in fnsObj) {
if (fnsObj.hasOwnProperty(name)) {
netscriptFns.push(name);
//Get functions from namespaces
const namespaces = ["bladeburner", "hacknet", "codingcontract", "gang", "sleeve", "heart", "formulas"];
if (namespaces.includes(name)) {
let namespace = fnsObj[name];
if (typeof namespace !== "object") {continue;}
let namespaceFns = Object.keys(namespace);
for (let i = 0; i < namespaceFns.length; ++i) {
netscriptFns.push(namespaceFns[i]);
}
}
}
}
CodeMirror.hint.netscript = function(editor) {
const origList = CodeMirror.hint.javascript(editor) || {from: editor.getCursor(), to: editor.getCursor(), list: []};
origList.list.push(...netscriptFns);
let list = origList.list || [];
let cursor = editor.getCursor();
let currentLine = editor.getLine(cursor.line);
let start = cursor.ch;
let end = start;
while (end < currentLine.length && /[\w$]+/.test(currentLine.charAt(end))) ++end;
while (start && /[\w$]+/.test(currentLine.charAt(start - 1))) --start;
let curWord = start != end && currentLine.slice(start, end);
let regex = new RegExp('^' + curWord, 'i');
let result = {
list: (!curWord ? list : list.filter(function (item) {
return item.match(regex);
})).sort(),
from: CodeMirror.Pos(cursor.line, start),
to: CodeMirror.Pos(cursor.line, end),
};
return result;
};
// Configure VIM keybindings
var VimApi = CodeMirror.Vim;
VimApi.defineEx('write', 'w', function() {
params.saveAndCloseFn();
});
VimApi.defineEx('quit', 'q', function() {
params.quitFn();
});
VimApi.defineEx('xwritequit', 'x', function() {
params.saveAndCloseFn();
});
VimApi.defineEx('wqwritequit', 'wq', function() {
params.saveAndCloseFn();
});
}
initialized() {
return (this.filenameInput != null);
}
create() {
function safeGetElementById(id, whatFor="") {
const elem = document.getElementById(id);
if (elem == null) {
throw new Error(`Could not find ${whatFor} DOM element(id=${id})`);
}
return elem;
}
function safeClearEventListeners(id, whatFor="") {
const elem = clearEventListeners(id);
if (elem == null) {
throw new Error(`Could not find ${whatFor} DOM element(id=${id})`);
}
return elem;
}
try {
if (!this.initialized()) {
console.warn(`CodeMirrorEditor.create() called when editor was not initialized`);
return;
}
// Get and sanitize the keybinding (keymap) setting
if (!(Object.values(CodeMirrorKeybindingSetting).includes(Settings.EditorKeybinding))) {
Settings.EditorKeybinding = CodeMirrorKeybindingSetting.Default;
}
// Initialize CodeMirror Editor
const textAreaElement = safeGetElementById("codemirror-editor", "CodeMirror Textarea");
const formElement = safeGetElementById("codemirror-form-wrapper", "CodeMirror Form Wrapper");
formElement.style.display = "block";
this.editor = CodeMirror.fromTextArea(textAreaElement, {
autofocus: true,
extraKeys: { "Ctrl-Space": "autocomplete" },
foldGutter: true,
gutters: ["CodeMirror-lint-markers", "CodeMirror-linenumbers", "CodeMirror-foldgutter"],
highlightSelectionMatches: true,
hintOptions: { hint: CodeMirror.hint.netscript },
indentUnit: 4,
keyMap: "default",
lineNumbers: true,
matchBrackets: true,
maxInvisibles: 32,
mode: "netscript",
theme: Settings.EditorTheme,
});
// Setup Theme Option
const themeDropdown = safeClearEventListeners("script-editor-option-theme", "Theme Selector");
removeChildrenFromElement(themeDropdown);
const themeOptions = Object.keys(CodeMirrorThemeSetting);
for (let i = 0; i < themeOptions.length; ++i) {
const themeKey = themeOptions[i];
const themeValue = CodeMirrorThemeSetting[themeKey];
themeDropdown.add(createOptionElement(themeKey, themeValue));
}
if (Settings.EditorTheme) {
var initialIndex = 0;
for (var i = 0; i < themeDropdown.options.length; ++i) {
if (themeDropdown.options[i].value === Settings.EditorTheme) {
initialIndex = i;
break;
}
}
themeDropdown.selectedIndex = initialIndex;
} else {
themeDropdown.selectedIndex = 0;
}
themeDropdown.onchange = () => {
const val = themeDropdown.value;
Settings.EditorTheme = val;
this.editor.setOption("theme", val);
};
themeDropdown.onchange();
// Setup Keymap Option
const keybindingDropdown = safeClearEventListeners("script-editor-option-keybinding", "Keymap Selector");
if (keybindingDropdown == null) {
console.error(`Could not find Script Editor's keybinding selector element (id="script-editor-option-keybinding")`);
return false;
}
removeChildrenFromElement(keybindingDropdown);
keybindingDropdown.add(createOptionElement("Default", CodeMirrorKeybindingSetting.Default));
keybindingDropdown.add(createOptionElement("Sublime", CodeMirrorKeybindingSetting.Sublime));
keybindingDropdown.add(createOptionElement("Vim", CodeMirrorKeybindingSetting.Vim));
keybindingDropdown.add(createOptionElement("Emacs", CodeMirrorKeybindingSetting.Emacs));
if (Settings.EditorKeybinding) {
var initialIndex = 0;
for (var i = 0; i < keybindingDropdown.options.length; ++i) {
if (keybindingDropdown.options[i].value === Settings.EditorKeybinding) {
initialIndex = i;
break;
}
}
keybindingDropdown.selectedIndex = initialIndex;
} else {
keybindingDropdown.selectedIndex = 0;
}
keybindingDropdown.onchange = () => {
// Set Vim command display to be invisible initially
this.vimCommandDisplayWrapper.style.display = "none";
const val = keybindingDropdown.value;
Settings.EditorKeybinding = val;
this.editor.removeKeyMap(CodeMirror.keyMap.default);
this.editor.removeKeyMap(CodeMirror.keyMap.sublime);
this.editor.removeKeyMap(CodeMirror.keyMap.emacs);
this.editor.removeKeyMap(CodeMirror.keyMap.vim);
// Setup the VIM command display
let keys = '';
const handleVimKeyPress = (key) => {
keys = keys + key;
this.vimCommandDisplay.innerHTML = keys;
}
const handleVimCommandDone = () => {
keys = '';
this.vimCommandDisplay.innerHTML = keys;
}
if (val === CodeMirrorKeybindingSetting.Vim) {
this.vimCommandDisplayWrapper.style.display = "block";
this.editor.on('vim-keypress', handleVimKeyPress);
this.editor.on('vim-command-done', handleVimCommandDone);
} else {
this.vimCommandDisplayWrapper.style.display = "none";
this.editor.off('vim-keypress', handleVimKeyPress);
this.editor.off('vim-command-done', handleVimCommandDone);
}
this.editor.addKeyMap(val);
this.editor.setOption("keyMap", val);
};
keybindingDropdown.onchange();
// Highlight Active line
const highlightActiveChkBox = safeClearEventListeners("script-editor-option-highlightactiveline", "Active Line Checkbox");
highlightActiveChkBox.checked = Settings.EditorHighlightActiveLine;
highlightActiveChkBox.onchange = () => {
Settings.EditorHighlightActiveLine = highlightActiveChkBox.checked;
this.editor.setOption("styleActiveLine", highlightActiveChkBox.checked);
};
highlightActiveChkBox.onchange();
// Show Invisibles
const showInvisiblesChkBox = safeClearEventListeners("script-editor-option-showinvisibles", "Show Invisible Checkbox");
showInvisiblesChkBox.checked = Settings.EditorShowInvisibles;
showInvisiblesChkBox.onchange = () => {
Settings.EditorShowInvisibles = showInvisiblesChkBox.checked;
const overlayMode = {
name: 'invisibles',
token: function(stream) {
var ret,
spaces = 0,
space = stream.peek() === ' ';
if (space) {
while (space && spaces < MaxInvisibles) {
++spaces;
stream.next();
space = stream.peek() === ' ';
}
ret = 'whitespace whitespace-' + spaces;
} else {
while (!stream.eol() && !space) {
stream.next();
space = stream.peek() === ' ';
}
ret = 'cm-eol';
}
return ret;
},
};
if (showInvisiblesChkBox.checked) {
// Spaces
this.editor.addOverlay(overlayMode);
// Tabs
this.tabsStyleElement.innerHTML = ".cm-tab {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAYAAAAkuj5RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABDSURBVEhLYxgFo2BkAGYH/9r/QFoAxIGyhx6AORzZA4xD1TcHNjYzQplDB2CLgaECYHkADIZqqgGBoZdsRsHgBgwMAB8iFHF42AERAAAAAElFTkSuQmCC);background-position: right;background-repeat: no-repeat;}";
} else {
this.editor.removeOverlay("invisibles");
this.tabsStyleElement.innerHTML = "";
}
};
showInvisiblesChkBox.onchange();
//Use Soft Tab
const softTabChkBox = safeClearEventListeners("script-editor-option-usesofttab", "Soft Tab Checkbox");
softTabChkBox.checked = Settings.EditorUseSoftTab;
softTabChkBox.onchange = () => {
Settings.EditorUseSoftTab = softTabChkBox.checked;
this.editor.setOption("indentWithTabs", !softTabChkBox.checked);
if (softTabChkBox.checked) {
this.editor.addKeyMap({
name: "soft-tabs-keymap",
"Tab": function (cm) {
if (cm.somethingSelected()) {
var sel = cm.getSelection("\n");
// Indent only if there are multiple lines selected, or if the selection spans a full line
if (sel.length > 0 && (sel.indexOf("\n") > -1 || sel.length === cm.getLine(cm.getCursor().line).length)) {
cm.indentSelection("add");
return;
}
}
if (cm.options.indentWithTabs)
cm.execCommand("insertTab");
else
cm.execCommand("insertSoftTab");
},
"Shift-Tab": function (cm) {
cm.indentSelection("subtract");
},
});
} else {
this.editor.removeKeyMap("soft-tabs-keymap");
}
};
softTabChkBox.onchange();
// Some helper functions for dealing with flexible options
function resetFlexibleOption(id) {
const fieldset = safeGetElementById(id);
removeChildrenFromElement(fieldset);
fieldset.style.display = "block";
return fieldset;
}
function removeFlexibleOption(id) {
// This doesn't really remove it, just sets it to invisible
const fieldset = resetFlexibleOption(id);
fieldset.style.display = "none";
return fieldset;
}
// Flex 1: Automatically Close Brackets and Quotes
const flex1Fieldset = resetFlexibleOption("script-editor-option-flex1-fieldset");
const flex1Id = "script-editor-option-flex1";
flex1Fieldset.appendChild(createElement("label", {
for: flex1Id,
innerText: "Auto-Close Brackets/Quotes",
}));
const flex1Checkbox = createElement("input", {
checked: Settings.EditorAutoCloseBrackets,
id: flex1Id,
name: flex1Id,
type: "checkbox",
});
flex1Fieldset.appendChild(flex1Checkbox);
flex1Checkbox.onchange = () => {
Settings.EditorAutoCloseBrackets = flex1Checkbox.checked;
this.editor.setOption("autoCloseBrackets", flex1Checkbox.checked);
};
flex1Checkbox.onchange();
// Flex 2: Disable/Enable Linting
const flex2Fieldset = resetFlexibleOption("script-editor-option-flex2-fieldset");
const flex2Id = "script-editor-option-flex2";
flex2Fieldset.appendChild(createElement("label", {
for: flex2Id,
innerText: "Enable Linting",
}));
const flex2Checkbox = createElement("input", {
checked: Settings.EditorEnableLinting,
id: flex2Id,
name: flex2Id,
type: "checkbox",
});
flex2Fieldset.appendChild(flex2Checkbox);
flex2Checkbox.onchange = () => {
if (flex2Checkbox.checked) {
Settings.EditorEnableLinting = true;
this.editor.setOption("lint", CodeMirror.lint.netscript);
} else {
Settings.EditorEnableLinting = false;
this.editor.setOption("lint", false);
}
}
flex2Checkbox.onchange();
// Flex 3: Continue Comments
const flex3Fieldset = resetFlexibleOption("script-editor-option-flex3-fieldset");
const flex3Id = "script-editor-option-flex3";
flex3Fieldset.appendChild(createElement("label", {
for: flex3Id,
innerText: "Continue Comments",
}));
const flex3Checkbox = createElement("input", {
checked: Settings.EditorContinueComments,
id: flex3Id,
name: flex3Id,
type: "checkbox",
});
flex3Fieldset.appendChild(flex3Checkbox);
flex3Checkbox.onchange = () => {
Settings.EditorContinueComments = flex3Checkbox.checked;
this.editor.setOption("continueComments", flex3Checkbox.checked);
}
flex3Checkbox.onchange();
removeFlexibleOption("script-editor-option-flex4-fieldset");
this.editor.refresh();
} catch(e) {
console.error(`Exception caught: ${e}. ${e.stack}`);
return false;
}
}
isFocused() {
if (this.editor == null) { return false; }
return this.editor.hasFocus();
}
// Sets the editor to be invisible
setInvisible() {
if (!this.initialized()) {
console.warn(`CodeMirrorEditor.setInvisible() called when editor was not initialized`);
return;
}
if (this.editor != null) {
this.editor.toTextArea();
this.editor = null;
}
const elem = document.getElementById("codemirror-form-wrapper");
if (elem instanceof HTMLElement) {
elem.style.display = "none";
}
}
getCursor() {
const c = this.editor.getCursor(); //I need to get the cursor position
return {row: c.line, column: c.ch};
}
setCursor(pos) {
this.editor.setCursor({line: pos.row, ch: pos.column});
}
}
export const CodeMirrorEditor = new CodeMirrorEditorWrapper();

View File

@@ -1,83 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("codemirror/lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["codemirror/lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
// declare global: JSHINT
function validator(text, options) {
if (!window.JSHINT) {
if (window.console) {
window.console.error("Error: window.JSHINT not defined, CodeMirror JavaScript linting cannot run.");
}
return [];
}
// To ignore the 'async/await' errors, we'll manually edit the code ('text')
// that gets processed by JSHINT to include the ignore directory
const splitText = text.split("\n");
const ignoreDirective = " // jshint ignore:line";
for (let i = 0; i < splitText.length; ++i) {
if (splitText[i].match(/.*async function.+{/g)) {
splitText[i] += ignoreDirective;
} else if (splitText[i].match(/.*await.+;/g)) {
splitText[i] += ignoreDirective;
}
}
const sanitizedText = splitText.join("\n");
// Configure JSHINT options
if (!options.indent) // JSHint error.character actually is a column index, this fixes underlining on lines using tabs for indentation
options.indent = 1; // JSHint default value is 4
options.esversion = 9;
JSHINT(sanitizedText, options, options.globals);
var errors = JSHINT.data().errors, result = [];
if (errors) parseErrors(errors, result);
return result;
}
CodeMirror.registerHelper("lint", "netscript", validator);
function parseErrors(errors, output) {
for ( var i = 0; i < errors.length; i++) {
var error = errors[i];
if (error) {
if (error.line == 0) { continue; }
if (error.line < 0) {
if (window.console) {
window.console.warn("Cannot display JSHint error (invalid line " + error.line + ")", error);
}
continue;
}
var start = error.character - 1, end = start + 1;
if (error.evidence) {
var index = error.evidence.substring(start).search(/.\b/);
if (index > -1) {
end += index;
}
}
// Convert to format expected by validation service
var hint = {
message: error.reason,
severity: error.code ? (error.code.startsWith('W') ? "warning" : "error") : "error",
from: CodeMirror.Pos(error.line - 1, start),
to: CodeMirror.Pos(error.line - 1, end),
};
output.push(hint);
}
}
}
});

View File

@@ -1,939 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
import CodeMirror from "codemirror/lib/codemirror.js";
import { NetscriptFunctions } from "../NetscriptFunctions";
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("codemirror/lib/codemirror.js"));
else if (typeof define == "function" && define.amd) // AMD
define(["codemirror/lib/codemirror.js"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineMode("netscript", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var statementIndent = parserConfig.statementIndent;
var jsonldMode = parserConfig.jsonld;
var jsonMode = parserConfig.json || jsonldMode;
var isTS = parserConfig.typescript;
var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
// Tokenizer
var keywords = (function(){
function kw(type) {return {type: type, style: "keyword"};}
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"), D = kw("keyword d");
var operator = kw("operator"), atom = {type: "atom", style: "atom"};
const ret = {
"if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
"return": D, "break": D, "continue": D, "new": kw("new"), "delete": C, "void": C, "throw": C,
"debugger": kw("debugger"), "var": kw("var"), "const": kw("var"), "let": kw("var"),
"function": kw("function"), "catch": kw("catch"),
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
"in": operator, "typeof": operator, "instanceof": operator,
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
"this": kw("this"), "class": kw("class"), "super": kw("atom"),
"yield": C, "export": kw("export"), "import": kw("import"), "extends": C,
"await": C,
// Netscript Basic Functions
"hack": atom,
};
function push(obj) {
for(const key of Object.keys(obj)) {
if(typeof obj[key] === 'function') ret[key] = atom;
if(typeof obj[key] === 'object') push(obj[key]);
}
}
push(NetscriptFunctions(null));
return ret;
}());
var isOperatorChar = /[+\-*&%=<>!?|~^@]/;
var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
function readRegexp(stream) {
var escaped = false, next, inSet = false;
while ((next = stream.next()) != null) {
if (!escaped) {
if (next == "/" && !inSet) return;
if (next == "[") inSet = true;
else if (inSet && next == "]") inSet = false;
}
escaped = !escaped && next == "\\";
}
}
// Used as scratch variables to communicate multiple values without
// consing up tons of objects.
var type, content;
function ret(tp, style, cont) {
type = tp; content = cont;
return style;
}
function tokenBase(stream, state) {
var ch = stream.next();
if (ch == '"' || ch == "'") {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
} else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) {
return ret("number", "number");
} else if (ch == "." && stream.match("..")) {
return ret("spread", "meta");
} else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
return ret(ch);
} else if (ch == "=" && stream.eat(">")) {
return ret("=>", "operator");
} else if (ch == "0" && stream.match(/^(?:x[\da-f]+|o[0-7]+|b[01]+)n?/i)) {
return ret("number", "number");
} else if (/\d/.test(ch)) {
stream.match(/^\d*(?:n|(?:\.\d*)?(?:[eE][+\-]?\d+)?)?/);
return ret("number", "number");
} else if (ch == "/") {
if (stream.eat("*")) {
state.tokenize = tokenComment;
return tokenComment(stream, state);
} else if (stream.eat("/")) {
stream.skipToEnd();
return ret("comment", "comment");
} else if (expressionAllowed(stream, state, 1)) {
readRegexp(stream);
stream.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/);
return ret("regexp", "string-2");
} else {
stream.eat("=");
return ret("operator", "operator", stream.current());
}
} else if (ch == "`") {
state.tokenize = tokenQuasi;
return tokenQuasi(stream, state);
} else if (ch == "#") {
stream.skipToEnd();
return ret("error", "error");
} else if (isOperatorChar.test(ch)) {
if (ch != ">" || !state.lexical || state.lexical.type != ">") {
if (stream.eat("=")) {
if (ch == "!" || ch == "=") stream.eat("=")
} else if (/[<>*+\-]/.test(ch)) {
stream.eat(ch)
if (ch == ">") stream.eat(ch)
}
}
return ret("operator", "operator", stream.current());
} else if (wordRE.test(ch)) {
stream.eatWhile(wordRE);
var word = stream.current()
if (state.lastType != ".") {
if (keywords.propertyIsEnumerable(word)) {
var kw = keywords[word]
return ret(kw.type, kw.style, word)
}
if (word == "async" && stream.match(/^(\s|\/\*.*?\*\/)*[\[\(\w]/, false))
return ret("async", "keyword", word)
}
return ret("variable", "variable", word)
}
}
function tokenString(quote) {
return function(stream, state) {
var escaped = false, next;
if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){
state.tokenize = tokenBase;
return ret("jsonld-keyword", "meta");
}
while ((next = stream.next()) != null) {
if (next == quote && !escaped) break;
escaped = !escaped && next == "\\";
}
if (!escaped) state.tokenize = tokenBase;
return ret("string", "string");
};
}
function tokenComment(stream, state) {
var maybeEnd = false, ch;
while (ch = stream.next()) {
if (ch == "/" && maybeEnd) {
state.tokenize = tokenBase;
break;
}
maybeEnd = (ch == "*");
}
return ret("comment", "comment");
}
function tokenQuasi(stream, state) {
var escaped = false, next;
while ((next = stream.next()) != null) {
if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
state.tokenize = tokenBase;
break;
}
escaped = !escaped && next == "\\";
}
return ret("quasi", "string-2", stream.current());
}
var brackets = "([{}])";
// This is a crude lookahead trick to try and notice that we're
// parsing the argument patterns for a fat-arrow function before we
// actually hit the arrow token. It only works if the arrow is on
// the same line as the arguments and there's no strange noise
// (comments) in between. Fallback is to only notice when we hit the
// arrow, and not declare the arguments as locals for the arrow
// body.
function findFatArrow(stream, state) {
if (state.fatArrowAt) state.fatArrowAt = null;
var arrow = stream.string.indexOf("=>", stream.start);
if (arrow < 0) return;
if (isTS) { // Try to skip TypeScript return type declarations after the arguments
var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow))
if (m) arrow = m.index
}
var depth = 0, sawSomething = false;
for (var pos = arrow - 1; pos >= 0; --pos) {
var ch = stream.string.charAt(pos);
var bracket = brackets.indexOf(ch);
if (bracket >= 0 && bracket < 3) {
if (!depth) { ++pos; break; }
if (--depth == 0) { if (ch == "(") sawSomething = true; break; }
} else if (bracket >= 3 && bracket < 6) {
++depth;
} else if (wordRE.test(ch)) {
sawSomething = true;
} else if (/["'\/]/.test(ch)) {
return;
} else if (sawSomething && !depth) {
++pos;
break;
}
}
if (sawSomething && !depth) state.fatArrowAt = pos;
}
// Parser
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true};
function JSLexical(indented, column, type, align, prev, info) {
this.indented = indented;
this.column = column;
this.type = type;
this.prev = prev;
this.info = info;
if (align != null) this.align = align;
}
function inScope(state, varname) {
for (var v = state.localVars; v; v = v.next)
if (v.name == varname) return true;
for (var cx = state.context; cx; cx = cx.prev) {
for (var v = cx.vars; v; v = v.next)
if (v.name == varname) return true;
}
}
function parseJS(state, style, type, content, stream) {
var cc = state.cc;
// Communicate our context to the combinators.
// (Less wasteful than consing up a hundred closures on every call.)
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = true;
while(true) {
var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
if (combinator(type, content)) {
while(cc.length && cc[cc.length - 1].lex)
cc.pop()();
if (cx.marked) return cx.marked;
if (type == "variable" && inScope(state, content)) return "variable-2";
return style;
}
}
}
// Combinator utils
var cx = {state: null, column: null, marked: null, cc: null};
function pass() {
for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
}
function cont() {
pass.apply(null, arguments);
return true;
}
function inList(name, list) {
for (var v = list; v; v = v.next) if (v.name == name) return true
return false;
}
function register(varname) {
var state = cx.state;
cx.marked = "def";
if (state.context) {
if (state.lexical.info == "var" && state.context && state.context.block) {
// FIXME function decls are also not block scoped
var newContext = registerVarScoped(varname, state.context)
if (newContext != null) {
state.context = newContext
return
}
} else if (!inList(varname, state.localVars)) {
state.localVars = new Var(varname, state.localVars)
return
}
}
// Fall through means this is global
if (parserConfig.globalVars && !inList(varname, state.globalVars))
state.globalVars = new Var(varname, state.globalVars)
}
function registerVarScoped(varname, context) {
if (!context) {
return null
} else if (context.block) {
var inner = registerVarScoped(varname, context.prev)
if (!inner) return null
if (inner == context.prev) return context
return new Context(inner, context.vars, true)
} else if (inList(varname, context.vars)) {
return context
} else {
return new Context(context.prev, new Var(varname, context.vars), false)
}
}
function isModifier(name) {
return name == "public" || name == "private" || name == "protected" || name == "abstract" || name == "readonly"
}
// Combinators
function Context(prev, vars, block) { this.prev = prev; this.vars = vars; this.block = block }
function Var(name, next) { this.name = name; this.next = next }
var defaultVars = new Var("this", new Var("arguments", null))
function pushcontext() {
cx.state.context = new Context(cx.state.context, cx.state.localVars, false)
cx.state.localVars = defaultVars
}
function pushblockcontext() {
cx.state.context = new Context(cx.state.context, cx.state.localVars, true)
cx.state.localVars = null
}
function popcontext() {
cx.state.localVars = cx.state.context.vars
cx.state.context = cx.state.context.prev
}
popcontext.lex = true
function pushlex(type, info) {
var result = function() {
var state = cx.state, indent = state.indented;
if (state.lexical.type == "stat") indent = state.lexical.indented;
else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev)
indent = outer.indented;
state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
};
result.lex = true;
return result;
}
function poplex() {
var state = cx.state;
if (state.lexical.prev) {
if (state.lexical.type == ")")
state.indented = state.lexical.indented;
state.lexical = state.lexical.prev;
}
}
poplex.lex = true;
function expect(wanted) {
function exp(type) {
if (type == wanted) return cont();
else if (wanted == ";" || type == "}" || type == ")" || type == "]") return pass();
else return cont(exp);
}
return exp;
}
function statement(type, value) {
if (type == "var") return cont(pushlex("vardef", value), vardef, expect(";"), poplex);
if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex);
if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
if (type == "keyword d") return cx.stream.match(/^\s*$/, false) ? cont() : cont(pushlex("stat"), maybeexpression, expect(";"), poplex);
if (type == "debugger") return cont(expect(";"));
if (type == "{") return cont(pushlex("}"), pushblockcontext, block, poplex, popcontext);
if (type == ";") return cont();
if (type == "if") {
if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
cx.state.cc.pop()();
return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse);
}
if (type == "function") return cont(functiondef);
if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
if (type == "class" || (isTS && value == "interface")) {
cx.marked = "keyword"
return cont(pushlex("form", type == "class" ? type : value), className, poplex)
}
if (type == "variable") {
if (isTS && value == "declare") {
cx.marked = "keyword"
return cont(statement)
} else if (isTS && (value == "module" || value == "enum" || value == "type") && cx.stream.match(/^\s*\w/, false)) {
cx.marked = "keyword"
if (value == "enum") return cont(enumdef);
else if (value == "type") return cont(typename, expect("operator"), typeexpr, expect(";"));
else return cont(pushlex("form"), pattern, expect("{"), pushlex("}"), block, poplex, poplex)
} else if (isTS && value == "namespace") {
cx.marked = "keyword"
return cont(pushlex("form"), expression, statement, poplex)
} else if (isTS && value == "abstract") {
cx.marked = "keyword"
return cont(statement)
} else {
return cont(pushlex("stat"), maybelabel);
}
}
if (type == "switch") return cont(pushlex("form"), parenExpr, expect("{"), pushlex("}", "switch"), pushblockcontext,
block, poplex, poplex, popcontext);
if (type == "case") return cont(expression, expect(":"));
if (type == "default") return cont(expect(":"));
if (type == "catch") return cont(pushlex("form"), pushcontext, maybeCatchBinding, statement, poplex, popcontext);
if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
if (type == "async") return cont(statement)
if (value == "@") return cont(expression, statement)
return pass(pushlex("stat"), expression, expect(";"), poplex);
}
function maybeCatchBinding(type) {
if (type == "(") return cont(funarg, expect(")"))
}
function expression(type, value) {
return expressionInner(type, value, false);
}
function expressionNoComma(type, value) {
return expressionInner(type, value, true);
}
function parenExpr(type) {
if (type != "(") return pass()
return cont(pushlex(")"), expression, expect(")"), poplex)
}
function expressionInner(type, value, noComma) {
if (cx.state.fatArrowAt == cx.stream.start) {
var body = noComma ? arrowBodyNoComma : arrowBody;
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, expect("=>"), body, popcontext);
else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
}
var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
if (type == "function") return cont(functiondef, maybeop);
if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), classExpression, poplex); }
if (type == "keyword c" || type == "async") return cont(noComma ? expressionNoComma : expression);
if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
if (type == "{") return contCommasep(objprop, "}", null, maybeop);
if (type == "quasi") return pass(quasi, maybeop);
if (type == "new") return cont(maybeTarget(noComma));
if (type == "import") return cont(expression);
return cont();
}
function maybeexpression(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expression);
}
function maybeoperatorComma(type, value) {
if (type == ",") return cont(expression);
return maybeoperatorNoComma(type, value, false);
}
function maybeoperatorNoComma(type, value, noComma) {
var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
var expr = noComma == false ? expression : expressionNoComma;
if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
if (type == "operator") {
if (/\+\+|--/.test(value) || isTS && value == "!") return cont(me);
if (isTS && value == "<" && cx.stream.match(/^([^>]|<.*?>)*>\s*\(/, false))
return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, me);
if (value == "?") return cont(expression, expect(":"), expr);
return cont(expr);
}
if (type == "quasi") { return pass(quasi, me); }
if (type == ";") return;
if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
if (type == ".") return cont(property, me);
if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
if (isTS && value == "as") { cx.marked = "keyword"; return cont(typeexpr, me) }
if (type == "regexp") {
cx.state.lastType = cx.marked = "operator"
cx.stream.backUp(cx.stream.pos - cx.stream.start - 1)
return cont(expr)
}
}
function quasi(type, value) {
if (type != "quasi") return pass();
if (value.slice(value.length - 2) != "${") return cont(quasi);
return cont(expression, continueQuasi);
}
function continueQuasi(type) {
if (type == "}") {
cx.marked = "string-2";
cx.state.tokenize = tokenQuasi;
return cont(quasi);
}
}
function arrowBody(type) {
findFatArrow(cx.stream, cx.state);
return pass(type == "{" ? statement : expression);
}
function arrowBodyNoComma(type) {
findFatArrow(cx.stream, cx.state);
return pass(type == "{" ? statement : expressionNoComma);
}
function maybeTarget(noComma) {
return function(type) {
if (type == ".") return cont(noComma ? targetNoComma : target);
else if (type == "variable" && isTS) return cont(maybeTypeArgs, noComma ? maybeoperatorNoComma : maybeoperatorComma)
else return pass(noComma ? expressionNoComma : expression);
};
}
function target(_, value) {
if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); }
}
function targetNoComma(_, value) {
if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); }
}
function maybelabel(type) {
if (type == ":") return cont(poplex, statement);
return pass(maybeoperatorComma, expect(";"), poplex);
}
function property(type) {
if (type == "variable") {cx.marked = "property"; return cont();}
}
function objprop(type, value) {
if (type == "async") {
cx.marked = "property";
return cont(objprop);
} else if (type == "variable" || cx.style == "keyword") {
cx.marked = "property";
if (value == "get" || value == "set") return cont(getterSetter);
var m // Work around fat-arrow-detection complication for detecting typescript typed arrow params
if (isTS && cx.state.fatArrowAt == cx.stream.start && (m = cx.stream.match(/^\s*:\s*/, false)))
cx.state.fatArrowAt = cx.stream.pos + m[0].length
return cont(afterprop);
} else if (type == "number" || type == "string") {
cx.marked = jsonldMode ? "property" : (cx.style + " property");
return cont(afterprop);
} else if (type == "jsonld-keyword") {
return cont(afterprop);
} else if (isTS && isModifier(value)) {
cx.marked = "keyword"
return cont(objprop)
} else if (type == "[") {
return cont(expression, maybetype, expect("]"), afterprop);
} else if (type == "spread") {
return cont(expressionNoComma, afterprop);
} else if (value == "*") {
cx.marked = "keyword";
return cont(objprop);
} else if (type == ":") {
return pass(afterprop)
}
}
function getterSetter(type) {
if (type != "variable") return pass(afterprop);
cx.marked = "property";
return cont(functiondef);
}
function afterprop(type) {
if (type == ":") return cont(expressionNoComma);
if (type == "(") return pass(functiondef);
}
function commasep(what, end, sep) {
function proceed(type, value) {
if (sep ? sep.indexOf(type) > -1 : type == ",") {
var lex = cx.state.lexical;
if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
return cont(function(type, value) {
if (type == end || value == end) return pass()
return pass(what)
}, proceed);
}
if (type == end || value == end) return cont();
if (sep && sep.indexOf(";") > -1) return pass(what)
return cont(expect(end));
}
return function(type, value) {
if (type == end || value == end) return cont();
return pass(what, proceed);
};
}
function contCommasep(what, end, info) {
for (var i = 3; i < arguments.length; i++)
cx.cc.push(arguments[i]);
return cont(pushlex(end, info), commasep(what, end), poplex);
}
function block(type) {
if (type == "}") return cont();
return pass(statement, block);
}
function maybetype(type, value) {
if (isTS) {
if (type == ":" || value == "in") return cont(typeexpr);
if (value == "?") return cont(maybetype);
}
}
function mayberettype(type) {
if (isTS && type == ":") {
if (cx.stream.match(/^\s*\w+\s+is\b/, false)) return cont(expression, isKW, typeexpr)
else return cont(typeexpr)
}
}
function isKW(_, value) {
if (value == "is") {
cx.marked = "keyword"
return cont()
}
}
function typeexpr(type, value) {
if (value == "keyof" || value == "typeof" || value == "infer") {
cx.marked = "keyword"
return cont(value == "typeof" ? expressionNoComma : typeexpr)
}
if (type == "variable" || value == "void") {
cx.marked = "type"
return cont(afterType)
}
if (type == "string" || type == "number" || type == "atom") return cont(afterType);
if (type == "[") return cont(pushlex("]"), commasep(typeexpr, "]", ","), poplex, afterType)
if (type == "{") return cont(pushlex("}"), commasep(typeprop, "}", ",;"), poplex, afterType)
if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType, afterType)
if (type == "<") return cont(commasep(typeexpr, ">"), typeexpr)
}
function maybeReturnType(type) {
if (type == "=>") return cont(typeexpr)
}
function typeprop(type, value) {
if (type == "variable" || cx.style == "keyword") {
cx.marked = "property"
return cont(typeprop)
} else if (value == "?" || type == "number" || type == "string") {
return cont(typeprop)
} else if (type == ":") {
return cont(typeexpr)
} else if (type == "[") {
return cont(expect("variable"), maybetype, expect("]"), typeprop)
} else if (type == "(") {
return pass(functiondecl, typeprop)
}
}
function typearg(type, value) {
if (type == "variable" && cx.stream.match(/^\s*[?:]/, false) || value == "?") return cont(typearg)
if (type == ":") return cont(typeexpr)
if (type == "spread") return cont(typearg)
return pass(typeexpr)
}
function afterType(type, value) {
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
if (value == "|" || type == "." || value == "&") return cont(typeexpr)
if (type == "[") return cont(typeexpr, expect("]"), afterType)
if (value == "extends" || value == "implements") { cx.marked = "keyword"; return cont(typeexpr) }
if (value == "?") return cont(typeexpr, expect(":"), typeexpr)
}
function maybeTypeArgs(_, value) {
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
}
function typeparam() {
return pass(typeexpr, maybeTypeDefault)
}
function maybeTypeDefault(_, value) {
if (value == "=") return cont(typeexpr)
}
function vardef(_, value) {
if (value == "enum") {cx.marked = "keyword"; return cont(enumdef)}
return pass(pattern, maybetype, maybeAssign, vardefCont);
}
function pattern(type, value) {
if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(pattern) }
if (type == "variable") { register(value); return cont(); }
if (type == "spread") return cont(pattern);
if (type == "[") return contCommasep(eltpattern, "]");
if (type == "{") return contCommasep(proppattern, "}");
}
function proppattern(type, value) {
if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
register(value);
return cont(maybeAssign);
}
if (type == "variable") cx.marked = "property";
if (type == "spread") return cont(pattern);
if (type == "}") return pass();
if (type == "[") return cont(expression, expect(']'), expect(':'), proppattern);
return cont(expect(":"), pattern, maybeAssign);
}
function eltpattern() {
return pass(pattern, maybeAssign)
}
function maybeAssign(_type, value) {
if (value == "=") return cont(expressionNoComma);
}
function vardefCont(type) {
if (type == ",") return cont(vardef);
}
function maybeelse(type, value) {
if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
}
function forspec(type, value) {
if (value == "await") return cont(forspec);
if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex);
}
function forspec1(type) {
if (type == "var") return cont(vardef, expect(";"), forspec2);
if (type == ";") return cont(forspec2);
if (type == "variable") return cont(formaybeinof);
return pass(expression, expect(";"), forspec2);
}
function formaybeinof(_type, value) {
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
return cont(maybeoperatorComma, forspec2);
}
function forspec2(type, value) {
if (type == ";") return cont(forspec3);
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
return pass(expression, expect(";"), forspec3);
}
function forspec3(type) {
if (type != ")") cont(expression);
}
function functiondef(type, value) {
if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
if (type == "variable") {register(value); return cont(functiondef);}
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, statement, popcontext);
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondef)
}
function functiondecl(type, value) {
if (value == "*") {cx.marked = "keyword"; return cont(functiondecl);}
if (type == "variable") {register(value); return cont(functiondecl);}
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, popcontext);
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondecl)
}
function typename(type, value) {
if (type == "keyword" || type == "variable") {
cx.marked = "type"
return cont(typename)
} else if (value == "<") {
return cont(pushlex(">"), commasep(typeparam, ">"), poplex)
}
}
function funarg(type, value) {
if (value == "@") cont(expression, funarg)
if (type == "spread") return cont(funarg);
if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(funarg); }
return pass(pattern, maybetype, maybeAssign);
}
function classExpression(type, value) {
// Class expressions may have an optional name.
if (type == "variable") return className(type, value);
return classNameAfter(type, value);
}
function className(type, value) {
if (type == "variable") {register(value); return cont(classNameAfter);}
}
function classNameAfter(type, value) {
if (value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, classNameAfter)
if (value == "extends" || value == "implements" || (isTS && type == ",")) {
if (value == "implements") cx.marked = "keyword";
return cont(isTS ? typeexpr : expression, classNameAfter);
}
if (type == "{") return cont(pushlex("}"), classBody, poplex);
}
function classBody(type, value) {
if (type == "async" ||
(type == "variable" &&
(value == "static" || value == "get" || value == "set" || (isTS && isModifier(value))) &&
cx.stream.match(/^\s+[\w$\xa1-\uffff]/, false))) {
cx.marked = "keyword";
return cont(classBody);
}
if (type == "variable" || cx.style == "keyword") {
cx.marked = "property";
return cont(isTS ? classfield : functiondef, classBody);
}
if (type == "number" || type == "string") return cont(isTS ? classfield : functiondef, classBody);
if (type == "[")
return cont(expression, maybetype, expect("]"), isTS ? classfield : functiondef, classBody)
if (value == "*") {
cx.marked = "keyword";
return cont(classBody);
}
if (isTS && type == "(") return pass(functiondecl, classBody)
if (type == ";" || type == ",") return cont(classBody);
if (type == "}") return cont();
if (value == "@") return cont(expression, classBody)
}
function classfield(type, value) {
if (value == "?") return cont(classfield)
if (type == ":") return cont(typeexpr, maybeAssign)
if (value == "=") return cont(expressionNoComma)
var context = cx.state.lexical.prev, isInterface = context && context.info == "interface"
return pass(isInterface ? functiondecl : functiondef)
}
function afterExport(type, value) {
if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
if (type == "{") return cont(commasep(exportField, "}"), maybeFrom, expect(";"));
return pass(statement);
}
function exportField(type, value) {
if (value == "as") { cx.marked = "keyword"; return cont(expect("variable")); }
if (type == "variable") return pass(expressionNoComma, exportField);
}
function afterImport(type) {
if (type == "string") return cont();
if (type == "(") return pass(expression);
return pass(importSpec, maybeMoreImports, maybeFrom);
}
function importSpec(type, value) {
if (type == "{") return contCommasep(importSpec, "}");
if (type == "variable") register(value);
if (value == "*") cx.marked = "keyword";
return cont(maybeAs);
}
function maybeMoreImports(type) {
if (type == ",") return cont(importSpec, maybeMoreImports)
}
function maybeAs(_type, value) {
if (value == "as") { cx.marked = "keyword"; return cont(importSpec); }
}
function maybeFrom(_type, value) {
if (value == "from") { cx.marked = "keyword"; return cont(expression); }
}
function arrayLiteral(type) {
if (type == "]") return cont();
return pass(commasep(expressionNoComma, "]"));
}
function enumdef() {
return pass(pushlex("form"), pattern, expect("{"), pushlex("}"), commasep(enummember, "}"), poplex, poplex)
}
function enummember() {
return pass(pattern, maybeAssign);
}
function isContinuedStatement(state, textAfter) {
return state.lastType == "operator" || state.lastType == "," ||
isOperatorChar.test(textAfter.charAt(0)) ||
/[,.]/.test(textAfter.charAt(0));
}
function expressionAllowed(stream, state, backUp) {
return state.tokenize == tokenBase &&
/^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
(state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
}
// Interface
return {
startState: function(basecolumn) {
var state = {
tokenize: tokenBase,
lastType: "sof",
cc: [],
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
localVars: parserConfig.localVars,
context: parserConfig.localVars && new Context(null, null, false),
indented: basecolumn || 0,
};
if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
state.globalVars = parserConfig.globalVars;
return state;
},
token: function(stream, state) {
if (stream.sol()) {
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = false;
state.indented = stream.indentation();
findFatArrow(stream, state);
}
if (state.tokenize != tokenComment && stream.eatSpace()) return null;
var style = state.tokenize(stream, state);
if (type == "comment") return style;
state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
return parseJS(state, style, type, content, stream);
},
indent: function(state, textAfter) {
if (state.tokenize == tokenComment) return CodeMirror.Pass;
if (state.tokenize != tokenBase) return 0;
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top
// Kludge to prevent 'maybelse' from blocking lexical scope pops
if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
var c = state.cc[i];
if (c == poplex) lexical = lexical.prev;
else if (c != maybeelse) break;
}
while ((lexical.type == "stat" || lexical.type == "form") &&
(firstChar == "}" || ((top = state.cc[state.cc.length - 1]) &&
(top == maybeoperatorComma || top == maybeoperatorNoComma) &&
!/^[,\.=+\-*:?[\(]/.test(textAfter))))
lexical = lexical.prev;
if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
lexical = lexical.prev;
var type = lexical.type, closing = firstChar == type;
if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info.length + 1 : 0);
else if (type == "form" && firstChar == "{") return lexical.indented;
else if (type == "form") return lexical.indented + indentUnit;
else if (type == "stat")
return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);
else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
else return lexical.indented + (closing ? 0 : indentUnit);
},
electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
blockCommentStart: jsonMode ? null : "/*",
blockCommentEnd: jsonMode ? null : "*/",
blockCommentContinue: jsonMode ? null : " * ",
lineComment: jsonMode ? null : "//",
fold: "brace",
closeBrackets: "()[]{}''\"\"``",
helperType: jsonMode ? "json" : "netscript",
jsonldMode: jsonldMode,
jsonMode: jsonMode,
expressionAllowed: expressionAllowed,
skipExpression: function(state) {
var top = state.cc[state.cc.length - 1]
if (top == expression || top == expressionNoComma) state.cc.pop()
},
};
});
CodeMirror.registerHelper("wordChars", "netscript", /[\w$]/);
CodeMirror.defineMIME("text/netscript", "netscript");
CodeMirror.defineMIME("text/ecmascript", "netscript");
CodeMirror.defineMIME("application/netscript", "netscript");
CodeMirror.defineMIME("application/x-netscript", "netscript");
CodeMirror.defineMIME("application/ecmascript", "netscript");
CodeMirror.defineMIME("application/json", {name: "netscript", json: true});
CodeMirror.defineMIME("application/x-json", {name: "netscript", json: true});
CodeMirror.defineMIME("application/ld+json", {name: "netscript", jsonld: true});
CodeMirror.defineMIME("text/typescript", { name: "netscript", typescript: true });
CodeMirror.defineMIME("application/typescript", { name: "netscript", typescript: true });
});

View File

@@ -18,8 +18,8 @@ export class PositionTracker {
const position = this.positions.get(filename);
if (!position) {
return {
row: 0,
column: 0,
row: -1,
column: -1,
};
}
return position;

View File

@@ -0,0 +1,158 @@
export const libSource = `interface NS {
args: string[];
/**
* Example documentation for scan.
* Example documentation for scan.
* Example documentation for scan.
* Example documentation for scan.
* Example documentation for scan.
* Example documentation for scan.
* Example documentation for scan.
* Example documentation for scan.
* Example documentation for scan.
*/
scan(ip: string, hostnames: boolean): string[];
hack(ip: string, threads: number, stock: boolean): Promise<string>;
hackAnalyzeThreads(ip: string, hackAmount: number): number;
hackAnalyzePercent(ip: string): number;
hackChance(ip: string): number;
sleep(time: number): Promise<void>;
grow(ip: string, threads: number, stock: boolean): Promise<string>;
growthAnalyze(ip: string, growth: number): number;
weaken(ip: string, threads: boolean): Promise<string>;
print(...args: any[]): void;
tprint(...args: any[]): void;
clearLog(): void;
disableLog(fn: string): void;
enableLog(fn: string): void;
isLogEnabled(fn: string): boolean;
getScriptLogs(fn: string, ip: string, ...scriptArgs: any[]): string[];
tail(fn: string, ip: string, ...scriptArgs: any[]): void;
nuke(ip: string): boolean;
brutessh(ip: string): boolean;
ftpcrack(ip: string): boolean;
relaysmtp(ip: string): boolean;
httpworm(ip: string): boolean;
sqlinject(ip: string): boolean;
run(scriptname: string, threads: number): number;
exec(scriptname: string, ip: string, threads: number): number;
spawn(scriptname: string, threads: number): void;
kill(filename: string, ip: string, ...scriptArgs: any[]): boolean;
killall(ip: string): boolean;
exit(): void;
scp(scriptname: string, ip1: string, ip2: string): boolean;
ls(ip: string, grep: string): string[];
ps(ip: string): {filename: string, threads: number, args: string[], pid: number}[];
hasRootAccess(ip: string): boolean;
getIp(): string;
getHostname(): string;
getHackingLevel(): number;
getHackingMultipliers(): number;
getHacknetMultipliers(): number;
getBitNodeMultipliers(): number;
getServer(ip: string): any;
getServerMoneyAvailable(ip: string): number;
getServerSecurityLevel(ip: string): number;
getServerBaseSecurityLevel(ip: string): number;
getServerMinSecurityLevel(ip: string): number;
getServerRequiredHackingLevel(ip: string): number;
getServerMaxMoney(ip: string): number;
getServerGrowth(ip: string): number;
getServerNumPortsRequired(ip: string): number;
getServerRam(ip: string): number[];
getServerMaxRam(ip: string): number;
getServerUsedRam(ip: string): number;
serverExists(ip: string): boolean;
fileExists(filename: string, ip: string): boolean;
isRunning(fn: string, ip: string, ...scriptArgs: any[]): boolean;
getStockSymbols(): string[];
getStockPrice(symbol: string): number;
getStockAskPrice(symbol: string): number;
getStockBidPrice(symbol: string): number;
getStockPosition(symbol: string): number;
getStockMaxShares(symbol: string): number;
getStockPurchaseCost(symbol: string, shares: number, posType: string): number;
getStockSaleGain(symbol: string, shares: number, posType: string): number;
buyStock(symbol: string, shares: number): number;
sellStock(symbol: string, shares: number): number;
shortStock(symbol: string, shares: number): number;
sellShort(symbol: string, shares: number): number;
placeOrder(symbol: string, shares: number, price: number, type: string, pos: string): boolean;
cancelOrder(symbol: string, shares: number, price: number, type: string, pos: string): boolean;
getOrders(): any;
getStockVolatility(symbol: string): number;
getStockForecast(symbol: string): number;
getPurchasedServerLimit(): number;
getPurchasedServerMaxRam(): number;
getPurchasedServerCost(ram: number): number;
purchaseServer(hostname: string, ram: number): string;
deleteServer(hostname: string): boolean;
getPurchasedServers(hostname: string): string[];
write(port: number, data: string, mode: string): boolean;
tryWrite(port: number, data: string): boolean;
read(port: number): any;
peek(port: number): any;
clear(port: number): number;
getPortHandle(port: number): any; // netscript port
rm(fn: string, ip: string): boolean;
scriptRunning(scriptname: string, ip: string): boolean;
scriptKill(scriptname: string, ip: string): boolean;
getScriptName(): string;
getScriptRam(scriptname: string, ip: string): number;
getRunningScript(fn: string, ip: string): any; // running script
getHackTime(ip: string): number;
getGrowTime(ip: string): number;
getWeakenTime(ip: string): number;
getScriptIncome(scriptname: string, ip: string): number;
getScriptExpGain(scriptname: string, ip: string): number;
nFormat(n: number, format: string): string;
tFormat(milliseconds: number, milliPrecision: boolean): string;
getTimeSinceLastAug(): number;
prompt(txt: string): Promise<boolean>;
getFavorToDonate(): number;
universityCourse(universityName: string, className: string): boolean;
gymWorkout(gymName: string, stat: string): boolean;
travelToCity(cityname: string): boolean;
purchaseTor(): boolean;
purchaseProgram(programName: string): boolean;
getCurrentServer(): any; // server object
connect(hostname: string): boolean;
manualHack(): Promise<string>;
installBackdoor(): Promise<void>;
getStats(): any; // complex type
getCharacterInformation(): any; // complex type
getPlayer(): any; // complex type
hospitalize(): number;
isBusy(): boolean;
stopAction(): boolean;
upgradeHomeRam(): number;
getUpgradeHomeRamCost(): number;
workForCompany(companyName: string): boolean;
applyToCompany(companyName: string, field: string): boolean;
getCompanyRep(companyName: string): number;
getCompanyFavor(companyName: string): number;
getCompanyFavorGain(companyName: string): number;
checkFactionInvitations(): string[];
joinFaction(name: string): boolean;
workForFaction(name: string, type: string): boolean;
getFactionRep(name: string): number;
getFactionFavor(name: string): number;
getFactionFavorGain(name: string): number;
donateToFaction(name: string, amt: number): boolean;
createProgram(name: string): boolean;
commitCrime(crimeRoughName: string): number;
getCrimeChance(crimeRoughName: string): boolean;
getCrimeStats(crimeRoughName: string): any; // complex type
getOwnedAugmentations(purchased: boolean): string[];
getOwnedSourceFiles(): any; // complex type
getAugmentationsFromFaction(facname: string): string[];
getAugmentationPrereq(name: string): string[];
getAugmentationCost(name: string): number;
getAugmentationStats(name: string): any; // complex type
purchaseAugmentation(faction: string, name: string): boolean;
softReset(cbScript: string): void;
installAugmentations(cbScript: string): void;
exploit(): void;
bypass(doc: any): void;
flags(data: any): any;
}`

View File

@@ -1,61 +0,0 @@
import { CursorPositions } from './CursorPositions';
// Base Script Editor class for the Ace/CodeMirror/etc. wrappers
import { js_beautify as beautify } from 'js-beautify';
export class ScriptEditor {
constructor() {
this.editor = null; // Stores the CodeMirror editor reference
this.filenameInput = null; // Stores the filename input DOM element
}
init() {
throw new Error(`Tried to initialize base ScriptEditor class`);
}
beautifyScript() {
if (this.editor == null) {
console.warn(`ScriptEditor.beautifyScript() called when editor was not initialized`);
return;
}
let code = this.editor.getValue();
code = beautify(code, {
indent_size: 4,
brace_style: "preserve-inline",
});
this.editor.setValue(code);
}
openScript(filename="", code="") {
if (this.editor == null || this.filenameInput == null) {
console.warn(`ScriptEditor.openScript() called when editor was not initialized`);
return;
}
if (filename != "") {
this.filenameInput.value = filename;
this.editor.setValue(code);
this.setCursor(CursorPositions.getCursor(filename));
}
this.editor.focus();
}
getCode() {
if (this.editor == null) {
console.warn(`ScriptEditor.getCode() called when editor was not initialized`);
return "";
}
return this.editor.getValue();
}
getFilename() {
if (this.filenameInput == null) {
console.warn(`ScriptEditor.getFilename() called when editor was not initialized`);
return "";
}
return this.filenameInput.value;
}
}

View File

@@ -0,0 +1,5 @@
export interface Options {
theme: string;
insertSpaces: boolean;
}

View File

@@ -0,0 +1,39 @@
import React, { useState } from 'react';
import { Options } from "./Options";
import { StdButton } from "../../ui/React/StdButton";
import { removePopup } from "../../ui/React/createPopup";
interface IProps {
id: string;
options: Options;
save: (options: Options) => void;
}
export function OptionsPopup(props: IProps): React.ReactElement {
const [theme, setTheme] = useState(props.options.theme);
const [insertSpaces, setInsertSpaces] = useState(props.options.insertSpaces);
function save() {
props.save({
theme: theme,
insertSpaces: insertSpaces,
});
removePopup(props.id);
}
return (<div className="editor-options-container noselect">
<div className="editor-options-line">
<p>Theme: </p>
<select className="dropdown" onChange={event => setTheme(event.target.value)} defaultValue={theme}>
<option value="vs-dark">vs-dark</option>
<option value="light">light</option>
</select>
</div>
<div className="editor-options-line">
<p>Use whitespace over tabs: </p>
<input type="checkbox" onChange={event => setInsertSpaces(event.target.checked)} checked={insertSpaces} />
</div>
<br />
<StdButton style={{width: '50px'}} text={"Save"} onClick={save} />
</div>);
}

View File

@@ -0,0 +1,330 @@
import React, { useState, useEffect, useRef } from 'react';
import { StdButton } from "../../ui/React/StdButton";
import Editor from "@monaco-editor/react";
import * as monaco from "monaco-editor";
type IStandaloneCodeEditor = monaco.editor.IStandaloneCodeEditor;
import { createPopup } from "../../ui/React/createPopup";
import { OptionsPopup } from "./OptionsPopup";
import { Options } from "./Options";
import { js_beautify as beautifyCode } from 'js-beautify';
import { isValidFilePath } from "../../Terminal/DirectoryHelpers";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { parseFconfSettings } from "../../Fconf/Fconf";
import { isScriptFilename } from "../../Script/ScriptHelpersTS";
import { Script } from "../../Script/Script";
import { TextFile } from "../../TextFile";
import { calculateRamUsage } from "../../Script/RamCalculations";
import { RamCalculationErrorCode } from "../../Script/RamCalculationErrorCodes";
import { numeralWrapper } from "../../ui/numeralFormat";
import { CursorPositions } from "../../ScriptEditor/CursorPositions";
import { libSource } from "../NetscriptDefinitions";
import { NetscriptFunctions } from "../../NetscriptFunctions";
import { WorkerScript } from "../../Netscript/WorkerScript";
import { Settings } from "../../Settings/Settings";
import { GetServerByHostname } from "../../Server/ServerHelpers";
import {
iTutorialNextStep,
ITutorial,
iTutorialSteps,
} from "../../InteractiveTutorial";
let symbols: string[] = [];
(function() {
const ns = NetscriptFunctions(({} as WorkerScript));
function populate(ns: any): string[] {
let symbols: string[] = [];
const keys = Object.keys(ns);
for(const key of keys) {
if(typeof ns[key] === 'object') {
symbols.push(key);
symbols = symbols.concat(populate(ns[key]));
}
if(typeof ns[key] === 'function') {
symbols.push(key);
}
}
return symbols;
}
symbols = populate(ns);
})();
interface IProps {
filename: string;
code: string;
player: IPlayer;
engine: IEngine;
}
/*
*/
// How to load function definition in monaco
// https://github.com/Microsoft/monaco-editor/issues/1415
// https://microsoft.github.io/monaco-editor/api/modules/monaco.languages.html
// https://www.npmjs.com/package/@monaco-editor/react#development-playground
// https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-custom-languages
// https://github.com/threehams/typescript-error-guide/blob/master/stories/components/Editor.tsx#L11-L39
// These variables are used to reload a script when it's clicked on. Because we
// won't have references to the old script.
let lastFilename = "";
let lastCode = "";
let lastPosition: monaco.Position | null = null;
export function Root(props: IProps): React.ReactElement {
const editorRef = useRef<IStandaloneCodeEditor | null>(null);
const [filename, setFilename] = useState(props.filename ? props.filename : lastFilename);
const [code, setCode] = useState<string>(props.code ? props.code : lastCode);
const [ram, setRAM] = useState('RAM: ???');
const [options, setOptions] = useState<Options>({
theme: Settings.MonacoTheme,
insertSpaces: Settings.MonacoInsertSpaces,
});
// store the last known state in case we need to restart without nano.
useEffect(() => {
if(props.filename === "") return;
lastFilename = props.filename;
lastCode = props.code;
lastPosition = null;
}, []);
function save(): void {
if(editorRef.current !== null) {
const position = editorRef.current.getPosition();
if(position !== null) {
CursorPositions.saveCursor(filename, {
row: position.lineNumber,
column: position.column,
});
}
}
lastPosition = null;
// TODO(hydroflame): re-enable the tutorial.
if (ITutorial.isRunning && ITutorial.currStep === iTutorialSteps.TerminalTypeScript) {
//Make sure filename + code properly follow tutorial
if (filename !== "n00dles.script") {
dialogBoxCreate("Leave the script name as 'n00dles'!");
return;
}
if (code.replace(/\s/g, "").indexOf("while(true){hack('n00dles');}") == -1) {
dialogBoxCreate("Please copy and paste the code from the tutorial!");
return;
}
//Save the script
const server = props.player.getCurrentServer();
if(server === null) throw new Error('Server should not be null but it is.');
for (let i = 0; i < server.scripts.length; i++) {
if (filename == server.scripts[i].filename) {
server.scripts[i].saveScript(code, props.player.currentServer, server.scripts);
props.engine.loadTerminalContent();
return iTutorialNextStep();
}
}
// If the current script does NOT exist, create a new one
const script = new Script();
script.saveScript(code, props.player.currentServer, server.scripts);
server.scripts.push(script);
return iTutorialNextStep();
}
if (filename == "") {
dialogBoxCreate("You must specify a filename!");
return;
}
if (filename !== ".fconf" && !isValidFilePath(filename)) {
dialogBoxCreate("Script filename can contain only alphanumerics, hyphens, and underscores, and must end with an extension.");
return;
}
const server = props.player.getCurrentServer();
if(server === null) throw new Error('Server should not be null but it is.');
if (filename === ".fconf") {
try {
parseFconfSettings(code);
} catch(e) {
dialogBoxCreate(`Invalid .fconf file: ${e}`);
return;
}
} else if (isScriptFilename(filename)) {
//If the current script already exists on the server, overwrite it
for (let i = 0; i < server.scripts.length; i++) {
if (filename == server.scripts[i].filename) {
server.scripts[i].saveScript(code, props.player.currentServer, server.scripts);
props.engine.loadTerminalContent();
return;
}
}
//If the current script does NOT exist, create a new one
const script = new Script();
script.saveScript(code, props.player.currentServer, server.scripts);
server.scripts.push(script);
} else if (filename.endsWith(".txt")) {
for (let i = 0; i < server.textFiles.length; ++i) {
if (server.textFiles[i].fn === filename) {
server.textFiles[i].write(code);
props.engine.loadTerminalContent();
return;
}
}
const textFile = new TextFile(filename, code);
server.textFiles.push(textFile);
} else {
dialogBoxCreate("Invalid filename. Must be either a script (.script, .js, or .ns) or " +
" or text file (.txt)")
return;
}
props.engine.loadTerminalContent();
}
function beautify(): void {
if (editorRef.current === null) return;
const pretty = beautifyCode(code, {
indent_with_tabs: !options.insertSpaces,
indent_size: 4,
brace_style: "preserve-inline",
});
editorRef.current.setValue(pretty);
}
function onFilenameChange(event: React.ChangeEvent<HTMLInputElement>): void {
lastFilename = filename;
setFilename(event.target.value);
}
function openOptions(): void {
const id="script-editor-options-popup";
const newOptions = {
theme: '',
insertSpaces: false,
};
Object.assign(newOptions, options);
createPopup(id, OptionsPopup, {
id: id,
options: newOptions,
save: (options: Options) => {
setOptions(options);
Settings.MonacoTheme = options.theme;
Settings.MonacoInsertSpaces = options.insertSpaces;
},
});
}
function updateCode(newCode?: string): void {
if(newCode === undefined) return;
lastCode = newCode;
if(editorRef.current !== null) {
lastPosition = editorRef.current.getPosition();
}
setCode(newCode);
}
async function updateRAM(): Promise<void> {
const codeCopy = code+"";
const ramUsage = await calculateRamUsage(codeCopy, props.player.getCurrentServer().scripts);
if (ramUsage > 0) {
setRAM("RAM: " + numeralWrapper.formatRAM(ramUsage));
return;
}
switch (ramUsage) {
case RamCalculationErrorCode.ImportError: {
setRAM("RAM: Import Error");
break;
}
case RamCalculationErrorCode.URLImportError: {
setRAM("RAM: HTTP Import Error");
break;
}
case RamCalculationErrorCode.SyntaxError:
default: {
setRAM("RAM: Syntax Error");
break;
}
}
return new Promise<void>(() => undefined);
}
useEffect(() => {
const id = setInterval(updateRAM, 1000);
return () => clearInterval(id);
}, [code]);
useEffect(() => {
function maybeSave(event: KeyboardEvent) {
if (Settings.DisableHotkeys) return;
//Ctrl + b
if (event.keyCode == 66 && (event.ctrlKey || event.metaKey)) {
event.preventDefault();
save();
}
}
document.addEventListener('keydown', maybeSave);
return () => document.removeEventListener('keydown', maybeSave);
})
function onMount(editor: IStandaloneCodeEditor): void {
editorRef.current = editor;
if(editorRef.current === null) return;
const position = CursorPositions.getCursor(filename);
if(position.row !== -1)
editorRef.current.setPosition({lineNumber: position.row, column: position.column});
else if(lastPosition !== null)
editorRef.current.setPosition({lineNumber: lastPosition.lineNumber, column: lastPosition.column+1});
editorRef.current.focus();
}
function beforeMount(monaco: any): void {
monaco.languages.registerCompletionItemProvider('javascript', {
provideCompletionItems: () => {
const suggestions = [];
for(const symbol of symbols) {
suggestions.push({
label: symbol,
kind: monaco.languages.CompletionItemKind.Function,
insertText: symbol,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
});
}
return { suggestions: suggestions };
},
});
monaco.languages.typescript.javascriptDefaults.addExtraLib(libSource, 'netscript.d.ts');
monaco.languages.typescript.typescriptDefaults.addExtraLib(libSource, 'netscript.d.ts');
}
return (<div id="script-editor-wrapper">
<div id="script-editor-filename-wrapper">
<p id="script-editor-filename-tag" className="noselect"> <strong style={{backgroundColor:'#555'}}>Script name: </strong></p>
<input id="script-editor-filename" type="text" maxLength={100} tabIndex={1} value={filename} onChange={onFilenameChange} />
<StdButton text={"options"} onClick={openOptions} />
</div>
<Editor
beforeMount={beforeMount}
onMount={onMount}
loading={<p>Loading script editor!</p>}
height="80%"
defaultLanguage="javascript"
defaultValue={code}
onChange={updateCode}
theme={options.theme}
options={options}
/>
<div id="script-editor-buttons-wrapper">
<StdButton text={"Beautify"} onClick={beautify} />
<p id="script-editor-status-text" style={{display:"inline-block", margin:"10px"}}>{ram}</p>
<button className="std-button" style={{display: "inline-block"}} onClick={save}>Save & Close (Ctrl/Cmd + b)</button>
<a className="std-button" style={{display: "inline-block"}} target="_blank" href="https://bitburner.readthedocs.io/en/latest/index.html">Netscript Documentation</a>
</div>
</div>);
}

View File

@@ -60,8 +60,8 @@ export function numCycleForGrowth(server: Server, growth: number, p: IPlayer): n
}
//Applied server growth for a single server. Returns the percentage growth
export function processSingleServerGrowth(server: Server, threads: number, p: IPlayer): number {
let serverGrowth = calculateServerGrowth(server, threads, p);
export function processSingleServerGrowth(server: Server, threads: number, p: IPlayer, cores = 1): number {
let serverGrowth = calculateServerGrowth(server, threads, p, cores);
if (serverGrowth < 1) {
console.warn("serverGrowth calculated to be less than 1");
serverGrowth = 1;

View File

@@ -3,7 +3,7 @@ import { Server } from "../Server";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { IPlayer } from "../../PersonObjects/IPlayer";
export function calculateServerGrowth(server: Server, threads: number, p: IPlayer): number {
export function calculateServerGrowth(server: Server, threads: number, p: IPlayer, cores = 1): number {
const numServerGrowthCycles = Math.max(Math.floor(threads), 0);
//Get adjusted growth rate, which accounts for server security
@@ -16,5 +16,6 @@ export function calculateServerGrowth(server: Server, threads: number, p: IPlaye
const numServerGrowthCyclesAdjusted = numServerGrowthCycles * serverGrowthPercentage * BitNodeMultipliers.ServerGrowthRate;
//Apply serverGrowth for the calculated number of growth cycles
return Math.pow(adjGrowthRate, numServerGrowthCyclesAdjusted * p.hacking_grow_mult);
const coreBonus = 1+(cores-1)/16;
return Math.pow(adjGrowthRate, numServerGrowthCyclesAdjusted * p.hacking_grow_mult * coreBonus);
}

View File

@@ -85,23 +85,6 @@ interface IDefaultSettings {
* Represents all possible settings the player wants to customize to their play style.
*/
interface ISettings extends IDefaultSettings {
/**
* Which editor should be used (CodeMirror or Ace)?
*/
Editor: EditorSetting;
/**
* The keybinding to use in the script editor.
* TODO: This should really be an enum of allowed values.
*/
EditorKeybinding: AceKeybindingSetting | CodeMirrorKeybindingSetting;
/**
* The theme used in the script editor.
* TODO: This should really be an enum of allowed values.
*/
EditorTheme: string | CodeMirrorThemeSetting;
/**
* What order the player's owned Augmentations/Source Files should be displayed in
*/
@@ -112,35 +95,10 @@ interface ISettings extends IDefaultSettings {
*/
PurchaseAugmentationsOrder: PurchaseAugmentationsOrderSetting;
/**
* Editor settings to highlight active line.
*/
EditorHighlightActiveLine: boolean;
/**
* Editor settings to show spaces and tabs.
*/
EditorShowInvisibles: boolean;
/**
* Editor settings to use tabs or 4 spaces.
*/
EditorUseSoftTab: boolean;
/**
* Editor settings to add matching bracket.
*/
EditorAutoCloseBrackets: boolean;
/**
* Editor settings to show linting (like missing semicolons)
*/
EditorEnableLinting: boolean;
/**
* Editor settings to add extra * when entering new line inside a /* comment.
*/
EditorContinueComments: boolean;
MonacoTheme: string;
MonacoInsertSpaces: boolean;
}
const defaultSettings: IDefaultSettings = {
@@ -170,9 +128,6 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
DisableASCIIArt: defaultSettings.DisableASCIIArt,
DisableHotkeys: defaultSettings.DisableHotkeys,
DisableTextEffects: defaultSettings.DisableTextEffects,
Editor: EditorSetting.CodeMirror,
EditorKeybinding: CodeMirrorKeybindingSetting.Default,
EditorTheme: "Monokai",
Locale: "en",
MaxLogCapacity: defaultSettings.MaxLogCapacity,
MaxPortCapacity: defaultSettings.MaxPortCapacity,
@@ -184,12 +139,8 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
SuppressMessages: defaultSettings.SuppressMessages,
SuppressTravelConfirmation: defaultSettings.SuppressTravelConfirmation,
SuppressBladeburnerPopup: defaultSettings.SuppressBladeburnerPopup,
EditorHighlightActiveLine: true,
EditorShowInvisibles: false,
EditorUseSoftTab: true,
EditorAutoCloseBrackets: true,
EditorEnableLinting: true,
EditorContinueComments: true,
MonacoTheme: 'vs-dark',
MonacoInsertSpaces: false,
init() {
Object.assign(Settings, defaultSettings);
},

View File

@@ -56,7 +56,6 @@ import { RunningScript } from "./Script/RunningScript";
import { compareArrays } from "../utils/helpers/compareArrays";
import { getRamUsageFromRunningScript } from "./Script/RunningScriptHelpers";
import {
getCurrentEditor,
findRunningScript,
findRunningScriptByPid,
} from "./Script/ScriptHelpers";
@@ -98,7 +97,9 @@ import { Money } from "./ui/React/Money";
import autosize from "autosize";
import * as JSZip from "jszip";
import * as FileSaver from "file-saver";
import * as libarg from 'arg';
import React from "react";
import ReactDOM from 'react-dom';
function postNetburnerText() {
@@ -1886,7 +1887,6 @@ let Terminal = {
}`;
}
Engine.loadScriptEditorContent(filepath, code);
getCurrentEditor().setCursor({row: 1, column: 4});
} else {
Engine.loadScriptEditorContent(filepath, script.code);
}
@@ -2378,28 +2378,18 @@ let Terminal = {
}
const server = Player.getCurrentServer();
let numThreads = 1;
const args = [];
const scriptName = Terminal.getFilepath(commandArray[1]);
if (commandArray.length > 2) {
if (commandArray.length >= 4 && commandArray[2] == "-t") {
numThreads = Math.round(parseFloat(commandArray[3]));
if (isNaN(numThreads) || numThreads < 1) {
postError("Invalid number of threads specified. Number of threads must be greater than 0");
return;
}
for (let i = 4; i < commandArray.length; ++i) {
args.push(commandArray[i]);
}
} else {
for (let i = 2; i < commandArray.length; ++i) {
args.push(commandArray[i])
}
}
const runArgs = {'--tail': Boolean, '-t': Number};
const flags = libarg(runArgs, {permissive: true, argv: commandArray.slice(2)});
const threadFlag = Math.round(parseFloat(flags['-t']));
const tailFlag = flags['--tail'] === true;
if(flags['-t'] !== undefined && (threadFlag < 0 || isNaN(threadFlag))) {
postError("Invalid number of threads specified. Number of threads must be greater than 0");
return;
}
const numThreads = !isNaN(threadFlag) && threadFlag > 0 ? threadFlag : 1;
const args = flags['_'];
// Check if this script is already running
if (findRunningScript(scriptName, args, server) != null) {
@@ -2408,33 +2398,41 @@ let Terminal = {
}
// Check if the script exists and if it does run it
for (var i = 0; i < server.scripts.length; i++) {
if (server.scripts[i].filename === scriptName) {
// Check for admin rights and that there is enough RAM availble to run
var script = server.scripts[i];
var ramUsage = script.ramUsage * numThreads;
var ramAvailable = server.maxRam - server.ramUsed;
if (server.hasAdminRights == false) {
post("Need root access to run script");
return;
} else if (ramUsage > ramAvailable){
post("This machine does not have enough RAM to run this script with " +
numThreads + " threads. Script requires " + ramUsage + "GB of RAM");
return;
} else {
// Able to run script
var runningScriptObj = new RunningScript(script, args);
runningScriptObj.threads = numThreads;
if (startWorkerScript(runningScriptObj, server)) {
post(`Running script with ${numThreads} thread(s), pid ${runningScriptObj.pid} and args: ${arrayToString(args)}.`);
} else {
postError(`Failed to start script`);
}
return;
}
for (let i = 0; i < server.scripts.length; i++) {
if (server.scripts[i].filename !== scriptName) {
continue
}
// Check for admin rights and that there is enough RAM availble to run
const script = server.scripts[i];
const ramUsage = script.ramUsage * numThreads;
const ramAvailable = server.maxRam - server.ramUsed;
if (!server.hasAdminRights) {
post("Need root access to run script");
return;
}
if (ramUsage > ramAvailable){
post("This machine does not have enough RAM to run this script with " +
numThreads + " threads. Script requires " + ramUsage + "GB of RAM");
return;
}
// Able to run script
const runningScript = new RunningScript(script, args);
runningScript.threads = numThreads;
const success = startWorkerScript(runningScript, server);
if (!success) {
postError(`Failed to start script`);
return
}
post(`Running script with ${numThreads} thread(s), pid ${runningScript.pid} and args: ${arrayToString(args)}.`);
if(tailFlag) {
logBoxCreate(runningScript)
}
return;
}
post("ERROR: No such script");

View File

@@ -7,7 +7,6 @@ import {
convertTimeMsToTimeElapsedString,
replaceAt,
} from "../utils/StringHelperFunctions";
import { logBoxUpdateText, logBoxOpened } from "../utils/LogBox";
import { Augmentations } from "./Augmentation/Augmentations";
import {
initAugmentations,
@@ -58,6 +57,7 @@ import {
loadAllRunningScripts,
updateOnlineScriptTimes,
} from "./NetscriptWorker";
import { GetServerByHostname } from "./Server/ServerHelpers";
import { Player } from "./Player";
import { prestigeAugmentation } from "./Prestige";
import {
@@ -67,11 +67,7 @@ import {
} from "./Programs/ProgramHelpers";
import { redPillFlag } from "./RedPill";
import { saveObject, loadGame } from "./SaveObject";
import {
getCurrentEditor,
scriptEditorInit,
updateScriptEditorContent,
} from "./Script/ScriptHelpers";
import { Root as ScriptEditorRoot } from "./ScriptEditor/ui/Root";
import { initForeignServers, AllServers } from "./Server/AllServers";
import { Settings } from "./Settings/Settings";
import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags";
@@ -106,6 +102,9 @@ import { ActiveScriptsRoot } from "./ui/ActiveScripts/Root";
import { initializeMainMenuHeaders } from "./ui/MainMenu/Headers";
import { initializeMainMenuLinks, MainMenuLinks } from "./ui/MainMenu/Links";
import { FileDiagnosticPopup } from "./Diagnostic/FileDiagnosticPopup";
import { createPopup } from "./ui/React/createPopup";
import { dialogBoxCreate } from "../utils/DialogBox";
import { gameOptionsBoxClose, gameOptionsBoxOpen } from "../utils/GameOptions";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
@@ -136,13 +135,6 @@ import ReactDOM from "react-dom";
$(document).keydown(function(e) {
if (Settings.DisableHotkeys === true) {return;}
// These hotkeys should be disabled if the player is writing scripts
try {
if (getCurrentEditor().isFocused()) {
return;
}
} catch(error) {}
if (!Player.isWorking && !redPillFlag && !inMission && !cinematicTextFlag) {
if (e.keyCode == KEY.T && e.altKey) {
e.preventDefault();
@@ -261,14 +253,13 @@ const Engine = {
loadScriptEditorContent: function(filename = "", code = "") {
Engine.hideAllContent();
Engine.Display.scriptEditorContent.style.display = "block";
try {
getCurrentEditor().openScript(filename, code);
} catch(e) {
exceptionAlert(e);
}
updateScriptEditorContent();
routing.navigateTo(Page.ScriptEditor);
ReactDOM.render(
<ScriptEditorRoot filename={filename} code={code} player={Player} engine={this} />,
Engine.Display.scriptEditorContent,
);
MainMenuLinks.ScriptEditor.classList.add("active");
},
@@ -505,6 +496,7 @@ const Engine = {
Engine.Display.terminalContent.style.display = "none";
Engine.Display.characterContent.style.display = "none";
Engine.Display.scriptEditorContent.style.display = "none";
ReactDOM.unmountComponentAtNode(Engine.Display.scriptEditorContent);
Engine.Display.activeScriptsContent.style.display = "none";
ReactDOM.unmountComponentAtNode(Engine.Display.activeScriptsContent);
@@ -784,10 +776,6 @@ const Engine = {
updateSleevesPage();
}
if (logBoxOpened) {
logBoxUpdateText();
}
Engine.Counters.updateDisplays = 3;
}
@@ -798,13 +786,6 @@ const Engine = {
Engine.Counters.updateDisplaysMed = 9;
}
if (Engine.Counters.updateDisplaysLong <= 0) {
if (routing.isOn(Page.ScriptEditor)) {
updateScriptEditorContent();
}
Engine.Counters.updateDisplaysLong = 15;
}
if (Engine.Counters.createProgramNotifications <= 0) {
var num = getNumAvailableCreateProgram();
var elem = document.getElementById("create-program-notification");
@@ -1208,7 +1189,6 @@ const Engine = {
}
// Initialize labels on game settings
setSettingsLabels();
scriptEditorInit();
Terminal.resetTerminalInput();
},
@@ -1546,6 +1526,12 @@ const Engine = {
gameOptionsBoxClose();
return false;
});
// DEBUG File diagnostic
document.getElementById("debug-files").addEventListener("click", function() {
createPopup("debug-files-diagnostic-popup", FileDiagnosticPopup, {});
return false;
});
},
start: function() {
@@ -1594,4 +1580,4 @@ window.onload = function() {
}
};
export {Engine, indexedDb};
export {Engine};

View File

@@ -125,11 +125,8 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
<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 -->
@@ -175,6 +172,14 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
<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 -->
@@ -290,16 +295,6 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
<!-- 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">
@@ -599,10 +594,12 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
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">
@@ -611,6 +608,14 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
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>

View File

@@ -6,6 +6,7 @@ import * as React from "react";
import { WorkerScriptAccordion } from "./WorkerScriptAccordion";
import { Accordion } from "../React/Accordion";
import { ServerAccordionContent } from "./ServerAccordionContent";
import { BaseServer } from "../../Server/BaseServer";
import { WorkerScript } from "../../Netscript/WorkerScript";
@@ -42,7 +43,7 @@ export function ServerAccordion(props: IProps): React.ReactElement {
<pre>{headerTxt}</pre>
}
panelContent={
<ul>{scripts}</ul>
<ServerAccordionContent workerScripts={props.workerScripts} />
}
/>
)

View File

@@ -0,0 +1,83 @@
import React, { useState } from "react";
import { WorkerScript } from "../../Netscript/WorkerScript";
import { WorkerScriptAccordion } from "./WorkerScriptAccordion";
import { AccordionButton } from "../React/AccordionButton";
const pageSize = 20;
interface IProps {
workerScripts: WorkerScript[];
}
export function ServerAccordionContent(props: IProps): React.ReactElement {
if(props.workerScripts.length > pageSize) {
return <ServerAccordionContentPaginated workerScripts={props.workerScripts} />
}
const scripts = props.workerScripts.map((ws) => {
return (
<WorkerScriptAccordion key={`${ws.name}_${ws.args}`} workerScript={ws} />
)
});
return (<ul>{scripts}</ul>);
}
export function ServerAccordionContentPaginated(props: IProps): React.ReactElement {
const [page, setPage] = useState(0);
const scripts: React.ReactElement[] = [];
const maxPage = Math.ceil(props.workerScripts.length/pageSize);
const maxScript = Math.min((page+1)*pageSize, props.workerScripts.length);
for(let i = page*pageSize; i < maxScript; i++) {
const ws = props.workerScripts[i];
scripts.push(<WorkerScriptAccordion key={`${ws.name}_${ws.args}`} workerScript={ws} />)
}
function capPage(page: number): number {
if(page < 0) {
page = 0;
}
if(maxPage-1 < page) {
page = maxPage-1;
}
return page;
}
// in case we're on an invalid page number because scripts were killed.
const capped = capPage(page);
if(capped !== page)
setPage(capped);
function changePage(n: number): void {
setPage(newPage => {
newPage += n;
newPage = Math.round(newPage);
return capPage(newPage);
})
}
return (<><ul>{scripts}</ul>
<AccordionButton
onClick={() => changePage(-1e99)}
text="<<"
/>
<AccordionButton
onClick={() => changePage(-1)}
text="<"
/>
<span className="text">{page+1} / {maxPage}</span>
<AccordionButton
onClick={() => changePage(1)}
text=">"
/>
<AccordionButton
onClick={() => changePage(1e99)}
text=">>"
/>
</>);
}

View File

@@ -73,9 +73,11 @@ class AccordionPanel extends React.Component<IPanelProps, any> {
className = this.props.panelClass;
}
if(!this.props.opened) return (<></>);
return (
<div className={className} style={{display: this.props.opened ? "block" : "none"}}>
<div className={className} style={{display: "block"}}>
{this.props.panelContent}
</div>
)

View File

@@ -31,7 +31,7 @@ export class AutoupdatingParagraph extends React.Component<IProps, IState> {
componentDidMount(): void {
const time = this.props.intervalTime ? this.props.intervalTime : 1000;
this.interval = setInterval(() => this.tick(), time);
this.interval = window.setInterval(() => this.tick(), time);
}
componentWillUnmount(): void {

View File

@@ -38,7 +38,7 @@ export class AutoupdatingStdButton extends React.Component<IProps, IState> {
componentDidMount(): void {
const time = this.props.intervalTime ? this.props.intervalTime : 1000;
this.interval = setInterval(() => this.tick(), time);
this.interval = window.setInterval(() => this.tick(), time);
}
componentWillUnmount(): void {

View File

@@ -5,12 +5,6 @@ import { RunningScript } from "../../src/Script/RunningScript";
import { Script } from "../../src/Script/Script";
import { SourceFileFlags } from "../../src/SourceFile/SourceFileFlags";
import { expect } from "chai";
console.log("Beginning Netscript Dynamic RAM Calculation/Generation Tests");
console.log("Beginning Netscript Static RAM Calculation/Generation Tests");
const ScriptBaseCost = RamCostConstants.ScriptBaseRamCost;
describe("Netscript Dynamic RAM Calculation/Generation Tests", function() {
@@ -27,12 +21,13 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function() {
// Tests numeric equality, allowing for floating point imprecision
function testEquality(val, expected) {
expect(val).to.be.within(expected - 100 * Number.EPSILON, expected + 100 * Number.EPSILON);
expect(val).toBeGreaterThanOrEqual(expected - 100 * Number.EPSILON);
expect(val).toBeLessThanOrEqual(expected + 100 * Number.EPSILON);
}
// Runs a Netscript function and properly catches it if it returns promise
function runPotentiallyAsyncFunction(fn) {
let res = fn();
const res = fn();
if (res instanceof Promise) {
res.catch(() => undefined);
}
@@ -47,9 +42,9 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function() {
* including the namespace(s). e.g. ["gang", "getMemberNames"]
*/
async function testNonzeroDynamicRamCost(fnDesc) {
if (!Array.isArray(fnDesc)) { expect.fail("Non-array passed to testNonzeroDynamicRamCost()"); }
if (!Array.isArray(fnDesc)) { throw new Error("Non-array passed to testNonzeroDynamicRamCost()"); }
const expected = getRamCost(...fnDesc);
expect(expected).to.be.above(0);
expect(expected).toBeGreaterThan(0);
const code = `${fnDesc.join(".")}();`
@@ -72,7 +67,7 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function() {
let curr = scope[fnDesc[0]];
for (let i = 1; i < fnDesc.length; ++i) {
if (curr == null) {
expect.fail(`Invalid function specified: [${fnDesc}]`);
throw new Error(`Invalid function specified: [${fnDesc}]`);
}
if (typeof curr === "function") {
@@ -92,13 +87,13 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function() {
runPotentiallyAsyncFunction(curr);
} catch(e) {}
} else {
expect.fail(`Invalid function specified: [${fndesc}]`);
throw new Error(`Invalid function specified: [${fnDesc}]`);
}
const fnName = fnDesc[fnDesc.length - 1];
testEquality(workerScript.dynamicRamUsage - ScriptBaseCost, expected);
testEquality(workerScript.dynamicRamUsage, runningScript.ramUsage);
expect(workerScript.dynamicLoadedFns).to.have.property(fnName);
expect(workerScript.dynamicLoadedFns).toHaveProperty(fnName);
}
/**
@@ -110,9 +105,9 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function() {
* including the namespace(s). e.g. ["gang", "getMemberNames"]
*/
async function testZeroDynamicRamCost(fnDesc) {
if (!Array.isArray(fnDesc)) { expect.fail("Non-array passed to testZeroDynamicRamCost()"); }
if (!Array.isArray(fnDesc)) { throw new Error("Non-array passed to testZeroDynamicRamCost()"); }
const expected = getRamCost(...fnDesc);
expect(expected).to.equal(0);
expect(expected).toEqual(0);
const code = `${fnDesc.join(".")}();`
@@ -135,7 +130,7 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function() {
let curr = scope[fnDesc[0]];
for (let i = 1; i < fnDesc.length; ++i) {
if (curr == null) {
expect.fail(`Invalid function specified: [${fnDesc}]`);
throw new Error(`Invalid function specified: [${fnDesc}]`);
}
if (typeof curr === "function") {
@@ -155,20 +150,20 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function() {
runPotentiallyAsyncFunction(curr);
} catch(e) {}
} else {
expect.fail(`Invalid function specified: [${fndesc}]`);
throw new Error(`Invalid function specified: [${fndesc}]`);
}
testEquality(workerScript.dynamicRamUsage, ScriptBaseCost);
testEquality(workerScript.dynamicRamUsage, runningScript.ramUsage);
}
before(function() {
beforeEach(function() {
for (let i = 0; i < SourceFileFlags.length; ++i) {
SourceFileFlags[i] = 3;
}
});
describe("Basic Functions", async function() {
describe("Basic Functions", function() {
it("hack()", async function() {
const f = ["hack"];
await testNonzeroDynamicRamCost(f);
@@ -570,14 +565,14 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function() {
});
});
describe("Advanced Functions", async function() {
describe("Advanced Functions", function() {
it("getBitNodeMultipliers()", async function() {
const f = ["getBitNodeMultipliers"];
await testNonzeroDynamicRamCost(f);
});
});
describe("TIX API", async function() {
describe("TIX API", function() {
it("getStockSymbols()", async function() {
const f = ["getStockSymbols"];
await testNonzeroDynamicRamCost(f);
@@ -664,7 +659,7 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function() {
});
});
describe("Singularity Functions", async function() {
describe("Singularity Functions", function() {
it("universityCourse()", async function() {
const f = ["universityCourse"];
await testNonzeroDynamicRamCost(f);
@@ -831,7 +826,7 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function() {
});
});
describe("Bladeburner API", async function() {
describe("Bladeburner API", function() {
it("getContractNames()", async function() {
const f = ["bladeburner", "getContractNames"];
await testNonzeroDynamicRamCost(f);
@@ -1003,7 +998,7 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function() {
});
});
describe("Gang API", async function() {
describe("Gang API", function() {
it("getMemberNames()", async function() {
const f = ["gang", "getMemberNames"];
await testNonzeroDynamicRamCost(f);
@@ -1085,7 +1080,7 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function() {
});
});
describe("Coding Contract API", async function() {
describe("Coding Contract API", function() {
it("attempt()", async function() {
const f = ["codingcontract", "attempt"];
await testNonzeroDynamicRamCost(f);
@@ -1112,7 +1107,7 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function() {
});
});
describe("Sleeve API", async function() {
describe("Sleeve API", function() {
it("getNumSleeves()", async function() {
const f = ["sleeve", "getNumSleeves"];
await testNonzeroDynamicRamCost(f);

View File

@@ -1,8 +1,5 @@
import { getRamCost, RamCostConstants } from "../../src/Netscript/RamCostGenerator";
import { calculateRamUsage } from "../../src/Script/RamCalculations"
import { expect } from "chai";
console.log("Beginning Netscript Static RAM Calculation/Generation Tests");
const ScriptBaseCost = RamCostConstants.ScriptBaseRamCost;
const HacknetNamespaceCost = RamCostConstants.ScriptHacknetNodesRamCost;
@@ -10,7 +7,8 @@ const HacknetNamespaceCost = RamCostConstants.ScriptHacknetNodesRamCost;
describe("Netscript Static RAM Calculation/Generation Tests", function() {
// Tests numeric equality, allowing for floating point imprecision
function testEquality(val, expected) {
expect(val).to.be.within(expected - 100 * Number.EPSILON, expected + 100 * Number.EPSILON);
expect(val).toBeGreaterThanOrEqual(expected - 100 * Number.EPSILON);
expect(val).toBeLessThanOrEqual(expected + 100 * Number.EPSILON);
}
/**
@@ -24,7 +22,7 @@ describe("Netscript Static RAM Calculation/Generation Tests", function() {
async function expectNonZeroRamCost(fnDesc) {
if (!Array.isArray(fnDesc)) { expect.fail("Non-array passed to expectNonZeroRamCost()"); }
const expected = getRamCost(...fnDesc);
expect(expected).to.be.above(0);
expect(expected).toBeGreaterThan(0);
const code = fnDesc.join(".") + "(); ";
@@ -33,7 +31,7 @@ describe("Netscript Static RAM Calculation/Generation Tests", function() {
const multipleCallsCode = code.repeat(3);
const multipleCallsCalculated = await calculateRamUsage(multipleCallsCode, []);
expect(multipleCallsCalculated).to.equal(calculated);
expect(multipleCallsCalculated).toEqual(calculated);
}
/**
@@ -47,7 +45,7 @@ describe("Netscript Static RAM Calculation/Generation Tests", function() {
async function expectZeroRamCost(fnDesc) {
if (!Array.isArray(fnDesc)) { expect.fail("Non-array passed to expectZeroRamCost()"); }
const expected = getRamCost(...fnDesc);
expect(expected).to.equal(0);
expect(expected).toEqual(0);
const code = fnDesc.join(".") + "(); ";
@@ -55,10 +53,10 @@ describe("Netscript Static RAM Calculation/Generation Tests", function() {
testEquality(calculated, ScriptBaseCost);
const multipleCallsCalculated = await calculateRamUsage(code, []);
expect(multipleCallsCalculated).to.equal(ScriptBaseCost);
expect(multipleCallsCalculated).toEqual(ScriptBaseCost);
}
describe("Basic Functions", async function() {
describe("Basic Functions", function() {
it("hack()", async function() {
const f = ["hack"];
await expectNonZeroRamCost(f);
@@ -460,14 +458,14 @@ describe("Netscript Static RAM Calculation/Generation Tests", function() {
});
});
describe("Advanced Functions", async function() {
describe("Advanced Functions", function() {
it("getBitNodeMultipliers()", async function() {
const f = ["getBitNodeMultipliers"];
await expectNonZeroRamCost(f);
});
});
describe("Hacknet Node API", async function() {
describe("Hacknet Node API", function() {
// The Hacknet Node API RAM cost is a bit different because
// it's just a one-time cost to access the 'hacknet' namespace.
// Otherwise, all functions cost 0 RAM
@@ -490,7 +488,7 @@ describe("Netscript Static RAM Calculation/Generation Tests", function() {
]
it("should have zero RAM cost for all functions", function() {
for (const fn of apiFunctions) {
expect(getRamCost("hacknet", fn)).to.equal(0);
expect(getRamCost("hacknet", fn)).toEqual(0);
}
});
@@ -505,7 +503,7 @@ describe("Netscript Static RAM Calculation/Generation Tests", function() {
});
});
describe("TIX API", async function() {
describe("TIX API", function() {
it("getStockSymbols()", async function() {
const f = ["getStockSymbols"];
await expectNonZeroRamCost(f);
@@ -602,7 +600,7 @@ describe("Netscript Static RAM Calculation/Generation Tests", function() {
});
});
describe("Singularity Functions", async function() {
describe("Singularity Functions", function() {
it("universityCourse()", async function() {
const f = ["universityCourse"];
await expectNonZeroRamCost(f);
@@ -769,7 +767,7 @@ describe("Netscript Static RAM Calculation/Generation Tests", function() {
});
});
describe("Bladeburner API", async function() {
describe("Bladeburner API", function() {
it("getContractNames()", async function() {
const f = ["bladeburner", "getContractNames"];
await expectNonZeroRamCost(f);
@@ -941,7 +939,7 @@ describe("Netscript Static RAM Calculation/Generation Tests", function() {
});
});
describe("Gang API", async function() {
describe("Gang API", function() {
it("getMemberNames()", async function() {
const f = ["gang", "getMemberNames"];
await expectNonZeroRamCost(f);
@@ -1023,7 +1021,7 @@ describe("Netscript Static RAM Calculation/Generation Tests", function() {
});
});
describe("Coding Contract API", async function() {
describe("Coding Contract API", function() {
it("attempt()", async function() {
const f = ["codingcontract", "attempt"];
await expectNonZeroRamCost(f);
@@ -1050,7 +1048,7 @@ describe("Netscript Static RAM Calculation/Generation Tests", function() {
});
});
describe("Sleeve API", async function() {
describe("Sleeve API", function() {
it("getNumSleeves()", async function() {
const f = ["sleeve", "getNumSleeves"];
await expectNonZeroRamCost(f);

View File

@@ -1,5 +1,10 @@
# Unit Tests
This directory contains unit tests for Bitburner.
Unit tests use Mocha/Chai and are run using mochapack (a mocha-webpack fork).
Run the test command with `npm run test`
Unit tests use jest.
## Running
Run tests with: `npm run test`
To watch for changes: `npm run test:watch`

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,33 @@
import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
describe("StringHelperFunctions Tests", function () {
it("transforms strings", () => {
expect(convertTimeMsToTimeElapsedString(1000)).toEqual("1 seconds");
expect(convertTimeMsToTimeElapsedString(5 * 60 * 1000 + 34 * 1000)).toEqual(
"5 minutes 34 seconds",
);
expect(
convertTimeMsToTimeElapsedString(
2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000,
),
).toEqual("2 days 5 minutes 34 seconds");
expect(
convertTimeMsToTimeElapsedString(
2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000,
true,
),
).toEqual("2 days 5 minutes 34.000 seconds");
expect(
convertTimeMsToTimeElapsedString(
2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000 + 123,
true,
),
).toEqual("2 days 5 minutes 34.123 seconds");
expect(
convertTimeMsToTimeElapsedString(
2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000 + 123.888,
true,
),
).toEqual("2 days 5 minutes 34.123 seconds");
})
});

View File

@@ -1,11 +0,0 @@
import { expect } from "chai";
import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
describe("StringHelperFunctions Tests", function() {
expect(convertTimeMsToTimeElapsedString(1000)).to.equal("1 seconds");
expect(convertTimeMsToTimeElapsedString(5*60*1000+34*1000)).to.equal("5 minutes 34 seconds");
expect(convertTimeMsToTimeElapsedString(2*60*60*24*1000+5*60*1000+34*1000)).to.equal("2 days 5 minutes 34 seconds");
expect(convertTimeMsToTimeElapsedString(2*60*60*24*1000+5*60*1000+34*1000, true)).to.equal("2 days 5 minutes 34.000 seconds");
expect(convertTimeMsToTimeElapsedString(2*60*60*24*1000+5*60*1000+34*1000+123, true)).to.equal("2 days 5 minutes 34.123 seconds");
expect(convertTimeMsToTimeElapsedString(2*60*60*24*1000+5*60*1000+34*1000+123.888, true)).to.equal("2 days 5 minutes 34.123 seconds");
})

View File

@@ -0,0 +1,295 @@
import * as dirHelpers from "../../src/Terminal/DirectoryHelpers";
describe("Terminal Directory Tests", function() {
describe("removeLeadingSlash()", function() {
const removeLeadingSlash = dirHelpers.removeLeadingSlash;
it("should remove first slash in a string", function() {
expect(removeLeadingSlash("/")).toEqual("");
expect(removeLeadingSlash("/foo.txt")).toEqual("foo.txt");
expect(removeLeadingSlash("/foo/file.txt")).toEqual("foo/file.txt");
});
it("should only remove one slash", function() {
expect(removeLeadingSlash("///")).toEqual("//");
expect(removeLeadingSlash("//foo")).toEqual("/foo");
});
it("should do nothing for a string that doesn't start with a slash", function() {
expect(removeLeadingSlash("foo.txt")).toEqual("foo.txt");
expect(removeLeadingSlash("foo/test.txt")).toEqual("foo/test.txt");
});
it("should not fail on an empty string", function() {
expect(removeLeadingSlash.bind(null, "")).not.toThrow();
expect(removeLeadingSlash("")).toEqual("");
});
});
describe("removeTrailingSlash()", function() {
const removeTrailingSlash = dirHelpers.removeTrailingSlash;
it("should remove last slash in a string", function() {
expect(removeTrailingSlash("/")).toEqual("");
expect(removeTrailingSlash("foo.txt/")).toEqual("foo.txt");
expect(removeTrailingSlash("foo/file.txt/")).toEqual("foo/file.txt");
});
it("should only remove one slash", function() {
expect(removeTrailingSlash("///")).toEqual("//");
expect(removeTrailingSlash("foo//")).toEqual("foo/");
});
it("should do nothing for a string that doesn't end with a slash", function() {
expect(removeTrailingSlash("foo.txt")).toEqual("foo.txt");
expect(removeTrailingSlash("foo/test.txt")).toEqual("foo/test.txt");
});
it("should not fail on an empty string", function() {
expect(removeTrailingSlash.bind(null, "")).not.toThrow();
expect(removeTrailingSlash("")).toEqual("");
});
});
describe("isValidFilename()", function() {
const isValidFilename = dirHelpers.isValidFilename;
it("should return true for valid filenames", function() {
expect(isValidFilename("test.txt")).toEqual(true);
expect(isValidFilename("123.script")).toEqual(true);
expect(isValidFilename("foo123.b")).toEqual(true);
expect(isValidFilename("my_script.script")).toEqual(true);
expect(isValidFilename("my-script.script")).toEqual(true);
expect(isValidFilename("_foo.lit")).toEqual(true);
expect(isValidFilename("mult.periods.script")).toEqual(true);
expect(isValidFilename("mult.per-iods.again.script")).toEqual(true);
expect(isValidFilename("BruteSSH.exe-50%-INC")).toEqual(true);
expect(isValidFilename("DeepscanV1.exe-1.01%-INC")).toEqual(true);
expect(isValidFilename("DeepscanV2.exe-1.00%-INC")).toEqual(true);
expect(isValidFilename("AutoLink.exe-1.%-INC")).toEqual(true);
});
it("should return false for invalid filenames", function() {
expect(isValidFilename("foo")).toEqual(false);
expect(isValidFilename("my script.script")).toEqual(false);
expect(isValidFilename("a^.txt")).toEqual(false);
expect(isValidFilename("b#.lit")).toEqual(false);
expect(isValidFilename("lib().js")).toEqual(false);
expect(isValidFilename("foo.script_")).toEqual(false);
expect(isValidFilename("foo._script")).toEqual(false);
expect(isValidFilename("foo.hyphened-ext")).toEqual(false);
expect(isValidFilename("")).toEqual(false);
expect(isValidFilename("AutoLink-1.%-INC.exe")).toEqual(false);
expect(isValidFilename("AutoLink.exe-1.%-INC.exe")).toEqual(false);
expect(isValidFilename("foo%.exe")).toEqual(false);
expect(isValidFilename("-1.00%-INC")).toEqual(false);
});
});
describe("isValidDirectoryName()", function() {
const isValidDirectoryName = dirHelpers.isValidDirectoryName;
it("should return true for valid directory names", function() {
expect(isValidDirectoryName("a")).toEqual(true);
expect(isValidDirectoryName("foo")).toEqual(true);
expect(isValidDirectoryName("foo-dir")).toEqual(true);
expect(isValidDirectoryName("foo_dir")).toEqual(true);
expect(isValidDirectoryName(".a")).toEqual(true);
expect(isValidDirectoryName("1")).toEqual(true);
expect(isValidDirectoryName("a1")).toEqual(true);
expect(isValidDirectoryName(".a1")).toEqual(true);
expect(isValidDirectoryName("._foo")).toEqual(true);
expect(isValidDirectoryName("_foo")).toEqual(true);
});
it("should return false for invalid directory names", function() {
expect(isValidDirectoryName("")).toEqual(false);
expect(isValidDirectoryName("foo.dir")).toEqual(false);
expect(isValidDirectoryName("1.")).toEqual(false);
expect(isValidDirectoryName("foo.")).toEqual(false);
expect(isValidDirectoryName("dir#")).toEqual(false);
expect(isValidDirectoryName("dir!")).toEqual(false);
expect(isValidDirectoryName("dir*")).toEqual(false);
expect(isValidDirectoryName(".")).toEqual(false);
});
});
describe("isValidDirectoryPath()", function() {
const isValidDirectoryPath = dirHelpers.isValidDirectoryPath;
it("should return false for empty strings", function() {
expect(isValidDirectoryPath("")).toEqual(false);
});
it("should return true only for the forward slash if the string has length 1", function() {
expect(isValidDirectoryPath("/")).toEqual(true);
expect(isValidDirectoryPath(" ")).toEqual(false);
expect(isValidDirectoryPath(".")).toEqual(false);
expect(isValidDirectoryPath("a")).toEqual(false);
});
it("should return true for valid directory paths", function() {
expect(isValidDirectoryPath("/a")).toEqual(true);
expect(isValidDirectoryPath("/dir/a")).toEqual(true);
expect(isValidDirectoryPath("/dir/foo")).toEqual(true);
expect(isValidDirectoryPath("/.dir/foo-dir")).toEqual(true);
expect(isValidDirectoryPath("/.dir/foo_dir")).toEqual(true);
expect(isValidDirectoryPath("/.dir/.a")).toEqual(true);
expect(isValidDirectoryPath("/dir1/1")).toEqual(true);
expect(isValidDirectoryPath("/dir1/a1")).toEqual(true);
expect(isValidDirectoryPath("/dir1/.a1")).toEqual(true);
expect(isValidDirectoryPath("/dir_/._foo")).toEqual(true);
expect(isValidDirectoryPath("/dir-/_foo")).toEqual(true);
});
it("should return false if the path does not have a leading slash", function() {
expect(isValidDirectoryPath("a")).toEqual(false);
expect(isValidDirectoryPath("dir/a")).toEqual(false);
expect(isValidDirectoryPath("dir/foo")).toEqual(false);
expect(isValidDirectoryPath(".dir/foo-dir")).toEqual(false);
expect(isValidDirectoryPath(".dir/foo_dir")).toEqual(false);
expect(isValidDirectoryPath(".dir/.a")).toEqual(false);
expect(isValidDirectoryPath("dir1/1")).toEqual(false);
expect(isValidDirectoryPath("dir1/a1")).toEqual(false);
expect(isValidDirectoryPath("dir1/.a1")).toEqual(false);
expect(isValidDirectoryPath("dir_/._foo")).toEqual(false);
expect(isValidDirectoryPath("dir-/_foo")).toEqual(false);
});
it("should accept dot notation", function() {
expect(isValidDirectoryPath("/dir/./a")).toEqual(true);
expect(isValidDirectoryPath("/dir/../foo")).toEqual(true);
expect(isValidDirectoryPath("/.dir/./foo-dir")).toEqual(true);
expect(isValidDirectoryPath("/.dir/../foo_dir")).toEqual(true);
expect(isValidDirectoryPath("/.dir/./.a")).toEqual(true);
expect(isValidDirectoryPath("/dir1/1/.")).toEqual(true);
expect(isValidDirectoryPath("/dir1/a1/..")).toEqual(true);
expect(isValidDirectoryPath("/dir1/.a1/..")).toEqual(true);
expect(isValidDirectoryPath("/dir_/._foo/.")).toEqual(true);
expect(isValidDirectoryPath("/./dir-/_foo")).toEqual(true);
expect(isValidDirectoryPath("/../dir-/_foo")).toEqual(true);
});
});
describe("isValidFilePath()", function() {
const isValidFilePath = dirHelpers.isValidFilePath;
it("should return false for strings that are too short", function() {
expect(isValidFilePath("/a")).toEqual(false);
expect(isValidFilePath("a.")).toEqual(false);
expect(isValidFilePath(".a")).toEqual(false);
expect(isValidFilePath("/.")).toEqual(false);
});
it("should return true for arguments that are just filenames", function() {
expect(isValidFilePath("test.txt")).toEqual(true);
expect(isValidFilePath("123.script")).toEqual(true);
expect(isValidFilePath("foo123.b")).toEqual(true);
expect(isValidFilePath("my_script.script")).toEqual(true);
expect(isValidFilePath("my-script.script")).toEqual(true);
expect(isValidFilePath("_foo.lit")).toEqual(true);
expect(isValidFilePath("mult.periods.script")).toEqual(true);
expect(isValidFilePath("mult.per-iods.again.script")).toEqual(true);
});
it("should return true for valid filepaths", function() {
expect(isValidFilePath("/foo/test.txt")).toEqual(true);
expect(isValidFilePath("/../123.script")).toEqual(true);
expect(isValidFilePath("/./foo123.b")).toEqual(true);
expect(isValidFilePath("/dir/my_script.script")).toEqual(true);
expect(isValidFilePath("/dir1/dir2/dir3/my-script.script")).toEqual(true);
expect(isValidFilePath("/dir1/dir2/././../_foo.lit")).toEqual(true);
expect(isValidFilePath("/.dir1/./../.dir2/mult.periods.script")).toEqual(true);
expect(isValidFilePath("/_dir/../dir2/mult.per-iods.again.script")).toEqual(true);
});
it("should return false for strings that end with a slash", function() {
expect(isValidFilePath("/foo/")).toEqual(false);
expect(isValidFilePath("foo.txt/")).toEqual(false);
expect(isValidFilePath("/")).toEqual(false);
expect(isValidFilePath("/_dir/")).toEqual(false);
});
it("should return false for invalid arguments", function() {
expect(isValidFilePath(null)).toEqual(false);
expect(isValidFilePath()).toEqual(false);
expect(isValidFilePath(5)).toEqual(false);
expect(isValidFilePath({})).toEqual(false);
})
});
describe("getFirstParentDirectory()", function() {
const getFirstParentDirectory = dirHelpers.getFirstParentDirectory;
it("should return the first parent directory in a filepath", function() {
expect(getFirstParentDirectory("/dir1/foo.txt")).toEqual("dir1/");
expect(getFirstParentDirectory("/dir1/dir2/dir3/dir4/foo.txt")).toEqual("dir1/");
expect(getFirstParentDirectory("/_dir1/dir2/foo.js")).toEqual("_dir1/");
});
it("should return '/' if there is no first parent directory", function() {
expect(getFirstParentDirectory("")).toEqual("/");
expect(getFirstParentDirectory(" ")).toEqual("/");
expect(getFirstParentDirectory("/")).toEqual("/");
expect(getFirstParentDirectory("//")).toEqual("/");
expect(getFirstParentDirectory("foo.script")).toEqual("/");
expect(getFirstParentDirectory("/foo.txt")).toEqual("/");
});
});
describe("getAllParentDirectories()", function() {
const getAllParentDirectories = dirHelpers.getAllParentDirectories;
it("should return all parent directories in a filepath", function() {
expect(getAllParentDirectories("/")).toEqual("/");
expect(getAllParentDirectories("/home/var/foo.txt")).toEqual("/home/var/");
expect(getAllParentDirectories("/home/var/")).toEqual("/home/var/");
expect(getAllParentDirectories("/home/var/test/")).toEqual("/home/var/test/");
});
it("should return an empty string if there are no parent directories", function() {
expect(getAllParentDirectories("foo.txt")).toEqual("");
});
});
describe("isInRootDirectory()", function() {
const isInRootDirectory = dirHelpers.isInRootDirectory;
it("should return true for filepaths that refer to a file in the root directory", function() {
expect(isInRootDirectory("a.b")).toEqual(true);
expect(isInRootDirectory("foo.txt")).toEqual(true);
expect(isInRootDirectory("/foo.txt")).toEqual(true);
});
it("should return false for filepaths that refer to a file that's NOT in the root directory", function() {
expect(isInRootDirectory("/dir/foo.txt")).toEqual(false);
expect(isInRootDirectory("dir/foo.txt")).toEqual(false);
expect(isInRootDirectory("/./foo.js")).toEqual(false);
expect(isInRootDirectory("../foo.js")).toEqual(false);
expect(isInRootDirectory("/dir1/dir2/dir3/foo.txt")).toEqual(false);
});
it("should return false for invalid inputs (inputs that aren't filepaths)", function() {
expect(isInRootDirectory(null)).toEqual(false);
expect(isInRootDirectory(undefined)).toEqual(false);
expect(isInRootDirectory("")).toEqual(false);
expect(isInRootDirectory(" ")).toEqual(false);
expect(isInRootDirectory("a")).toEqual(false);
expect(isInRootDirectory("/dir")).toEqual(false);
expect(isInRootDirectory("/dir/")).toEqual(false);
expect(isInRootDirectory("/dir/foo")).toEqual(false);
});
});
describe("evaluateDirectoryPath()", function() {
//const evaluateDirectoryPath = dirHelpers.evaluateDirectoryPath;
// TODO
});
describe("evaluateFilePath()", function() {
//const evaluateFilePath = dirHelpers.evaluateFilePath;
// TODO
})
});

View File

@@ -1,299 +0,0 @@
import * as dirHelpers from "../../src/Terminal/DirectoryHelpers";
import { expect } from "chai";
console.log("Beginning Terminal Directory Tests");
describe("Terminal Directory Tests", function() {
describe("removeLeadingSlash()", function() {
const removeLeadingSlash = dirHelpers.removeLeadingSlash;
it("should remove first slash in a string", function() {
expect(removeLeadingSlash("/")).to.equal("");
expect(removeLeadingSlash("/foo.txt")).to.equal("foo.txt");
expect(removeLeadingSlash("/foo/file.txt")).to.equal("foo/file.txt");
});
it("should only remove one slash", function() {
expect(removeLeadingSlash("///")).to.equal("//");
expect(removeLeadingSlash("//foo")).to.equal("/foo");
});
it("should do nothing for a string that doesn't start with a slash", function() {
expect(removeLeadingSlash("foo.txt")).to.equal("foo.txt");
expect(removeLeadingSlash("foo/test.txt")).to.equal("foo/test.txt");
});
it("should not fail on an empty string", function() {
expect(removeLeadingSlash.bind(null, "")).to.not.throw();
expect(removeLeadingSlash("")).to.equal("");
});
});
describe("removeTrailingSlash()", function() {
const removeTrailingSlash = dirHelpers.removeTrailingSlash;
it("should remove last slash in a string", function() {
expect(removeTrailingSlash("/")).to.equal("");
expect(removeTrailingSlash("foo.txt/")).to.equal("foo.txt");
expect(removeTrailingSlash("foo/file.txt/")).to.equal("foo/file.txt");
});
it("should only remove one slash", function() {
expect(removeTrailingSlash("///")).to.equal("//");
expect(removeTrailingSlash("foo//")).to.equal("foo/");
});
it("should do nothing for a string that doesn't end with a slash", function() {
expect(removeTrailingSlash("foo.txt")).to.equal("foo.txt");
expect(removeTrailingSlash("foo/test.txt")).to.equal("foo/test.txt");
});
it("should not fail on an empty string", function() {
expect(removeTrailingSlash.bind(null, "")).to.not.throw();
expect(removeTrailingSlash("")).to.equal("");
});
});
describe("isValidFilename()", function() {
const isValidFilename = dirHelpers.isValidFilename;
it("should return true for valid filenames", function() {
expect(isValidFilename("test.txt")).to.equal(true);
expect(isValidFilename("123.script")).to.equal(true);
expect(isValidFilename("foo123.b")).to.equal(true);
expect(isValidFilename("my_script.script")).to.equal(true);
expect(isValidFilename("my-script.script")).to.equal(true);
expect(isValidFilename("_foo.lit")).to.equal(true);
expect(isValidFilename("mult.periods.script")).to.equal(true);
expect(isValidFilename("mult.per-iods.again.script")).to.equal(true);
expect(isValidFilename("BruteSSH.exe-50%-INC")).to.equal(true);
expect(isValidFilename("DeepscanV1.exe-1.01%-INC")).to.equal(true);
expect(isValidFilename("DeepscanV2.exe-1.00%-INC")).to.equal(true);
expect(isValidFilename("AutoLink.exe-1.%-INC")).to.equal(true);
});
it("should return false for invalid filenames", function() {
expect(isValidFilename("foo")).to.equal(false);
expect(isValidFilename("my script.script")).to.equal(false);
expect(isValidFilename("a^.txt")).to.equal(false);
expect(isValidFilename("b#.lit")).to.equal(false);
expect(isValidFilename("lib().js")).to.equal(false);
expect(isValidFilename("foo.script_")).to.equal(false);
expect(isValidFilename("foo._script")).to.equal(false);
expect(isValidFilename("foo.hyphened-ext")).to.equal(false);
expect(isValidFilename("")).to.equal(false);
expect(isValidFilename("AutoLink-1.%-INC.exe")).to.equal(false);
expect(isValidFilename("AutoLink.exe-1.%-INC.exe")).to.equal(false);
expect(isValidFilename("foo%.exe")).to.equal(false);
expect(isValidFilename("-1.00%-INC")).to.equal(false);
});
});
describe("isValidDirectoryName()", function() {
const isValidDirectoryName = dirHelpers.isValidDirectoryName;
it("should return true for valid directory names", function() {
expect(isValidDirectoryName("a")).to.equal(true);
expect(isValidDirectoryName("foo")).to.equal(true);
expect(isValidDirectoryName("foo-dir")).to.equal(true);
expect(isValidDirectoryName("foo_dir")).to.equal(true);
expect(isValidDirectoryName(".a")).to.equal(true);
expect(isValidDirectoryName("1")).to.equal(true);
expect(isValidDirectoryName("a1")).to.equal(true);
expect(isValidDirectoryName(".a1")).to.equal(true);
expect(isValidDirectoryName("._foo")).to.equal(true);
expect(isValidDirectoryName("_foo")).to.equal(true);
});
it("should return false for invalid directory names", function() {
expect(isValidDirectoryName("")).to.equal(false);
expect(isValidDirectoryName("foo.dir")).to.equal(false);
expect(isValidDirectoryName("1.")).to.equal(false);
expect(isValidDirectoryName("foo.")).to.equal(false);
expect(isValidDirectoryName("dir#")).to.equal(false);
expect(isValidDirectoryName("dir!")).to.equal(false);
expect(isValidDirectoryName("dir*")).to.equal(false);
expect(isValidDirectoryName(".")).to.equal(false);
});
});
describe("isValidDirectoryPath()", function() {
const isValidDirectoryPath = dirHelpers.isValidDirectoryPath;
it("should return false for empty strings", function() {
expect(isValidDirectoryPath("")).to.equal(false);
});
it("should return true only for the forward slash if the string has length 1", function() {
expect(isValidDirectoryPath("/")).to.equal(true);
expect(isValidDirectoryPath(" ")).to.equal(false);
expect(isValidDirectoryPath(".")).to.equal(false);
expect(isValidDirectoryPath("a")).to.equal(false);
});
it("should return true for valid directory paths", function() {
expect(isValidDirectoryPath("/a")).to.equal(true);
expect(isValidDirectoryPath("/dir/a")).to.equal(true);
expect(isValidDirectoryPath("/dir/foo")).to.equal(true);
expect(isValidDirectoryPath("/.dir/foo-dir")).to.equal(true);
expect(isValidDirectoryPath("/.dir/foo_dir")).to.equal(true);
expect(isValidDirectoryPath("/.dir/.a")).to.equal(true);
expect(isValidDirectoryPath("/dir1/1")).to.equal(true);
expect(isValidDirectoryPath("/dir1/a1")).to.equal(true);
expect(isValidDirectoryPath("/dir1/.a1")).to.equal(true);
expect(isValidDirectoryPath("/dir_/._foo")).to.equal(true);
expect(isValidDirectoryPath("/dir-/_foo")).to.equal(true);
});
it("should return false if the path does not have a leading slash", function() {
expect(isValidDirectoryPath("a")).to.equal(false);
expect(isValidDirectoryPath("dir/a")).to.equal(false);
expect(isValidDirectoryPath("dir/foo")).to.equal(false);
expect(isValidDirectoryPath(".dir/foo-dir")).to.equal(false);
expect(isValidDirectoryPath(".dir/foo_dir")).to.equal(false);
expect(isValidDirectoryPath(".dir/.a")).to.equal(false);
expect(isValidDirectoryPath("dir1/1")).to.equal(false);
expect(isValidDirectoryPath("dir1/a1")).to.equal(false);
expect(isValidDirectoryPath("dir1/.a1")).to.equal(false);
expect(isValidDirectoryPath("dir_/._foo")).to.equal(false);
expect(isValidDirectoryPath("dir-/_foo")).to.equal(false);
});
it("should accept dot notation", function() {
expect(isValidDirectoryPath("/dir/./a")).to.equal(true);
expect(isValidDirectoryPath("/dir/../foo")).to.equal(true);
expect(isValidDirectoryPath("/.dir/./foo-dir")).to.equal(true);
expect(isValidDirectoryPath("/.dir/../foo_dir")).to.equal(true);
expect(isValidDirectoryPath("/.dir/./.a")).to.equal(true);
expect(isValidDirectoryPath("/dir1/1/.")).to.equal(true);
expect(isValidDirectoryPath("/dir1/a1/..")).to.equal(true);
expect(isValidDirectoryPath("/dir1/.a1/..")).to.equal(true);
expect(isValidDirectoryPath("/dir_/._foo/.")).to.equal(true);
expect(isValidDirectoryPath("/./dir-/_foo")).to.equal(true);
expect(isValidDirectoryPath("/../dir-/_foo")).to.equal(true);
});
});
describe("isValidFilePath()", function() {
const isValidFilePath = dirHelpers.isValidFilePath;
it("should return false for strings that are too short", function() {
expect(isValidFilePath("/a")).to.equal(false);
expect(isValidFilePath("a.")).to.equal(false);
expect(isValidFilePath(".a")).to.equal(false);
expect(isValidFilePath("/.")).to.equal(false);
});
it("should return true for arguments that are just filenames", function() {
expect(isValidFilePath("test.txt")).to.equal(true);
expect(isValidFilePath("123.script")).to.equal(true);
expect(isValidFilePath("foo123.b")).to.equal(true);
expect(isValidFilePath("my_script.script")).to.equal(true);
expect(isValidFilePath("my-script.script")).to.equal(true);
expect(isValidFilePath("_foo.lit")).to.equal(true);
expect(isValidFilePath("mult.periods.script")).to.equal(true);
expect(isValidFilePath("mult.per-iods.again.script")).to.equal(true);
});
it("should return true for valid filepaths", function() {
expect(isValidFilePath("/foo/test.txt")).to.equal(true);
expect(isValidFilePath("/../123.script")).to.equal(true);
expect(isValidFilePath("/./foo123.b")).to.equal(true);
expect(isValidFilePath("/dir/my_script.script")).to.equal(true);
expect(isValidFilePath("/dir1/dir2/dir3/my-script.script")).to.equal(true);
expect(isValidFilePath("/dir1/dir2/././../_foo.lit")).to.equal(true);
expect(isValidFilePath("/.dir1/./../.dir2/mult.periods.script")).to.equal(true);
expect(isValidFilePath("/_dir/../dir2/mult.per-iods.again.script")).to.equal(true);
});
it("should return false for strings that end with a slash", function() {
expect(isValidFilePath("/foo/")).to.equal(false);
expect(isValidFilePath("foo.txt/")).to.equal(false);
expect(isValidFilePath("/")).to.equal(false);
expect(isValidFilePath("/_dir/")).to.equal(false);
});
it("should return false for invalid arguments", function() {
expect(isValidFilePath(null)).to.equal(false);
expect(isValidFilePath()).to.equal(false);
expect(isValidFilePath(5)).to.equal(false);
expect(isValidFilePath({})).to.equal(false);
})
});
describe("getFirstParentDirectory()", function() {
const getFirstParentDirectory = dirHelpers.getFirstParentDirectory;
it("should return the first parent directory in a filepath", function() {
expect(getFirstParentDirectory("/dir1/foo.txt")).to.equal("dir1/");
expect(getFirstParentDirectory("/dir1/dir2/dir3/dir4/foo.txt")).to.equal("dir1/");
expect(getFirstParentDirectory("/_dir1/dir2/foo.js")).to.equal("_dir1/");
});
it("should return '/' if there is no first parent directory", function() {
expect(getFirstParentDirectory("")).to.equal("/");
expect(getFirstParentDirectory(" ")).to.equal("/");
expect(getFirstParentDirectory("/")).to.equal("/");
expect(getFirstParentDirectory("//")).to.equal("/");
expect(getFirstParentDirectory("foo.script")).to.equal("/");
expect(getFirstParentDirectory("/foo.txt")).to.equal("/");
});
});
describe("getAllParentDirectories()", function() {
const getAllParentDirectories = dirHelpers.getAllParentDirectories;
it("should return all parent directories in a filepath", function() {
expect(getAllParentDirectories("/")).to.equal("/");
expect(getAllParentDirectories("/home/var/foo.txt")).to.equal("/home/var/");
expect(getAllParentDirectories("/home/var/")).to.equal("/home/var/");
expect(getAllParentDirectories("/home/var/test/")).to.equal("/home/var/test/");
});
it("should return an empty string if there are no parent directories", function() {
expect(getAllParentDirectories("foo.txt")).to.equal("");
});
});
describe("isInRootDirectory()", function() {
const isInRootDirectory = dirHelpers.isInRootDirectory;
it("should return true for filepaths that refer to a file in the root directory", function() {
expect(isInRootDirectory("a.b")).to.equal(true);
expect(isInRootDirectory("foo.txt")).to.equal(true);
expect(isInRootDirectory("/foo.txt")).to.equal(true);
});
it("should return false for filepaths that refer to a file that's NOT in the root directory", function() {
expect(isInRootDirectory("/dir/foo.txt")).to.equal(false);
expect(isInRootDirectory("dir/foo.txt")).to.equal(false);
expect(isInRootDirectory("/./foo.js")).to.equal(false);
expect(isInRootDirectory("../foo.js")).to.equal(false);
expect(isInRootDirectory("/dir1/dir2/dir3/foo.txt")).to.equal(false);
});
it("should return false for invalid inputs (inputs that aren't filepaths)", function() {
expect(isInRootDirectory(null)).to.equal(false);
expect(isInRootDirectory(undefined)).to.equal(false);
expect(isInRootDirectory("")).to.equal(false);
expect(isInRootDirectory(" ")).to.equal(false);
expect(isInRootDirectory("a")).to.equal(false);
expect(isInRootDirectory("/dir")).to.equal(false);
expect(isInRootDirectory("/dir/")).to.equal(false);
expect(isInRootDirectory("/dir/foo")).to.equal(false);
});
});
describe("evaluateDirectoryPath()", function() {
//const evaluateDirectoryPath = dirHelpers.evaluateDirectoryPath;
// TODO
});
describe("evaluateFilePath()", function() {
//const evaluateFilePath = dirHelpers.evaluateFilePath;
// TODO
})
});

View File

@@ -1,24 +0,0 @@
<html>
<!-- NOT CURRENTLY USED. Used to run mocha in browser -->
<head>
<meta charset="utf-8">
<title>Mocha Tests</title>
<link href="https://unpkg.com/mocha@6.1.4/mocha.css" rel="stylesheet" />
</head>
<body>
<div id="mocha"></div>
<script defer src="https://unpkg.com/chai/chai.js"></script>
<script defer src="https://unpkg.com/mocha/mocha.js"></script>
<script type="module" class="mocha-init">
mocha.setup('bdd');
mocha.checkLeaks();
</script>
<script type="module" src="test.bundle.js"></script>
<script class="mocha-exec" type="module">
console.log("Running Tests");
mocha.run();
</script>
</body>
</html>

View File

@@ -1,5 +0,0 @@
export * from "./Netscript/DynamicRamCalculationTests";
export * from "./Netscript/StaticRamCalculationTests";
export * from "./StockMarketTests";
export * from "./StringHelperFunctionsTests";
export * from "./Terminal/DirectoryTests";

View File

@@ -1,106 +0,0 @@
import { killWorkerScript } from "../src/Netscript/killWorkerScript";
import { RunningScript } from "../src/Script/RunningScript";
import { clearEventListeners } from "./uiHelpers/clearEventListeners";
import { arrayToString } from "./helpers/arrayToString";
import { KEY } from "./helpers/keyCodes";
document.addEventListener("keydown", function(event: KeyboardEvent) {
if (logBoxOpened && event.keyCode == KEY.ESC) {
logBoxClose();
}
});
let logBoxContainer: HTMLElement | null;
let textHeader: HTMLElement | null;
let logText: HTMLElement | null;
function logBoxInit(): void {
// Initialize Close button click listener
const closeButton = document.getElementById("log-box-close");
if (closeButton == null) {
console.error(`Could not find LogBox's close button`);
return;
}
closeButton.addEventListener("click", function() {
logBoxClose();
return false;
});
// Initialize text header
textHeader = document.getElementById("log-box-text-header");
if (textHeader instanceof HTMLElement) {
textHeader.style.display = "inline-block";
}
// Initialize references to other DOM elements
logBoxContainer = document.getElementById("log-box-container");
logText = document.getElementById("log-box-text");
logBoxClose();
document.removeEventListener("DOMContentLoaded", logBoxInit);
}
document.addEventListener("DOMContentLoaded", logBoxInit);
function logBoxClose(): void {
logBoxOpened = false;
if (logBoxContainer instanceof HTMLElement) {
logBoxContainer.style.display = "none";
}
}
function logBoxOpen(): void {
logBoxOpened = true;
if (logBoxContainer instanceof HTMLElement) {
logBoxContainer.style.display = "block";
}
}
export let logBoxOpened = false;
let logBoxCurrentScript: RunningScript | null = null;
export function logBoxCreate(script: RunningScript): void {
logBoxCurrentScript = script;
const killScriptBtn = clearEventListeners("log-box-kill-script");
if (killScriptBtn == null) {
console.error(`Could not find LogBox's 'Kill Script' button`);
return;
}
killScriptBtn.addEventListener("click", () => {
killWorkerScript(script, script.server, true);
return false;
});
killScriptBtn.style.display = "inline-block";
logBoxOpen();
if (textHeader instanceof HTMLElement) {
textHeader.innerHTML = `${logBoxCurrentScript.filename} ${arrayToString(logBoxCurrentScript.args)}:<br><br>`;
} else {
console.warn(`LogBox's Text Header DOM element is null`);
}
logBoxCurrentScript.logUpd = true;
logBoxUpdateText();
}
export function logBoxUpdateText(): void {
if (!(logText instanceof HTMLElement)) { return; }
if (logBoxCurrentScript && logBoxOpened && logBoxCurrentScript.logUpd) {
logText.innerHTML = "";
for (let i = 0; i < logBoxCurrentScript.logs.length; ++i) {
logText.innerHTML += logBoxCurrentScript.logs[i];
logText.innerHTML += "<br>";
}
logBoxCurrentScript.logUpd = false;
}
}

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