Merge pull request #1313 from danielyxie/dev

0.54 again
This commit is contained in:
hydroflame 2021-09-20 17:29:17 -04:00 committed by GitHub
commit e379288536
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
200 changed files with 12287 additions and 5774 deletions

@ -1,129 +0,0 @@
@import "theme";
.active-scripts-list {
list-style-type: none;
}
.active-scripts-container {
> p {
width: 70%;
margin: 6px;
padding: 4px;
}
.accordion-header {
> pre {
color: white;
}
}
}
.active-scripts-server-header {
background-color: #444;
font-size: $defaultFontSize * 1.25;
color: #fff;
margin: 6px 6px 0 6px;
padding: 6px;
cursor: pointer;
width: 60%;
text-align: left;
border: none;
outline: none;
&:after {
content: "\02795"; /* "plus" sign (+) */
font-size: $defaultFontSize * 0.8125;
color: #fff;
float: right;
margin-left: 5px;
}
&.active,
&:hover {
background-color: #555;
}
}
.active-scripts-server-header.active {
&:after {
content: "\2796"; /* "minus" sign (-) */
font-size: $defaultFontSize * 0.8125;
color: #fff;
float: right;
margin-left: 5px;
}
&:hover {
background-color: #666;
}
}
.active-scripts-server-panel {
margin: 0 6px 6px 6px;
padding: 0 6px 6px 6px;
width: 55%;
margin-left: 5%;
display: none;
div,
ul,
ul > li {
background-color: #555;
}
}
.active-scripts-script-header {
background-color: #555;
border: none;
color: var(--my-font-color);
cursor: pointer;
display: block;
outline: none;
padding: 4px 25px 4px 10px;
position: relative;
text-align: left;
width: auto;
&:after {
content: "\02795"; /* "plus" sign (+) */
font-size: $defaultFontSize * 0.8125;
float: right;
margin-left: 5px;
color: transparent;
text-shadow: 0 0 0 var(--my-font-color);
position: absolute;
bottom: 4px;
}
&.active:after {
content: "\2796"; /* "minus" sign (-) */
}
&:hover,
&.active:hover {
background-color: #666;
}
&.active {
background-color: #555;
}
}
.active-scripts-script-panel {
background-color: #555;
display: none;
font-size: 14px;
margin-bottom: 6px;
padding: 0 18px;
width: auto;
pre,
h2,
ul,
li {
background-color: #555;
width: auto;
color: #fff;
margin-left: 5%;
}
}

@ -8,7 +8,6 @@
.augmentations-content { .augmentations-content {
> p { > p {
font-size: $defaultFontSize * 0.875; font-size: $defaultFontSize * 0.875;
width: 70%;
} }
} }

@ -7,7 +7,6 @@
.hacknet-general-info { .hacknet-general-info {
margin: 10px; margin: 10px;
width: 70vw;
} }
#hacknet-nodes-container li { #hacknet-nodes-container li {

@ -32,7 +32,7 @@
.loaderoverlay { .loaderoverlay {
$spinnerBoxSize: 200px; $spinnerBoxSize: 200px;
$themeColor: #6f3; $themeColor: #0c0;
position: absolute; position: absolute;
width: 100%; width: 100%;

@ -4,7 +4,7 @@
@import "reset"; @import "reset";
:root { :root {
--my-font-color: #6f3; --my-font-color: #0c0;
--my-background-color: #000; --my-background-color: #000;
--my-highlight-color: #fff; --my-highlight-color: #fff;
--my-prompt-color: #f92672; --my-prompt-color: #f92672;
@ -331,7 +331,7 @@ a:visited {
#status-text-container { #status-text-container {
background-color: transparent; background-color: transparent;
position: absolute; position: fixed;
top: 0; top: 0;
left: 50%; left: 50%;
} }

@ -1,76 +0,0 @@
@import "theme";
#terminal-container {
height: 100%;
width: 99%;
overflow: auto;
overflow-y: scroll;
scrollbar-width: "none"; // firefox
}
#terminal-container::-webkit-scrollbar {
display: none;
}
#terminal {
padding-top: 10px;
padding-left: 10px;
height: auto;
width: 70%;
font-size: $defaultFontSize;
overflow: auto;
overflow-y: scroll;
background-color: var(--my-background-color);
table-layout: fixed;
.prompt {
color: var(--my-prompt-color);
margin: 0;
padding: 0;
}
}
#terminal-input {
background-color: var(--my-background-color);
color: var(--my-font-color);
transition: height 1s;
}
.terminal-input {
display: inline-block;
padding: 0 !important;
margin: 0 !important;
border: 0;
background-color: var(--my-background-color);
font-size: $defaultFontSize;
outline: none;
color: var(--my-font-color);
}
.terminal-line {
width: 70%;
word-wrap: break-word;
hyphens: auto;
-webkit-hyphens: auto;
-moz-hyphens: auto;
}
#terminal-input-td {
display: flex;
}
#terminal-input-td textarea {
overflow: hidden;
resize: none;
height: auto;
}
#terminal-input-header {
white-space: pre;
}
#terminal-input-text-box {
margin-left: 2px;
flex: 1 1 auto;
}

@ -4,5 +4,7 @@
"trashAssetsBeforeRuns": true, "trashAssetsBeforeRuns": true,
"screenshotsFolder": ".cypress/screenshots", "screenshotsFolder": ".cypress/screenshots",
"videosFolder": ".cypress/videos", "videosFolder": ".cypress/videos",
"videoUploadOnPasses": false "videoUploadOnPasses": false,
"viewportWidth": 1980,
"viewportHeight": 1080
} }

@ -0,0 +1,73 @@
export {};
describe("netscript", () => {
it("Do naviguation", () => {
cy.findByRole("button", { name: "SKIP TUTORIAL" }).click();
cy.findByText("Got it!").click();
cy.findByText("Dev").click();
cy.findByText(/Source-Files/i).click();
cy.findByLabelText(/all-sf-3/i).click();
cy.findByText(/Experience/i).click();
cy.findByText(/Tons of exp/i).click();
cy.findByText(/General/i).click();
cy.findByText(/Hack w0/i).click();
cy.findByText(/SEMPOOL INVALID/i);
cy.findByText(/Many decades/i, { timeout: 15000 });
cy.findByLabelText("enter-bitnode-1").click();
cy.findByText(/Enter BN1.2/i).click();
cy.get("body").type("{esc}");
cy.findByText("Dev").click();
cy.findByText(/Experience/i).click();
cy.findByText(/Tons of exp/i).click();
cy.findByText("Create Script").click();
cy.findByText(/Script name:/i);
cy.findByText("Active Scripts").click();
cy.findByText(/Total online production of/i);
cy.findByText("Create Program").click();
cy.findByText(/This page displays/i);
cy.findByText("Stats").click();
cy.findByText(/Current City:/i);
cy.findByText("Factions").click();
cy.findByText(/Lists all/i);
cy.findByText("Augmentations").click();
cy.findByText(/Purchased Augmentations/i);
cy.findByText("Hacknet").click();
cy.findByText(/The Hacknet is a global/i);
cy.findByText("Sleeves").click();
cy.findByText(/Duplicate Sleeves are MK/i);
cy.findByText("City").click();
cy.findByText(/Sector-12/i);
cy.findByLabelText("The Slums").click();
cy.findByText("City").click();
cy.findByLabelText("Powerhouse Gym").click();
cy.findByText("City").click();
cy.findByLabelText("MegaCorp").click();
cy.findByText("Travel").click();
cy.findByText(/Travel Agency/i);
cy.findByText("Stock Market").click();
cy.findByText(/ECorp/i);
cy.findByText("Milestones").click();
cy.findByText(/don't reward you for/i);
cy.findByText("Tutorial").click();
cy.findByText(/AKA Links to/i);
cy.findByText("Options").click();
cy.findByText(/Netscript exec time/i);
});
});

@ -2,7 +2,7 @@ export {};
describe("netscript", () => { describe("netscript", () => {
it("creates and runs a NetScript 2.0 script", () => { it("creates and runs a NetScript 2.0 script", () => {
cy.findByRole("button", { name: "Exit Tutorial" }).click(); cy.findByRole("button", { name: "SKIP TUTORIAL" }).click();
cy.findByText("Got it!").click(); cy.findByText("Got it!").click();
cy.findByRole("textbox").type("connect n00dles{enter}"); cy.findByRole("textbox").type("connect n00dles{enter}");
@ -32,7 +32,7 @@ describe("netscript", () => {
}); });
it("errors and shows a dialog box when static RAM !== dynamic RAM", () => { it("errors and shows a dialog box when static RAM !== dynamic RAM", () => {
cy.findByRole("button", { name: "Exit Tutorial" }).click(); cy.findByRole("button", { name: "SKIP TUTORIAL" }).click();
cy.findByText("Got it!").click(); cy.findByText("Got it!").click();
cy.findByRole("textbox").type("nano script.js{enter}"); cy.findByRole("textbox").type("nano script.js{enter}");

@ -3,19 +3,19 @@ export {};
describe("tutorial", () => { describe("tutorial", () => {
it("completes the tutorial", () => { it("completes the tutorial", () => {
cy.findByText(/dark, dystopian future/); cy.findByText(/dark, dystopian future/);
cy.findByRole("button", { name: "Next" }).click(); cy.findByRole("button", { name: "next" }).click();
cy.findByText(/heading to the Stats page/); cy.findByText(/heading to the Stats page/);
cy.findByRole("button", { name: "Stats" }).click(); cy.findByRole("button", { name: "Stats" }).click();
cy.findByText(/lot of important information/); cy.findByText(/lot of important information/);
cy.findByRole("button", { name: "Next" }).click(); cy.findByRole("button", { name: "next" }).click();
cy.findByText(/head to your computer's terminal/); cy.findByText(/head to your computer's terminal/);
cy.findByRole("button", { name: "Terminal" }).click(); cy.findByRole("button", { name: "Terminal" }).click();
cy.findByText(/is used to interface/); cy.findByText(/is used to interface/);
cy.findByRole("button", { name: "Next" }).click(); cy.findByRole("button", { name: "next" }).click();
cy.findByText(/Let's try it out/i); cy.findByText(/Let's try it out/i);
cy.findByRole("textbox").type("help{enter}"); cy.findByRole("textbox").type("help{enter}");
@ -29,7 +29,7 @@ describe("tutorial", () => {
cy.findByText(/that's great and all/i); cy.findByText(/that's great and all/i);
cy.findByRole("textbox").type("scan-analyze{enter}"); cy.findByRole("textbox").type("scan-analyze{enter}");
cy.findByText(/this command shows more detailed information/i); cy.findByText(/shows more detailed information/i);
cy.findByRole("textbox").type("scan-analyze 2{enter}"); cy.findByRole("textbox").type("scan-analyze 2{enter}");
cy.findByText(/now you can see information/i); cy.findByText(/now you can see information/i);
@ -46,10 +46,11 @@ describe("tutorial", () => {
cy.findByRole("textbox").type("hack{enter}"); cy.findByRole("textbox").type("hack{enter}");
cy.findByText(/now attempting to hack the server/i); cy.findByText(/now attempting to hack the server/i);
cy.findByRole("button", { name: "Next" }).click(); cy.findByRole("button", { name: "next" }).click();
cy.findByText(/hacking exp/i); cy.findByRole("textbox", { timeout: 15_000 }).should("not.be.disabled").type("home{enter}");
cy.findByRole("textbox", { timeout: 15_000 }).should("not.be.disabled").type("nano n00dles.script{enter}");
cy.findByRole("textbox").type("nano n00dles.script{enter}");
// monaco can take a bit // monaco can take a bit
cy.findByRole("code", { timeout: 15_000 }).type("{selectall}{del}").type("while(true) {{}{enter}hack('n00dles');"); cy.findByRole("code", { timeout: 15_000 }).type("{selectall}{del}").type("while(true) {{}{enter}hack('n00dles');");
@ -59,7 +60,7 @@ describe("tutorial", () => {
cy.findByText(/now we'll run the script/i); cy.findByText(/now we'll run the script/i);
cy.findByRole("textbox").type("free{enter}"); cy.findByRole("textbox").type("free{enter}");
cy.findByText(/We have 4GB of free RAM on this machine/i); cy.findByText(/We have 8GB of free RAM on this machine/i);
cy.findByRole("textbox").type("run n00dles.script{enter}"); cy.findByRole("textbox").type("run n00dles.script{enter}");
cy.findByText(/Your script is now running/i); cy.findByText(/Your script is now running/i);
@ -72,7 +73,7 @@ describe("tutorial", () => {
cy.findByRole("textbox").type("tail n00dles.script{enter}"); cy.findByRole("textbox").type("tail n00dles.script{enter}");
cy.findByText(/The log for this script won't show much/i); cy.findByText(/The log for this script won't show much/i);
cy.findByRole("button", { name: "Next" }).click(); cy.findByRole("button", { name: "next" }).click();
cy.findByText(/Hacking is not the only way to earn money/i); cy.findByText(/Hacking is not the only way to earn money/i);
cy.findByRole("button", { name: "Hacknet" }).click(); cy.findByRole("button", { name: "Hacknet" }).click();
@ -87,7 +88,7 @@ describe("tutorial", () => {
cy.findByRole("button", { name: "Tutorial" }).click(); cy.findByRole("button", { name: "Tutorial" }).click();
cy.findByText(/a lot of different documentation about the game/i); cy.findByText(/a lot of different documentation about the game/i);
cy.findByRole("button", { name: "Finish Tutorial" }).click(); cy.findByRole("button", { name: "FINISH TUTORIAL" }).click();
cy.findByText("Got it!").click(); cy.findByText("Got it!").click();
cy.findByText(/Tutorial \(AKA Links to Documentation\)/i); cy.findByText(/Tutorial \(AKA Links to Documentation\)/i);

File diff suppressed because one or more lines are too long

@ -1,2 +1,2 @@
!function(n){function t(t){for(var e,i,f=t[0],c=t[1],l=t[2],a=0,s=[];a<f.length;a++)i=f[a],Object.prototype.hasOwnProperty.call(r,i)&&r[i]&&s.push(r[i][0]),r[i]=0;for(e in c)Object.prototype.hasOwnProperty.call(c,e)&&(n[e]=c[e]);for(p&&p(t);s.length;)s.shift()();return u.push.apply(u,l||[]),o()}function o(){for(var n,t=0;t<u.length;t++){for(var o=u[t],e=!0,f=1;f<o.length;f++){var c=o[f];0!==r[c]&&(e=!1)}e&&(u.splice(t--,1),n=i(i.s=o[0]))}return n}var e={},r={2:0},u=[];function i(t){if(e[t])return e[t].exports;var o=e[t]={i:t,l:!1,exports:{}};return n[t].call(o.exports,o,o.exports,i),o.l=!0,o.exports}i.m=n,i.c=e,i.d=function(n,t,o){i.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:o})},i.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},i.t=function(n,t){if(1&t&&(n=i(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var o=Object.create(null);if(i.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var e in n)i.d(o,e,function(t){return n[t]}.bind(null,e));return o},i.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return i.d(t,"a",t),t},i.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},i.p="";var f=window.webpackJsonp=window.webpackJsonp||[],c=f.push.bind(f);f.push=t,f=f.slice();for(var l=0;l<f.length;l++)t(f[l]);var p=c;u.push([1252,0]),o()}({1252:function(n,t,o){"use strict";o.r(t);o(1253),o(1255),o(1257),o(1259),o(1261),o(1263),o(1265),o(1267),o(1269),o(1271),o(1273),o(1275),o(1277),o(1279),o(1281),o(1283),o(1285),o(1287),o(1289),o(1291),o(1293),o(1295),o(1297),o(1299),o(1301),o(1303),o(1305),o(1307),o(1309),o(1311),o(1313)},1255:function(n,t,o){},1257:function(n,t,o){},1259:function(n,t,o){},1261:function(n,t,o){},1263:function(n,t,o){},1265:function(n,t,o){},1267:function(n,t,o){},1269:function(n,t,o){},1271:function(n,t,o){},1273:function(n,t,o){},1275:function(n,t,o){},1277:function(n,t,o){},1279:function(n,t,o){},1281:function(n,t,o){},1283:function(n,t,o){},1285:function(n,t,o){},1287:function(n,t,o){},1289:function(n,t,o){},1291:function(n,t,o){},1293:function(n,t,o){},1295:function(n,t,o){},1297:function(n,t,o){},1299:function(n,t,o){},1301:function(n,t,o){},1303:function(n,t,o){},1305:function(n,t,o){},1307:function(n,t,o){},1309:function(n,t,o){},1311:function(n,t,o){},1313:function(n,t,o){}}); !function(n){function t(t){for(var e,i,f=t[0],c=t[1],l=t[2],a=0,s=[];a<f.length;a++)i=f[a],Object.prototype.hasOwnProperty.call(r,i)&&r[i]&&s.push(r[i][0]),r[i]=0;for(e in c)Object.prototype.hasOwnProperty.call(c,e)&&(n[e]=c[e]);for(p&&p(t);s.length;)s.shift()();return u.push.apply(u,l||[]),o()}function o(){for(var n,t=0;t<u.length;t++){for(var o=u[t],e=!0,f=1;f<o.length;f++){var c=o[f];0!==r[c]&&(e=!1)}e&&(u.splice(t--,1),n=i(i.s=o[0]))}return n}var e={},r={2:0},u=[];function i(t){if(e[t])return e[t].exports;var o=e[t]={i:t,l:!1,exports:{}};return n[t].call(o.exports,o,o.exports,i),o.l=!0,o.exports}i.m=n,i.c=e,i.d=function(n,t,o){i.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:o})},i.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},i.t=function(n,t){if(1&t&&(n=i(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var o=Object.create(null);if(i.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var e in n)i.d(o,e,function(t){return n[t]}.bind(null,e));return o},i.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return i.d(t,"a",t),t},i.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},i.p="";var f=window.webpackJsonp=window.webpackJsonp||[],c=f.push.bind(f);f.push=t,f=f.slice();for(var l=0;l<f.length;l++)t(f[l]);var p=c;u.push([1281,0]),o()}({1281:function(n,t,o){"use strict";o.r(t);o(1282),o(1284),o(1286),o(1288),o(1290),o(1292),o(1294),o(1296),o(1298),o(1300),o(1302),o(1304),o(1306),o(1308),o(1310),o(1312),o(1314),o(1316),o(1318),o(1320),o(1322),o(1324),o(1326),o(1328),o(1330),o(1332),o(1334),o(1336),o(1338),o(1340)},1284:function(n,t,o){},1286:function(n,t,o){},1288:function(n,t,o){},1290:function(n,t,o){},1292:function(n,t,o){},1294:function(n,t,o){},1296:function(n,t,o){},1298:function(n,t,o){},1300:function(n,t,o){},1302:function(n,t,o){},1304:function(n,t,o){},1306:function(n,t,o){},1308:function(n,t,o){},1310:function(n,t,o){},1312:function(n,t,o){},1314:function(n,t,o){},1316:function(n,t,o){},1318:function(n,t,o){},1320:function(n,t,o){},1322:function(n,t,o){},1324:function(n,t,o){},1326:function(n,t,o){},1328:function(n,t,o){},1330:function(n,t,o){},1332:function(n,t,o){},1334:function(n,t,o){},1336:function(n,t,o){},1338:function(n,t,o){},1340:function(n,t,o){}});
//# sourceMappingURL=engineStyle.bundle.js.map //# sourceMappingURL=engineStyle.bundle.js.map

119
dist/engineStyle.css vendored

@ -15,7 +15,7 @@
vertical-align: middle; } vertical-align: middle; }
:root { :root {
--my-font-color: #6f3; --my-font-color: #0c0;
--my-background-color: #000; --my-background-color: #000;
--my-highlight-color: #fff; --my-highlight-color: #fff;
--my-prompt-color: #f92672; } --my-prompt-color: #f92672; }
@ -573,7 +573,7 @@ input[type="checkbox"] {
vertical-align: middle; } vertical-align: middle; }
:root { :root {
--my-font-color: #6f3; --my-font-color: #0c0;
--my-background-color: #000; --my-background-color: #000;
--my-highlight-color: #fff; --my-highlight-color: #fff;
--my-prompt-color: #f92672; } --my-prompt-color: #f92672; }
@ -1374,115 +1374,13 @@ button {
align-items: center; align-items: center;
justify-content: start; } justify-content: start; }
/* COLORS */
/* Attributes */
.active-scripts-list {
list-style-type: none; }
.active-scripts-container > p {
width: 70%;
margin: 6px;
padding: 4px; }
.active-scripts-container .accordion-header > pre {
color: white; }
.active-scripts-server-header {
background-color: #444;
font-size: 20px;
color: #fff;
margin: 6px 6px 0 6px;
padding: 6px;
cursor: pointer;
width: 60%;
text-align: left;
border: none;
outline: none; }
.active-scripts-server-header:after {
content: "\2795";
/* "plus" sign (+) */
font-size: 13px;
color: #fff;
float: right;
margin-left: 5px; }
.active-scripts-server-header.active, .active-scripts-server-header:hover {
background-color: #555; }
.active-scripts-server-header.active:after {
content: "\2796";
/* "minus" sign (-) */
font-size: 13px;
color: #fff;
float: right;
margin-left: 5px; }
.active-scripts-server-header.active:hover {
background-color: #666; }
.active-scripts-server-panel {
margin: 0 6px 6px 6px;
padding: 0 6px 6px 6px;
width: 55%;
margin-left: 5%;
display: none; }
.active-scripts-server-panel div,
.active-scripts-server-panel ul,
.active-scripts-server-panel ul > li {
background-color: #555; }
.active-scripts-script-header {
background-color: #555;
border: none;
color: var(--my-font-color);
cursor: pointer;
display: block;
outline: none;
padding: 4px 25px 4px 10px;
position: relative;
text-align: left;
width: auto; }
.active-scripts-script-header:after {
content: "\2795";
/* "plus" sign (+) */
font-size: 13px;
float: right;
margin-left: 5px;
color: transparent;
text-shadow: 0 0 0 var(--my-font-color);
position: absolute;
bottom: 4px; }
.active-scripts-script-header.active:after {
content: "\2796";
/* "minus" sign (-) */ }
.active-scripts-script-header:hover, .active-scripts-script-header.active:hover {
background-color: #666; }
.active-scripts-script-header.active {
background-color: #555; }
.active-scripts-script-panel {
background-color: #555;
display: none;
font-size: 14px;
margin-bottom: 6px;
padding: 0 18px;
width: auto; }
.active-scripts-script-panel pre,
.active-scripts-script-panel h2,
.active-scripts-script-panel ul,
.active-scripts-script-panel li {
background-color: #555;
width: auto;
color: #fff;
margin-left: 5%; }
/* COLORS */ /* COLORS */
/* Attributes */ /* Attributes */
/** /**
* Styling for the Hacknet Nodes UI Page * Styling for the Hacknet Nodes UI Page
*/ */
.hacknet-general-info { .hacknet-general-info {
margin: 10px; margin: 10px; }
width: 70vw; }
#hacknet-nodes-container li { #hacknet-nodes-container li {
float: left; float: left;
@ -1650,8 +1548,7 @@ button {
/* COLORS */ /* COLORS */
/* Attributes */ /* Attributes */
.augmentations-content > p { .augmentations-content > p {
font-size: 14px; font-size: 14px; }
width: 70%; }
.augmentations-list button, .augmentations-list button,
.augmentations-list div { .augmentations-list div {
@ -1824,7 +1721,7 @@ button {
vertical-align: middle; } vertical-align: middle; }
:root { :root {
--my-font-color: #6f3; --my-font-color: #0c0;
--my-background-color: #000; --my-background-color: #000;
--my-highlight-color: #fff; --my-highlight-color: #fff;
--my-prompt-color: #f92672; } --my-prompt-color: #f92672; }
@ -2691,11 +2588,11 @@ input[type="checkbox"] {
width: 100%; width: 100%;
height: 100%; height: 100%;
background: #000; background: #000;
color: #6f3; } color: #0c0; }
.loaderoverlay .loaderspinner, .loaderoverlay .loaderspinner:before, .loaderoverlay .loaderspinner:after { .loaderoverlay .loaderspinner, .loaderoverlay .loaderspinner:before, .loaderoverlay .loaderspinner:after {
border: 20px solid rgba(0, 0, 0, 0); border: 20px solid rgba(0, 0, 0, 0);
border-top-color: #6f3; border-top-color: #0c0;
border-bottom-color: #6f3; border-bottom-color: #0c0;
border-radius: 1000px; border-radius: 1000px;
position: absolute; position: absolute;
top: 50%; top: 50%;

81
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

@ -3,6 +3,50 @@
Changelog Changelog
========= =========
v0.54.0 - 2021-09-20 One big react node (hydroflame & community)
-------------------------------------------
** UI **
* The UI is now completely(ish) in react and I'm starting to implement
Material-UI everywhere. This will help make the game feel more consistent.
* Major help from (@threehams)
* New Terminal
* New Active Scripts page
* New sidebar.
* New Character overview
* New tutorial
* New options page
* New create program page (@Nolshine)
** Netscript **
* Add companyName to getPlayer
** Factions **
* Megacorp factions are no longer removed when installing.
** Corporation **
* All research tooltips are always visible.
* Smart supply is enabled by default if purchased (@Nolshine)
** Misc. **
* Fix "Game saved" animation. (@Nolshine)
* Update commitCrime documentation (@Tryneus)
* Fix logbox scrolling weird (@Nolshine)
* Fix weird scrolling in corporations (@BartKoppelmans)
* Fix typo (@BartKoppelmans & @Nolshine)
* Delete game now has a confirmation modal (@Nolshine)
* Fix issue where skills would not get properly updated when entering new
BN. (@Nolshine)
* Convert create gang to popup (@vmesecher)
* Fixed a bug that prevented travel to Sector-12 and New Tokyo when not using
ASCII art.
* nerf noodle bar
v0.53.0 - 2021-09-09 Way too many things. (hydroflame & community) v0.53.0 - 2021-09-09 Way too many things. (hydroflame & community)
------------------------------------------- -------------------------------------------

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

@ -117,30 +117,6 @@ that are loaded.
Examples Examples
-------- --------
**DOM Manipulation (tprintColored.ns)**
Directly alter the game's terminal and print colored text::
export function tprintColored(txt, color) {
let terminalInput = document.getElementById("terminal-input");
let rowElement = document.createElement("tr");
let cellElement = document.createElement("td");
rowElement.classList.add("posted");
cellElement.classList.add("terminal-line");
cellElement.style.color = color;
cellElement.innerText = txt;
rowElement.appendChild(cellElement);
terminalInput.before(rowElement);
}
export async function main(ns) {
tprintColored("Red Text!", "red");
tprintColored("Blue Text!", "blue");
tprintColored("Use Hex Codes!", "#3087E3");
}
**Script Scheduler (scriptScheduler.ns)** **Script Scheduler (scriptScheduler.ns)**
This script shows some of the new functionality that is available in NetscriptJS, This script shows some of the new functionality that is available in NetscriptJS,

@ -26,8 +26,8 @@ commitCrime() Netscript Function
This function is used to automatically attempt to commit crimes. If you are already in the middle of some 'working' action This function is used to automatically attempt to commit crimes. If you are already in the middle of some 'working' action
(such as working for a company or training at a gym), then running this function will automatically cancel that action and give you your earnings. (such as working for a company or training at a gym), then running this function will automatically cancel that action and give you your earnings.
This function returns the number of seconds it takes to attempt the specified crime (e.g It takes 60 seconds to attempt the 'Rob Store' crime, This function returns the number of milliseconds it takes to attempt the specified crime (e.g It takes 60 seconds to attempt the 'Rob Store' crime,
so running ``commitCrime('rob store')`` will return 60). so running ``commitCrime('rob store')`` will return 60000).
Warning: I do not recommend using the time returned from this function to try and schedule your crime attempts. Warning: I do not recommend using the time returned from this function to try and schedule your crime attempts.
Instead, I would use the :doc:`isBusy<isBusy>` Singularity function to check whether you have finished attempting a crime. Instead, I would use the :doc:`isBusy<isBusy>` Singularity function to check whether you have finished attempting a crime.

@ -36,46 +36,10 @@
ga("send", "pageview"); ga("send", "pageview");
</script> </script>
<link rel="shortcut icon" href="favicon.ico"><link href="dist/vendor.css" rel="stylesheet"><link href="dist/engineStyle.css" rel="stylesheet"></head> <link rel="shortcut icon" href="favicon.ico"><link href="dist/vendor.css" rel="stylesheet"><link href="main.css" rel="stylesheet"></head>
<body> <body>
<div id="entire-game-container" style="visibility: hidden"> <div id="entire-game-container">
<div id="mainmenu-container" style="display: flex; flex-direction: row"> <div id="mainmenu-container" style="display: flex; flex-direction: row"></div>
<!-- Main menu -->
<div id="sidebar" style=""></div>
<div id="generic-react-container"></div>
</div>
<div id="infiltration-container" class="generic-fullscreen-container"></div>
<div id="mission-container" class="generic-fullscreen-container"></div>
<!-- Work in progress screen -->
<div id="work-in-progress-container" class="generic-fullscreen-container">
<p id="work-in-progress-text"></p>
<button id="work-in-progress-cancel-button" class="work-button">Cancel Work</button>
<button id="work-in-progress-something-else-button" class="work-button">
Do something else simultaneously
</button>
</div>
<!-- Red Pill Container -->
<div id="red-pill-container" class="generic-fullscreen-container"></div>
<!-- Cinematic Text Container -->
<div id="cinematic-text-container" class="generic-fullscreen-container"></div>
<!-- Interactive Tutorial Text Screen -->
<div id="interactive-tutorial-wrapper">
<div id="interactive-tutorial-container">
<p id="interactive-tutorial-text"></p>
<button id="interactive-tutorial-exit">Exit Tutorial</button>
<button id="interactive-tutorial-next">Next</button>
<button id="interactive-tutorial-back">Back</button>
</div>
</div>
<!-- Character Overview Screen -->
<div id="character-overview"></div>
<!-- Status text --> <!-- Status text -->
<div id="status-text-container"> <div id="status-text-container">
@ -83,33 +47,8 @@
</div> </div>
</div> </div>
<input type="file" id="import-game-file-selector" name="file"/>
<!-- Loader (Loading screen) -->
<div id="loader" class="loaderoverlay">
<div class="loaderspinner"></div>
<div class="loaderlabel">Loading Bitburner...</div>
<div id="killAllMessageWrapper" class="killAllMessage killAllMessageWrapperHidden">
<script>
setTimeout(function () {
var w = document.getElementById("killAllMessageWrapper");
if (w == null) {
return;
}
w.classList.remove("killAllMessageWrapperHidden");
w.classList.add("killAllMessageWrapperShow");
}, 2000);
</script>
<p>
If the game fails to load, consider
<a href="?noScripts">killing all scripts</a>
</p>
</div>
</div>
<div id="unclickable" style="display: none">Click on this to upgrade your Source-File -1!</div> <div id="unclickable" style="display: none">Click on this to upgrade your Source-File -1!</div>
<script type="text/javascript" src="dist/vendor.bundle.js"></script><script type="text/javascript" src="dist/engine.bundle.js"></script><script type="text/javascript" src="dist/engineStyle.bundle.js"></script></body> <script type="text/javascript" src="dist/vendor.bundle.js"></script><script type="text/javascript" src="main.bundle.js"></script></body>
<!-- Misc Scripts -->
<script src="src/ThirdParty/raphael.min.js"></script> <script src="src/ThirdParty/raphael.min.js"></script>
</html> </html>

20
main.bundle.js Normal file

File diff suppressed because one or more lines are too long

1
main.bundle.js.map Normal file

File diff suppressed because one or more lines are too long

6006
main.css Normal file

File diff suppressed because it is too large Load Diff

1
main.css.map Normal file

File diff suppressed because one or more lines are too long

540
package-lock.json generated

@ -64,6 +64,7 @@
"@babel/preset-env": "^7.15.0", "@babel/preset-env": "^7.15.0",
"@babel/preset-react": "^7.0.0", "@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.15.0", "@babel/preset-typescript": "^7.15.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.1",
"@testing-library/cypress": "^8.0.1", "@testing-library/cypress": "^8.0.1",
"@types/file-saver": "^2.0.3", "@types/file-saver": "^2.0.3",
"@types/jest": "^27.0.1", "@types/jest": "^27.0.1",
@ -102,6 +103,7 @@
"null-loader": "^1.0.0", "null-loader": "^1.0.0",
"prettier": "^2.3.2", "prettier": "^2.3.2",
"raw-loader": "~0.5.0", "raw-loader": "~0.5.0",
"react-refresh": "^0.10.0",
"regenerator-runtime": "^0.13.9", "regenerator-runtime": "^0.13.9",
"sass-loader": "^7.0.3", "sass-loader": "^7.0.3",
"script-loader": "~0.7.0", "script-loader": "~0.7.0",
@ -2465,6 +2467,15 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/@eslint/eslintrc/node_modules/type-fest": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
"integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/@hapi/hoek": { "node_modules/@hapi/hoek": {
"version": "9.2.0", "version": "9.2.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.0.tgz", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.0.tgz",
@ -3914,6 +3925,240 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/@pmmmwh/react-refresh-webpack-plugin": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.1.tgz",
"integrity": "sha512-ccap6o7+y5L8cnvkZ9h8UXCGyy2DqtwCD+/N3Yru6lxMvcdkPKtdx13qd7sAC9s5qZktOmWf9lfUjsGOvSdYhg==",
"dev": true,
"dependencies": {
"ansi-html-community": "^0.0.8",
"common-path-prefix": "^3.0.0",
"core-js-pure": "^3.8.1",
"error-stack-parser": "^2.0.6",
"find-up": "^5.0.0",
"html-entities": "^2.1.0",
"loader-utils": "^2.0.0",
"schema-utils": "^3.0.0",
"source-map": "^0.7.3"
},
"engines": {
"node": ">= 10.13"
},
"peerDependencies": {
"@types/webpack": "4.x || 5.x",
"react-refresh": "^0.10.0",
"sockjs-client": "^1.4.0",
"type-fest": ">=0.17.0 <3.0.0",
"webpack": ">=4.43.0 <6.0.0",
"webpack-dev-server": "3.x || 4.x",
"webpack-hot-middleware": "2.x",
"webpack-plugin-serve": "0.x || 1.x"
},
"peerDependenciesMeta": {
"@types/webpack": {
"optional": true
},
"sockjs-client": {
"optional": true
},
"type-fest": {
"optional": true
},
"webpack-dev-server": {
"optional": true
},
"webpack-hot-middleware": {
"optional": true
},
"webpack-plugin-serve": {
"optional": true
}
}
},
"node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"peerDependencies": {
"ajv": "^6.9.1"
}
},
"node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/big.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
"dev": true,
"engines": {
"node": "*"
}
},
"node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/emojis-list": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
"dev": true,
"engines": {
"node": ">= 4"
}
},
"node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
},
"node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
"dev": true,
"dependencies": {
"locate-path": "^6.0.0",
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/html-entities": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.2.tgz",
"integrity": "sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==",
"dev": true
},
"node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/json5": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
"integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
"dev": true,
"dependencies": {
"minimist": "^1.2.5"
},
"bin": {
"json5": "lib/cli.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"dev": true,
"dependencies": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
},
"engines": {
"node": ">=8.9.0"
}
},
"node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
"dev": true,
"dependencies": {
"p-locate": "^5.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"dev": true,
"dependencies": {
"yocto-queue": "^0.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/p-locate": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
"dev": true,
"dependencies": {
"p-limit": "^3.0.2"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/schema-utils": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
"integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.8",
"ajv": "^6.12.5",
"ajv-keywords": "^3.5.2"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/@popperjs/core": { "node_modules/@popperjs/core": {
"version": "2.10.1", "version": "2.10.1",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.10.1.tgz", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.10.1.tgz",
@ -4204,9 +4449,9 @@
"integrity": "sha512-crV/441NhrynLIclg94i1wV6nX/6rU9ByUyn4muCrsL0HPd3nBzrt6kpQ9MQOB+HeYgLcRARteNJcbnYkp5OwA==" "integrity": "sha512-crV/441NhrynLIclg94i1wV6nX/6rU9ByUyn4muCrsL0HPd3nBzrt6kpQ9MQOB+HeYgLcRARteNJcbnYkp5OwA=="
}, },
"node_modules/@types/json-schema": { "node_modules/@types/json-schema": {
"version": "7.0.7", "version": "7.0.9",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
"integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
"dev": true "dev": true
}, },
"node_modules/@types/lodash": { "node_modules/@types/lodash": {
@ -5266,6 +5511,18 @@
"ansi-html": "bin/ansi-html" "ansi-html": "bin/ansi-html"
} }
}, },
"node_modules/ansi-html-community": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz",
"integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==",
"dev": true,
"engines": [
"node >= 0.8.0"
],
"bin": {
"ansi-html": "bin/ansi-html"
}
},
"node_modules/ansi-regex": { "node_modules/ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
@ -7633,6 +7890,12 @@
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true "dev": true
}, },
"node_modules/common-path-prefix": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz",
"integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==",
"dev": true
},
"node_modules/common-tags": { "node_modules/common-tags": {
"version": "1.8.0", "version": "1.8.0",
"resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz", "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz",
@ -9544,6 +9807,15 @@
"is-arrayish": "^0.2.1" "is-arrayish": "^0.2.1"
} }
}, },
"node_modules/error-stack-parser": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz",
"integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==",
"dev": true,
"dependencies": {
"stackframe": "^1.1.1"
}
},
"node_modules/es-abstract": { "node_modules/es-abstract": {
"version": "1.12.0", "version": "1.12.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz",
@ -21846,6 +22118,15 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/react-refresh": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.10.0.tgz",
"integrity": "sha512-PgidR3wST3dDYKr6b4pJoqQFpPGNKDSCDx4cZoshjXipw3LzO7mG1My2pwEzz2JVkF+inx3xRpDeQLFQGH/hsQ==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-transition-group": { "node_modules/react-transition-group": {
"version": "4.4.1", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz",
@ -21951,6 +22232,14 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/read-pkg-up/node_modules/type-fest": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
"integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
"engines": {
"node": ">=8"
}
},
"node_modules/read-pkg/node_modules/type-fest": { "node_modules/read-pkg/node_modules/type-fest": {
"version": "0.6.0", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
@ -23784,6 +24073,12 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/stackframe": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz",
"integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==",
"dev": true
},
"node_modules/start-server-and-test": { "node_modules/start-server-and-test": {
"version": "1.14.0", "version": "1.14.0",
"resolved": "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-1.14.0.tgz", "resolved": "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-1.14.0.tgz",
@ -25917,11 +26212,17 @@
} }
}, },
"node_modules/type-fest": { "node_modules/type-fest": {
"version": "0.8.1", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.3.2.tgz",
"integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "integrity": "sha512-cfvZ1nOC/VqAt8bVOIlFz8x+HdDASpiFYrSi0U0nzcAFlOnzzQ/gsPg2PP1uqjreO7sQCtraYJHMduXSewQsSA==",
"dev": true,
"optional": true,
"peer": true,
"engines": { "engines": {
"node": ">=8" "node": ">=12.20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/type-is": { "node_modules/type-is": {
@ -27969,6 +28270,18 @@
"buffer-crc32": "~0.2.3", "buffer-crc32": "~0.2.3",
"fd-slicer": "~1.1.0" "fd-slicer": "~1.1.0"
} }
},
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
} }
}, },
"dependencies": { "dependencies": {
@ -29655,6 +29968,12 @@
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true "dev": true
},
"type-fest": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
"integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
"dev": true
} }
} }
}, },
@ -30651,6 +30970,154 @@
"fastq": "^1.6.0" "fastq": "^1.6.0"
} }
}, },
"@pmmmwh/react-refresh-webpack-plugin": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.1.tgz",
"integrity": "sha512-ccap6o7+y5L8cnvkZ9h8UXCGyy2DqtwCD+/N3Yru6lxMvcdkPKtdx13qd7sAC9s5qZktOmWf9lfUjsGOvSdYhg==",
"dev": true,
"requires": {
"ansi-html-community": "^0.0.8",
"common-path-prefix": "^3.0.0",
"core-js-pure": "^3.8.1",
"error-stack-parser": "^2.0.6",
"find-up": "^5.0.0",
"html-entities": "^2.1.0",
"loader-utils": "^2.0.0",
"schema-utils": "^3.0.0",
"source-map": "^0.7.3"
},
"dependencies": {
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"requires": {}
},
"big.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
"dev": true
},
"emojis-list": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
"dev": true
},
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
},
"find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
"dev": true,
"requires": {
"locate-path": "^6.0.0",
"path-exists": "^4.0.0"
}
},
"html-entities": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.2.tgz",
"integrity": "sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==",
"dev": true
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"json5": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
"integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
"dev": true,
"requires": {
"minimist": "^1.2.5"
}
},
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
"dev": true,
"requires": {
"p-locate": "^5.0.0"
}
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"dev": true,
"requires": {
"yocto-queue": "^0.1.0"
}
},
"p-locate": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
"dev": true,
"requires": {
"p-limit": "^3.0.2"
}
},
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true
},
"schema-utils": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
"integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.8",
"ajv": "^6.12.5",
"ajv-keywords": "^3.5.2"
}
}
}
},
"@popperjs/core": { "@popperjs/core": {
"version": "2.10.1", "version": "2.10.1",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.10.1.tgz", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.10.1.tgz",
@ -30899,9 +31366,9 @@
"integrity": "sha512-crV/441NhrynLIclg94i1wV6nX/6rU9ByUyn4muCrsL0HPd3nBzrt6kpQ9MQOB+HeYgLcRARteNJcbnYkp5OwA==" "integrity": "sha512-crV/441NhrynLIclg94i1wV6nX/6rU9ByUyn4muCrsL0HPd3nBzrt6kpQ9MQOB+HeYgLcRARteNJcbnYkp5OwA=="
}, },
"@types/json-schema": { "@types/json-schema": {
"version": "7.0.7", "version": "7.0.9",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
"integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
"dev": true "dev": true
}, },
"@types/lodash": { "@types/lodash": {
@ -31806,6 +32273,12 @@
"integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=",
"dev": true "dev": true
}, },
"ansi-html-community": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz",
"integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==",
"dev": true
},
"ansi-regex": { "ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
@ -33744,6 +34217,12 @@
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true "dev": true
}, },
"common-path-prefix": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz",
"integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==",
"dev": true
},
"common-tags": { "common-tags": {
"version": "1.8.0", "version": "1.8.0",
"resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz", "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz",
@ -35321,6 +35800,15 @@
"is-arrayish": "^0.2.1" "is-arrayish": "^0.2.1"
} }
}, },
"error-stack-parser": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz",
"integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==",
"dev": true,
"requires": {
"stackframe": "^1.1.1"
}
},
"es-abstract": { "es-abstract": {
"version": "1.12.0", "version": "1.12.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz",
@ -45063,6 +45551,12 @@
"warning": "^4.0.3" "warning": "^4.0.3"
} }
}, },
"react-refresh": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.10.0.tgz",
"integrity": "sha512-PgidR3wST3dDYKr6b4pJoqQFpPGNKDSCDx4cZoshjXipw3LzO7mG1My2pwEzz2JVkF+inx3xRpDeQLFQGH/hsQ==",
"dev": true
},
"react-transition-group": { "react-transition-group": {
"version": "4.4.1", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz",
@ -45144,6 +45638,11 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
},
"type-fest": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
"integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA=="
} }
} }
}, },
@ -46735,6 +47234,12 @@
} }
} }
}, },
"stackframe": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz",
"integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==",
"dev": true
},
"start-server-and-test": { "start-server-and-test": {
"version": "1.14.0", "version": "1.14.0",
"resolved": "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-1.14.0.tgz", "resolved": "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-1.14.0.tgz",
@ -48442,9 +48947,12 @@
"dev": true "dev": true
}, },
"type-fest": { "type-fest": {
"version": "0.8.1", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.3.2.tgz",
"integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" "integrity": "sha512-cfvZ1nOC/VqAt8bVOIlFz8x+HdDASpiFYrSi0U0nzcAFlOnzzQ/gsPg2PP1uqjreO7sQCtraYJHMduXSewQsSA==",
"dev": true,
"optional": true,
"peer": true
}, },
"type-is": { "type-is": {
"version": "1.6.18", "version": "1.6.18",
@ -50137,6 +50645,12 @@
"buffer-crc32": "~0.2.3", "buffer-crc32": "~0.2.3",
"fd-slicer": "~1.1.0" "fd-slicer": "~1.1.0"
} }
},
"yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true
} }
} }
} }

@ -66,6 +66,7 @@
"@babel/preset-env": "^7.15.0", "@babel/preset-env": "^7.15.0",
"@babel/preset-react": "^7.0.0", "@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.15.0", "@babel/preset-typescript": "^7.15.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.1",
"@testing-library/cypress": "^8.0.1", "@testing-library/cypress": "^8.0.1",
"@types/file-saver": "^2.0.3", "@types/file-saver": "^2.0.3",
"@types/jest": "^27.0.1", "@types/jest": "^27.0.1",
@ -104,6 +105,7 @@
"null-loader": "^1.0.0", "null-loader": "^1.0.0",
"prettier": "^2.3.2", "prettier": "^2.3.2",
"raw-loader": "~0.5.0", "raw-loader": "~0.5.0",
"react-refresh": "^0.10.0",
"regenerator-runtime": "^0.13.9", "regenerator-runtime": "^0.13.9",
"sass-loader": "^7.0.3", "sass-loader": "^7.0.3",
"script-loader": "~0.7.0", "script-loader": "~0.7.0",

@ -1 +1,2 @@
export declare function isRepeatableAug(aug: Augmentation): boolean; export declare function isRepeatableAug(aug: Augmentation): boolean;
export declare function installAugmentations(): void;

@ -1,16 +1,17 @@
import React from "react"; import React from "react";
import { hackWorldDaemon } from "../../RedPill";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { IRouter } from "../../ui/Router";
import { removePopup } from "../../ui/React/createPopup"; import { removePopup } from "../../ui/React/createPopup";
interface IProps { interface IProps {
player: IPlayer; player: IPlayer;
router: IRouter;
popupId: string; popupId: string;
} }
export function BitFlumePopup(props: IProps): React.ReactElement { export function BitFlumePopup(props: IProps): React.ReactElement {
function flume(): void { function flume(): void {
hackWorldDaemon(props.player.bitNodeN, true, false); props.router.toBitVerse(true, false);
removePopup(props.popupId); removePopup(props.popupId);
} }
return ( return (

@ -1,18 +1,22 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags"; import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { IRouter } from "../../ui/Router";
import { BitNodes } from "../BitNode"; import { BitNodes } from "../BitNode";
import { enterBitNode } from "../../RedPill";
import { PortalPopup } from "./PortalPopup"; import { PortalPopup } from "./PortalPopup";
import { createPopup } from "../../ui/React/createPopup"; import { createPopup } from "../../ui/React/createPopup";
import { CinematicText } from "../../ui/React/CinematicText"; import { CinematicText } from "../../ui/React/CinematicText";
import { use } from "../../ui/Context";
interface IPortalProps { interface IPortalProps {
n: number; n: number;
level: number; level: number;
destroyedBitNode: number; destroyedBitNode: number;
flume: boolean; flume: boolean;
enter: (flume: boolean, destroyedBitNode: number, newBitNode: number) => void; enter: (router: IRouter, flume: boolean, destroyedBitNode: number, newBitNode: number) => void;
} }
function BitNodePortal(props: IPortalProps): React.ReactElement { function BitNodePortal(props: IPortalProps): React.ReactElement {
const router = use.Router();
const bitNode = BitNodes[`BitNode${props.n}`]; const bitNode = BitNodes[`BitNode${props.n}`];
if (bitNode == null) { if (bitNode == null) {
return <>O</>; return <>O</>;
@ -32,6 +36,7 @@ function BitNodePortal(props: IPortalProps): React.ReactElement {
n: props.n, n: props.n,
level: props.level, level: props.level,
enter: props.enter, enter: props.enter,
router: router,
destroyedBitNode: props.destroyedBitNode, destroyedBitNode: props.destroyedBitNode,
flume: props.flume, flume: props.flume,
popupId: popupId, popupId: popupId,
@ -39,7 +44,11 @@ function BitNodePortal(props: IPortalProps): React.ReactElement {
} }
return ( return (
<a className={`bitnode ${cssClass} tooltip`} onClick={openPortalPopup}> <button
className={`bitnode ${cssClass} tooltip`}
aria-label={`enter-bitnode-${bitNode.number.toString()}`}
onClick={openPortalPopup}
>
<strong>O</strong> <strong>O</strong>
<span className="tooltiptext"> <span className="tooltiptext">
<strong> <strong>
@ -51,24 +60,26 @@ function BitNodePortal(props: IPortalProps): React.ReactElement {
{bitNode.desc} {bitNode.desc}
<br /> <br />
</span> </span>
</a> </button>
); );
} }
interface IProps { interface IProps {
flume: boolean; flume: boolean;
destroyedBitNodeNum: number;
quick: boolean; quick: boolean;
enter: (flume: boolean, destroyedBitNode: number, newBitNode: number) => void; enter: (router: IRouter, flume: boolean, destroyedBitNode: number, newBitNode: number) => void;
} }
export function BitverseRoot(props: IProps): React.ReactElement { export function BitverseRoot(props: IProps): React.ReactElement {
const player = use.Player();
const enter = enterBitNode;
const destroyed = player.bitNodeN;
const [destroySequence, setDestroySequence] = useState(true && !props.quick); const [destroySequence, setDestroySequence] = useState(true && !props.quick);
// Update NextSourceFileFlags // Update NextSourceFileFlags
const nextSourceFileFlags = SourceFileFlags.slice(); const nextSourceFileFlags = SourceFileFlags.slice();
if (!props.flume) { if (!props.flume) {
if (nextSourceFileFlags[props.destroyedBitNodeNum] < 3) ++nextSourceFileFlags[props.destroyedBitNodeNum]; if (nextSourceFileFlags[destroyed] < 3) ++nextSourceFileFlags[destroyed];
} }
if (destroySequence) { if (destroySequence) {
@ -84,7 +95,7 @@ export function BitverseRoot(props: IProps): React.ReactElement {
"0020 7124696B 0000FF69 74652E6F FFFF1111", "0020 7124696B 0000FF69 74652E6F FFFF1111",
"----------------------------------------", "----------------------------------------",
"Failsafe initiated...", "Failsafe initiated...",
`Restarting BitNode-${props.destroyedBitNodeNum}...`, `Restarting BitNode-${destroyed}...`,
"...........", "...........",
"...........", "...........",
"[ERROR] FAILED TO AUTOMATICALLY REBOOT BITNODE", "[ERROR] FAILED TO AUTOMATICALLY REBOOT BITNODE",
@ -96,6 +107,7 @@ export function BitverseRoot(props: IProps): React.ReactElement {
"..............................................", "..............................................",
]} ]}
onDone={() => setDestroySequence(false)} onDone={() => setDestroySequence(false)}
auto={true}
/> />
); );
} }
@ -116,16 +128,16 @@ export function BitverseRoot(props: IProps): React.ReactElement {
<pre> \| O | |_/ |\| \ O \__| \_| | O |/ </pre> <pre> \| O | |_/ |\| \ O \__| \_| | O |/ </pre>
<pre> | | |_/ | | \| / | \_| | | </pre> <pre> | | |_/ | | \| / | \_| | | </pre>
<pre> \| / \| | / / \ |/ </pre> <pre> \| / \| | / / \ |/ </pre>
<pre> | <BitNodePortal n={10} level={nextSourceFileFlags[10]} enter={props.enter} flume={props.flume} destroyedBitNode={props.destroyedBitNodeNum} /> | | / | <BitNodePortal n={11} level={nextSourceFileFlags[11]} enter={props.enter} flume={props.flume} destroyedBitNode={props.destroyedBitNodeNum} /> | </pre> <pre> | <BitNodePortal n={10} level={nextSourceFileFlags[10]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | / | <BitNodePortal n={11} level={nextSourceFileFlags[11]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | </pre>
<pre> <BitNodePortal n={9} level={nextSourceFileFlags[9]} enter={props.enter} flume={props.flume} destroyedBitNode={props.destroyedBitNodeNum} /> | | | | | | | <BitNodePortal n={12} level={nextSourceFileFlags[12]} enter={props.enter} flume={props.flume} destroyedBitNode={props.destroyedBitNodeNum} /> </pre> <pre> <BitNodePortal n={9} level={nextSourceFileFlags[9]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | | | | | | <BitNodePortal n={12} level={nextSourceFileFlags[12]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> </pre>
<pre> | | | / / \ \ | | | </pre> <pre> | | | / / \ \ | | | </pre>
<pre> \| | / <BitNodePortal n={7} level={nextSourceFileFlags[7]} enter={props.enter} flume={props.flume} destroyedBitNode={props.destroyedBitNodeNum} /> / \ <BitNodePortal n={8} level={nextSourceFileFlags[8]} enter={props.enter} flume={props.flume} destroyedBitNode={props.destroyedBitNodeNum} /> \ | |/ </pre> <pre> \| | / <BitNodePortal n={7} level={nextSourceFileFlags[7]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> / \ <BitNodePortal n={8} level={nextSourceFileFlags[8]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \ | |/ </pre>
<pre> \ | / / | | \ \ | / </pre> <pre> \ | / / | | \ \ | / </pre>
<pre> \ \JUMP <BitNodePortal n={5} level={nextSourceFileFlags[5]} enter={props.enter} flume={props.flume} destroyedBitNode={props.destroyedBitNodeNum} />3R | | | | | | R3<BitNodePortal n={6} level={nextSourceFileFlags[6]} enter={props.enter} flume={props.flume} destroyedBitNode={props.destroyedBitNodeNum} /> PMUJ/ / </pre> <pre> \ \JUMP <BitNodePortal n={5} level={nextSourceFileFlags[5]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} />3R | | | | | | R3<BitNodePortal n={6} level={nextSourceFileFlags[6]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> PMUJ/ / </pre>
<pre> \|| | | | | | | | | ||/ </pre> <pre> \|| | | | | | | | | ||/ </pre>
<pre> \| \_ | | | | | | _/ |/ </pre> <pre> \| \_ | | | | | | _/ |/ </pre>
<pre> \ \| / \ / \ |/ / </pre> <pre> \ \| / \ / \ |/ / </pre>
<pre> <BitNodePortal n={1} level={nextSourceFileFlags[1]} enter={props.enter} flume={props.flume} destroyedBitNode={props.destroyedBitNodeNum} /> |/ <BitNodePortal n={2} level={nextSourceFileFlags[2]} enter={props.enter} flume={props.flume} destroyedBitNode={props.destroyedBitNodeNum} /> | | <BitNodePortal n={3} level={nextSourceFileFlags[3]} enter={props.enter} flume={props.flume} destroyedBitNode={props.destroyedBitNodeNum} /> \| <BitNodePortal n={4} level={nextSourceFileFlags[4]} enter={props.enter} flume={props.flume} destroyedBitNode={props.destroyedBitNodeNum} /> </pre> <pre> <BitNodePortal n={1} level={nextSourceFileFlags[1]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> |/ <BitNodePortal n={2} level={nextSourceFileFlags[2]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | <BitNodePortal n={3} level={nextSourceFileFlags[3]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \| <BitNodePortal n={4} level={nextSourceFileFlags[4]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> </pre>
<pre> | | | | | | | | </pre> <pre> | | | | | | | | </pre>
<pre> \JUMP3R|JUMP|3R| |R3|PMUJ|R3PMUJ/ </pre> <pre> \JUMP3R|JUMP|3R| |R3|PMUJ|R3PMUJ/ </pre>
<br /> <br />

@ -1,13 +1,15 @@
import React from "react"; import React from "react";
import { BitNodes } from "../BitNode"; import { BitNodes } from "../BitNode";
import { IRouter } from "../../ui/Router";
import { removePopup } from "../../ui/React/createPopup"; import { removePopup } from "../../ui/React/createPopup";
interface IProps { interface IProps {
n: number; n: number;
level: number; level: number;
destroyedBitNode: number; destroyedBitNode: number;
flume: boolean; flume: boolean;
enter: (flume: boolean, destroyedBitNode: number, newBitNode: number) => void; router: IRouter;
enter: (router: IRouter, flume: boolean, destroyedBitNode: number, newBitNode: number) => void;
popupId: string; popupId: string;
} }
@ -33,7 +35,7 @@ export function PortalPopup(props: IProps): React.ReactElement {
<button <button
className="std-button" className="std-button"
onClick={() => { onClick={() => {
props.enter(props.flume, props.destroyedBitNode, props.n); props.enter(props.router, props.flume, props.destroyedBitNode, props.n);
removePopup(props.popupId); removePopup(props.popupId);
}} }}
> >

@ -15,6 +15,7 @@ import { Skill } from "./Skill";
import { City } from "./City"; import { City } from "./City";
import { IAction } from "./IAction"; import { IAction } from "./IAction";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { IRouter } from "../ui/Router";
import { ConsoleHelpText } from "./data/Help"; import { ConsoleHelpText } from "./data/Help";
import { exceptionAlert } from "../../utils/helpers/exceptionAlert"; import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
import { getRandomInt } from "../../utils/helpers/getRandomInt"; import { getRandomInt } from "../../utils/helpers/getRandomInt";
@ -25,7 +26,7 @@ import { addOffset } from "../../utils/helpers/addOffset";
import { Faction } from "../Faction/Faction"; import { Faction } from "../Faction/Faction";
import { Factions, factionExists } from "../Faction/Factions"; import { Factions, factionExists } from "../Faction/Factions";
import { calculateHospitalizationCost } from "../Hospital/Hospital"; import { calculateHospitalizationCost } from "../Hospital/Hospital";
import { hackWorldDaemon, redPillFlag } from "../RedPill"; import { redPillFlag } from "../RedPill";
import { dialogBoxCreate } from "../../utils/DialogBox"; import { dialogBoxCreate } from "../../utils/DialogBox";
import { Settings } from "../Settings/Settings"; import { Settings } from "../Settings/Settings";
import { Augmentations } from "../Augmentation/Augmentations"; import { Augmentations } from "../Augmentation/Augmentations";
@ -1203,7 +1204,7 @@ export class Bladeburner implements IBladeburner {
} }
} }
completeAction(player: IPlayer): void { completeAction(router: IRouter, player: IPlayer): void {
switch (this.action.type) { switch (this.action.type) {
case ActionTypes["Contract"]: case ActionTypes["Contract"]:
case ActionTypes["Operation"]: { case ActionTypes["Operation"]: {
@ -1338,7 +1339,7 @@ export class Bladeburner implements IBladeburner {
// Operation Daedalus // Operation Daedalus
if (action.name === "Operation Daedalus") { if (action.name === "Operation Daedalus") {
this.resetAction(); this.resetAction();
return hackWorldDaemon(player.bitNodeN); return router.toBitVerse(false, false);
} }
if (this.logging.blackops) { if (this.logging.blackops) {
@ -1540,7 +1541,7 @@ export class Bladeburner implements IBladeburner {
} }
} }
processAction(player: IPlayer, seconds: number): void { processAction(router: IRouter, player: IPlayer, seconds: number): void {
if (this.action.type === ActionTypes["Idle"]) return; if (this.action.type === ActionTypes["Idle"]) return;
if (this.actionTimeToComplete <= 0) { if (this.actionTimeToComplete <= 0) {
throw new Error(`Invalid actionTimeToComplete value: ${this.actionTimeToComplete}, type; ${this.action.type}`); throw new Error(`Invalid actionTimeToComplete value: ${this.actionTimeToComplete}, type; ${this.action.type}`);
@ -1555,7 +1556,7 @@ export class Bladeburner implements IBladeburner {
this.actionTimeOverflow = 0; this.actionTimeOverflow = 0;
if (this.actionTimeCurrent >= this.actionTimeToComplete) { if (this.actionTimeCurrent >= this.actionTimeToComplete) {
this.actionTimeOverflow = this.actionTimeCurrent - this.actionTimeToComplete; this.actionTimeOverflow = this.actionTimeCurrent - this.actionTimeToComplete;
return this.completeAction(player); return this.completeAction(router, player);
} }
} }
@ -1888,10 +1889,10 @@ export class Bladeburner implements IBladeburner {
}); });
} }
process(player: IPlayer): void { process(router: IRouter, player: IPlayer): void {
// Edge case condition...if Operation Daedalus is complete trigger the BitNode // Edge case condition...if Operation Daedalus is complete trigger the BitNode
if (redPillFlag === false && this.blackops.hasOwnProperty("Operation Daedalus")) { if (redPillFlag === false && this.blackops.hasOwnProperty("Operation Daedalus")) {
return hackWorldDaemon(player.bitNodeN); return router.toBitVerse(false, false);
} }
// If the Player starts doing some other actions, set action to idle and alert // If the Player starts doing some other actions, set action to idle and alert
@ -1958,7 +1959,7 @@ export class Bladeburner implements IBladeburner {
this.randomEventCounter += getRandomInt(240, 600); this.randomEventCounter += getRandomInt(240, 600);
} }
this.processAction(player, seconds); this.processAction(router, player, seconds);
// Automation // Automation
if (this.automateEnabled) { if (this.automateEnabled) {

@ -3,6 +3,7 @@ import { City } from "./City";
import { Skill } from "./Skill"; import { Skill } from "./Skill";
import { IAction } from "./IAction"; import { IAction } from "./IAction";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { IRouter } from "../ui/Router";
import { WorkerScript } from "../Netscript/WorkerScript"; import { WorkerScript } from "../Netscript/WorkerScript";
export interface IBladeburner { export interface IBladeburner {
@ -103,11 +104,11 @@ export interface IBladeburner {
completeOperation(success: boolean): void; completeOperation(success: boolean): void;
getActionObject(actionId: IActionIdentifier): IAction | null; getActionObject(actionId: IActionIdentifier): IAction | null;
completeContract(success: boolean): void; completeContract(success: boolean): void;
completeAction(player: IPlayer): void; completeAction(router: IRouter, player: IPlayer): void;
changeRank(player: IPlayer, change: number): void; changeRank(player: IPlayer, change: number): void;
processAction(player: IPlayer, seconds: number): void; processAction(router: IRouter, player: IPlayer, seconds: number): void;
calculateStaminaGainPerSecond(player: IPlayer): number; calculateStaminaGainPerSecond(player: IPlayer): number;
calculateMaxStamina(player: IPlayer): void; calculateMaxStamina(player: IPlayer): void;
create(): void; create(): void;
process(player: IPlayer): void; process(router: IRouter, player: IPlayer): void;
} }

@ -0,0 +1,42 @@
import React from "react";
import { use } from "../../ui/Context";
import { CinematicText } from "../../ui/React/CinematicText";
import { dialogBoxCreate } from "../../../utils/DialogBox";
export function BladeburnerCinematic(): React.ReactElement {
const router = use.Router();
return (
<CinematicText
lines={[
"In the middle of the 21st century, OmniTek Incorporated advanced robot evolution ",
"with their Synthoids (synthetic androids), a being virtually identical to a human.",
"------",
"Their sixth-generation Synthoids, called MK-VI, were stronger, faster, and more ",
"intelligent than humans. Many argued that the MK-VI Synthoids were the first ",
"example of sentient AI.",
"------",
"Unfortunately, in 2070 a terrorist group called Ascendis Totalis hacked into OmniTek and ",
"uploaded a rogue AI into their Synthoid manufacturing facilities.",
"------",
"The MK-VI Synthoids infected by the rogue AI turned hostile toward humanity, initiating ",
"the deadliest conflict in human history. This dark chapter is now known as the Synthoid Uprising.",
"------",
"In the aftermath of the Uprising, further manufacturing of Synthoids with advanced AI ",
"was banned. MK-VI Synthoids that did not have the rogue Ascendis Totalis AI were ",
"allowed to continue their existence.",
"------",
"The intelligence community believes that not all of the rogue MK-VI Synthoids from the Uprising were ",
"found and destroyed, and that many of them are blending in as normal humans in society today. ",
"As a result, many nations have created Bladeburner divisions, special units that are tasked with ",
"investigating and dealing with Synthoid threats.",
]}
onDone={() => {
router.toTerminal();
dialogBoxCreate(
"Visit the National Security Agency (NSA) to apply for their Bladeburner " +
"division! You will need 100 of each combat stat before doing this.",
);
}}
/>
);
}

@ -3,17 +3,16 @@ import { Stats } from "./Stats";
import { Console } from "./Console"; import { Console } from "./Console";
import { AllPages } from "./AllPages"; import { AllPages } from "./AllPages";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { use } from "../../ui/Context";
import { IEngine } from "../../IEngine";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
engine: IEngine;
player: IPlayer;
} }
export function Root(props: IProps): React.ReactElement { export function BladeburnerRoot(props: IProps): React.ReactElement {
const player = use.Player();
const router = use.Router();
return ( return (
<div className="bladeburner-container"> <div className="bladeburner-container">
<div style={{ height: "60%", display: "block", position: "relative" }}> <div style={{ height: "60%", display: "block", position: "relative" }}>
@ -25,9 +24,9 @@ export function Root(props: IProps): React.ReactElement {
border: "1px solid white", border: "1px solid white",
}} }}
> >
<Stats bladeburner={props.bladeburner} player={props.player} engine={props.engine} /> <Stats bladeburner={props.bladeburner} player={player} router={router} />
</div> </div>
<Console bladeburner={props.bladeburner} player={props.player} /> <Console bladeburner={props.bladeburner} player={player} />
</div> </div>
<div <div
style={{ style={{
@ -39,7 +38,7 @@ export function Root(props: IProps): React.ReactElement {
position: "relative", position: "relative",
}} }}
> >
<AllPages bladeburner={props.bladeburner} player={props.player} /> <AllPages bladeburner={props.bladeburner} player={player} />
</div> </div>
</div> </div>
); );

@ -2,21 +2,21 @@ import React, { useState, useEffect } from "react";
import { formatNumber, convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions"; import { formatNumber, convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions";
import { BladeburnerConstants } from "../data/Constants"; import { BladeburnerConstants } from "../data/Constants";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { StatsTable } from "../../ui/React/StatsTable"; import { StatsTable } from "../../ui/React/StatsTable";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { dialogBoxCreate } from "../../../utils/DialogBox";
import { createPopup } from "../../ui/React/createPopup"; import { createPopup } from "../../ui/React/createPopup";
import { Factions } from "../../Faction/Factions"; import { Factions } from "../../Faction/Factions";
import { joinFaction, displayFactionContent } from "../../Faction/FactionHelpers"; import { IRouter } from "../../ui/Router";
import { joinFaction } from "../../Faction/FactionHelpers";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import { TravelPopup } from "./TravelPopup"; import { TravelPopup } from "./TravelPopup";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
engine: IEngine; router: IRouter;
player: IPlayer; player: IPlayer;
} }
@ -72,8 +72,7 @@ export function Stats(props: IProps): React.ReactElement {
function openFaction(): void { function openFaction(): void {
const faction = Factions["Bladeburners"]; const faction = Factions["Bladeburners"];
if (faction.isMember) { if (faction.isMember) {
props.engine.loadFactionContent(); props.router.toFaction(faction);
displayFactionContent("Bladeburners");
} else { } else {
if (props.bladeburner.rank >= BladeburnerConstants.RankNeededForFaction) { if (props.bladeburner.rank >= BladeburnerConstants.RankNeededForFaction) {
joinFaction(faction); joinFaction(faction);
@ -148,12 +147,14 @@ export function Stats(props: IProps): React.ReactElement {
</p> </p>
<p>Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}</p> <p>Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}</p>
<br /> <br />
{StatsTable([ <StatsTable
["Aug. Success Chance mult: ", formatNumber(props.player.bladeburner_success_chance_mult * 100, 1) + "%"], rows={[
["Aug. Max Stamina mult: ", formatNumber(props.player.bladeburner_max_stamina_mult * 100, 1) + "%"], ["Aug. Success Chance mult: ", formatNumber(props.player.bladeburner_success_chance_mult * 100, 1) + "%"],
["Aug. Stamina Gain mult: ", formatNumber(props.player.bladeburner_stamina_gain_mult * 100, 1) + "%"], ["Aug. Max Stamina mult: ", formatNumber(props.player.bladeburner_max_stamina_mult * 100, 1) + "%"],
["Aug. Field Analysis mult: ", formatNumber(props.player.bladeburner_analysis_mult * 100, 1) + "%"], ["Aug. Stamina Gain mult: ", formatNumber(props.player.bladeburner_stamina_gain_mult * 100, 1) + "%"],
])} ["Aug. Field Analysis mult: ", formatNumber(props.player.bladeburner_analysis_mult * 100, 1) + "%"],
]}
/>
<br /> <br />
<a onClick={openTravel} className="a-link-button" style={{ display: "inline-block" }}> <a onClick={openTravel} className="a-link-button" style={{ display: "inline-block" }}>
Travel Travel

@ -340,15 +340,13 @@ export class Blackjack extends Game<Props, State> {
{/* Buttons */} {/* Buttons */}
{!gameInProgress ? ( {!gameInProgress ? (
<div> <div>
<MuiButton color="primary" onClick={this.startOnClick} disabled={wagerInvalid || !this.canStartGame()}> <MuiButton onClick={this.startOnClick} disabled={wagerInvalid || !this.canStartGame()}>
Start Start
</MuiButton> </MuiButton>
</div> </div>
) : ( ) : (
<div> <div>
<MuiButton color="primary" onClick={this.playerHit}> <MuiButton onClick={this.playerHit}>Hit</MuiButton>
Hit
</MuiButton>
<MuiButton color="secondary" onClick={this.playerStay}> <MuiButton color="secondary" onClick={this.playerStay}>
Stay Stay
</MuiButton> </MuiButton>

@ -121,7 +121,7 @@ export const CONSTANTS: {
TotalNumBitNodes: number; TotalNumBitNodes: number;
LatestUpdate: string; LatestUpdate: string;
} = { } = {
Version: "0.53.0", Version: "0.54.0",
// Speed (in ms) at which the main loop is updated // Speed (in ms) at which the main loop is updated
_idleSpeed: 200, _idleSpeed: 200,
@ -344,83 +344,48 @@ export const CONSTANTS: {
TotalNumBitNodes: 24, TotalNumBitNodes: 24,
LatestUpdate: ` LatestUpdate: `
v0.53.0 - 2021-09-09 Way too many things. (hydroflame & community) v0.54.0 - 2021-09-20 One big react node (hydroflame & community)
------------------------------------------- -------------------------------------------
** Dev? ** ** UI **
* The entire codebase has been run through a code prettifier, hurray for consistency. (@threehams) * The UI is now completely(ish) in react and I'm starting to implement
* Lots of test. (@threehams) Material-UI everywhere. This will help make the game feel more consistent.
* Massive improvements to build speed. (@threehams) * Major help from (@threehams)
* Dev notes: This won't affect any players but is immensely useful for me. * New Terminal
* New Active Scripts page
* New sidebar.
* New Character overview
* New tutorial
* New options page
* New create program page (@Nolshine)
** Hacknet ** ** Netscript **
* Converted to ts/react * Add companyName to getPlayer
** Resleeving ** ** Factions **
* Converted to ts/react * Megacorp factions are no longer removed when installing.
** Sleeves ** ** Corporation **
* Converted to ts/react. The ui should also have a better feel. * All research tooltips are always visible.
* Fixed a bug that allowed players to recover shock much faster than intended. * Smart supply is enabled by default if purchased (@Nolshine)
** BN10 **
* You have access to Sleeves right away
* In BN10 Sleeves start with 75 shock and 25 sync.
** MathJax **
* Several tooltips have been updated to display the relevant formula in Mathjax, e.g. Favor and reputation
** Corporation **
* Completely rewritten in React. Paving the way for bigger change.
* Smart Supply is now smarter and won't deadlock the warehouse. It is also more configurable.
* Several UI fixes.
** Bladeburner **
* Action count is no longer decided when joining the Bladeburners. Experiences for all players should be more similar.
** Factions **
* No factions have home computer ram requirement. This caused some confusion for new players.
** Gang **
* Made it clear when there's a new equipment coming up.
** Netscript **
* getActionCountRemaining now returns Infinity for bladeburner general actions. (@brubsy)
* getActionEstimatedSuccessChance now returns 100% for Diplomacy and Hyperbolic Regeneration Chamber. (@brubsy)
* disableLog('ALL') now disables all logs individually, meaning you can re-enable the ones you want after. (@Cass)
* getPlayer returns numPeopleKilled.
* Dynamic RAM calculation errors have a better error message.
* Hide some functions from autocomplete.
* Added getAugmentationPrice, getAugmentationRepReq, deprecated getAugmentationCost. (@TempFound)
* Fixed bug where some crime API would return "assassinate" when that's not accepted in other functions.
** Coding Contract **
* Spiralize Matrix is easier to read.
** Misc. ** ** Misc. **
* The world map is now used in sleeve travel and bladeburner travel. * Fix "Game saved" animation. (@Nolshine)
* noselect a bunch of stuff. * Update commitCrime documentation (@Tryneus)
* Ascii maps letters are more contrasting * Fix logbox scrolling weird (@Nolshine)
* Updated documentation for infiltration. * Fix weird scrolling in corporations (@BartKoppelmans)
* Most money costs in the game will turn grey/cyan when you don't have enough money. * Fix typo (@BartKoppelmans & @Nolshine)
* Donation textbox has better look & feel. * Delete game now has a confirmation modal (@Nolshine)
* Tech vendors ram & cores buttons have better look and feels. * Fix issue where skills would not get properly updated when entering new
* cores cost modified to be a formula instead of a semi-random array of numbers. BN. (@Nolshine)
* Tech vendors now give a hint about where to get bigger servers. * Convert create gang to popup (@vmesecher)
* logboxes now displays whitespaces exactly. (@Cass) * Fixed a bug that prevented travel to Sector-12 and New Tokyo when not using
ASCII art.
* nerf noodle bar * nerf noodle bar
`, `,

@ -1,6 +1,7 @@
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../Constants";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { IPlayerOrSleeve } from "../PersonObjects/IPlayerOrSleeve"; import { IPlayerOrSleeve } from "../PersonObjects/IPlayerOrSleeve";
import { IRouter } from "../ui/Router";
export interface IConstructorParams { export interface IConstructorParams {
hacking_success_weight?: number; hacking_success_weight?: number;
@ -85,11 +86,12 @@ export class Crime {
this.kills = params.kills ? params.kills : 0; this.kills = params.kills ? params.kills : 0;
} }
commit(p: IPlayer, div = 1, singParams: any = null): number { commit(router: IRouter, p: IPlayer, div = 1, singParams: any = null): number {
if (div <= 0) { if (div <= 0) {
div = 1; div = 1;
} }
p.startCrime( p.startCrime(
router,
this.type, this.type,
this.hacking_exp / div, this.hacking_exp / div,
this.strength_exp / div, this.strength_exp / div,

@ -1,9 +1,9 @@
import { IPlayer } from "./PersonObjects/IPlayer"; import { IPlayer } from "./PersonObjects/IPlayer";
import { Bladeburner } from "./Bladeburner/Bladeburner"; import { Bladeburner } from "./Bladeburner/Bladeburner";
import { IEngine } from "./IEngine"; import { IEngine } from "./IEngine";
import { IRouter } from "./ui/Router";
import React from "react"; import React from "react";
import { TTheme as Theme } from "./ui/React/Theme";
import { General } from "./DevMenu/ui/General"; import { General } from "./DevMenu/ui/General";
import { Stats } from "./DevMenu/ui/Stats"; import { Stats } from "./DevMenu/ui/Stats";
@ -24,36 +24,35 @@ import { TimeSkip } from "./DevMenu/ui/TimeSkip";
interface IProps { interface IProps {
player: IPlayer; player: IPlayer;
engine: IEngine; engine: IEngine;
router: IRouter;
} }
export function DevMenuRoot(props: IProps): React.ReactElement { export function DevMenuRoot(props: IProps): React.ReactElement {
return ( return (
<Theme> <>
<> <h1>Development Menu - Only meant to be used for testing/debugging</h1>
<h1>Development Menu - Only meant to be used for testing/debugging</h1> <General player={props.player} router={props.router} />
<General player={props.player} /> <Stats player={props.player} />
<Stats player={props.player} /> <Factions player={props.player} />
<Factions player={props.player} /> <Augmentations player={props.player} />
<Augmentations player={props.player} /> <SourceFiles player={props.player} />
<SourceFiles player={props.player} /> <Programs player={props.player} />
<Programs player={props.player} /> <Servers />
<Servers /> <Companies />
<Companies />
{props.player.bladeburner instanceof Bladeburner && <BladeburnerElem player={props.player} />} {props.player.bladeburner instanceof Bladeburner && <BladeburnerElem player={props.player} />}
{props.player.inGang() && <Gang player={props.player} />} {props.player.inGang() && <Gang player={props.player} />}
{props.player.hasCorporation() && <Corporation player={props.player} />} {props.player.hasCorporation() && <Corporation player={props.player} />}
<CodingContracts /> <CodingContracts />
{props.player.hasWseAccount && <StockMarket />} {props.player.hasWseAccount && <StockMarket />}
{props.player.sleeves.length > 0 && <Sleeves player={props.player} />} {props.player.sleeves.length > 0 && <Sleeves player={props.player} />}
<TimeSkip player={props.player} engine={props.engine} /> <TimeSkip player={props.player} engine={props.engine} />
</> </>
</Theme>
); );
} }

@ -37,12 +37,12 @@ export function Adjuster(props: IProps): React.ReactElement {
startAdornment: ( startAdornment: (
<> <>
<Tooltip title="Add a lot"> <Tooltip title="Add a lot">
<IconButton color="primary" onClick={tons} size="large"> <IconButton onClick={tons} size="large">
<DoubleArrowIcon style={{ transform: "rotate(-90deg)" }} /> <DoubleArrowIcon style={{ transform: "rotate(-90deg)" }} />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
<Tooltip title="Add"> <Tooltip title="Add">
<IconButton color="primary" onClick={() => add(typeof value !== "string" ? value : 0)} size="large"> <IconButton onClick={() => add(typeof value !== "string" ? value : 0)} size="large">
<AddIcon /> <AddIcon />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
@ -51,16 +51,12 @@ export function Adjuster(props: IProps): React.ReactElement {
endAdornment: ( endAdornment: (
<> <>
<Tooltip title="Remove"> <Tooltip title="Remove">
<IconButton <IconButton onClick={() => subtract(typeof value !== "string" ? value : 0)} size="large">
color="primary"
onClick={() => subtract(typeof value !== "string" ? value : 0)}
size="large"
>
<RemoveIcon /> <RemoveIcon />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
<Tooltip title="Reset"> <Tooltip title="Reset">
<IconButton color="primary" onClick={reset} size="large"> <IconButton onClick={reset} size="large">
<ClearIcon /> <ClearIcon />
</IconButton> </IconButton>
</Tooltip> </Tooltip>

@ -40,7 +40,7 @@ export function Augmentations(props: IProps): React.ReactElement {
} }
return ( return (
<Accordion> <Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}> <AccordionSummary expandIcon={<ExpandMoreIcon />}>
<h2>Augmentations</h2> <h2>Augmentations</h2>
</AccordionSummary> </AccordionSummary>
@ -53,23 +53,21 @@ export function Augmentations(props: IProps): React.ReactElement {
</td> </td>
<td> <td>
<Select <Select
id="dev-augs-dropdown"
className="dropdown"
onChange={setAugmentationDropdown} onChange={setAugmentationDropdown}
value={augmentation} value={augmentation}
startAdornment={ startAdornment={
<> <>
<IconButton color="primary" onClick={queueAllAugs} size="large"> <IconButton onClick={queueAllAugs} size="large">
<ReplyAllIcon /> <ReplyAllIcon />
</IconButton> </IconButton>
<IconButton color="primary" onClick={queueAug} size="large"> <IconButton onClick={queueAug} size="large">
<ReplyIcon /> <ReplyIcon />
</IconButton> </IconButton>
</> </>
} }
endAdornment={ endAdornment={
<> <>
<IconButton color="primary" onClick={clearAugs} size="large"> <IconButton onClick={clearAugs} size="large">
<ClearIcon /> <ClearIcon />
</IconButton> </IconButton>
</> </>

@ -55,7 +55,7 @@ export function Bladeburner(props: IProps): React.ReactElement {
} }
return ( return (
<Accordion> <Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}> <AccordionSummary expandIcon={<ExpandMoreIcon />}>
<h2>Bladeburner</h2> <h2>Bladeburner</h2>
</AccordionSummary> </AccordionSummary>

@ -25,7 +25,7 @@ export function CodingContracts(): React.ReactElement {
} }
return ( return (
<Accordion> <Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}> <AccordionSummary expandIcon={<ExpandMoreIcon />}>
<h2>Coding Contracts</h2> <h2>Coding Contracts</h2>
</AccordionSummary> </AccordionSummary>

@ -69,7 +69,7 @@ export function Companies(): React.ReactElement {
} }
return ( return (
<Accordion> <Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}> <AccordionSummary expandIcon={<ExpandMoreIcon />}>
<h2>Companies</h2> <h2>Companies</h2>
</AccordionSummary> </AccordionSummary>

@ -67,7 +67,7 @@ export function Corporation(props: IProps): React.ReactElement {
} }
return ( return (
<Accordion> <Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}> <AccordionSummary expandIcon={<ExpandMoreIcon />}>
<h2>Corporation</h2> <h2>Corporation</h2>
</AccordionSummary> </AccordionSummary>

@ -97,7 +97,7 @@ export function Factions(props: IProps): React.ReactElement {
} }
return ( return (
<Accordion> <Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}> <AccordionSummary expandIcon={<ExpandMoreIcon />}>
<h2>Factions</h2> <h2>Factions</h2>
</AccordionSummary> </AccordionSummary>
@ -119,10 +119,10 @@ export function Factions(props: IProps): React.ReactElement {
value={faction} value={faction}
startAdornment={ startAdornment={
<> <>
<IconButton color="primary" onClick={receiveAllInvites} size="large"> <IconButton onClick={receiveAllInvites} size="large" arial-label="receive-all-invitation">
<ReplyAllIcon /> <ReplyAllIcon />
</IconButton> </IconButton>
<IconButton color="primary" onClick={receiveInvite} size="large"> <IconButton onClick={receiveInvite} size="large" arial-label="receive-one-invitation">
<ReplyIcon /> <ReplyIcon />
</IconButton> </IconButton>
</> </>

@ -36,7 +36,7 @@ export function Gang(props: IProps): React.ReactElement {
} }
return ( return (
<Accordion> <Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}> <AccordionSummary expandIcon={<ExpandMoreIcon />}>
<h2>Gang</h2> <h2>Gang</h2>
</AccordionSummary> </AccordionSummary>

@ -8,10 +8,11 @@ import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { hackWorldDaemon } from "../../RedPill"; import { IRouter } from "../../ui/Router";
interface IProps { interface IProps {
player: IPlayer; player: IPlayer;
router: IRouter;
} }
export function General(props: IProps): React.ReactElement { export function General(props: IProps): React.ReactElement {
@ -26,23 +27,23 @@ export function General(props: IProps): React.ReactElement {
} }
function quickB1tFlum3(): void { function quickB1tFlum3(): void {
hackWorldDaemon(props.player.bitNodeN, true, true); props.router.toBitVerse(true, true);
} }
function b1tflum3(): void { function b1tflum3(): void {
hackWorldDaemon(props.player.bitNodeN, true); props.router.toBitVerse(true, false);
} }
function quickHackW0r1dD43m0n(): void { function quickHackW0r1dD43m0n(): void {
hackWorldDaemon(props.player.bitNodeN, false, true); props.router.toBitVerse(false, true);
} }
function hackW0r1dD43m0n(): void { function hackW0r1dD43m0n(): void {
hackWorldDaemon(props.player.bitNodeN); props.router.toBitVerse(false, false);
} }
return ( return (
<Accordion> <Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}> <AccordionSummary expandIcon={<ExpandMoreIcon />}>
<h2>General</h2> <h2>General</h2>
</AccordionSummary> </AccordionSummary>

@ -35,7 +35,7 @@ export function Programs(props: IProps): React.ReactElement {
} }
return ( return (
<Accordion> <Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}> <AccordionSummary expandIcon={<ExpandMoreIcon />}>
<h2>Programs</h2> <h2>Programs</h2>
</AccordionSummary> </AccordionSummary>

@ -75,7 +75,7 @@ export function Servers(): React.ReactElement {
} }
return ( return (
<Accordion> <Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}> <AccordionSummary expandIcon={<ExpandMoreIcon />}>
<h2>Servers</h2> <h2>Servers</h2>
</AccordionSummary> </AccordionSummary>

@ -38,7 +38,7 @@ export function Sleeves(props: IProps): React.ReactElement {
} }
return ( return (
<Accordion> <Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}> <AccordionSummary expandIcon={<ExpandMoreIcon />}>
<h2>Sleeves</h2> <h2>Sleeves</h2>
</AccordionSummary> </AccordionSummary>

@ -51,7 +51,7 @@ export function SourceFiles(props: IProps): React.ReactElement {
} }
return ( return (
<Accordion> <Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}> <AccordionSummary expandIcon={<ExpandMoreIcon />}>
<h2>Source-Files</h2> <h2>Source-Files</h2>
</AccordionSummary> </AccordionSummary>
@ -72,10 +72,18 @@ export function SourceFiles(props: IProps): React.ReactElement {
</td> </td>
<td> <td>
<ButtonGroup> <ButtonGroup>
<Button onClick={setAllSF(0)}>0</Button> <Button aria-label="all-sf-0" onClick={setAllSF(0)}>
<Button onClick={setAllSF(1)}>1</Button> 0
<Button onClick={setAllSF(2)}>2</Button> </Button>
<Button onClick={setAllSF(3)}>3</Button> <Button aria-label="all-sf-1" onClick={setAllSF(1)}>
1
</Button>
<Button aria-label="all-sf-2" onClick={setAllSF(2)}>
2
</Button>
<Button aria-label="all-sf-3" onClick={setAllSF(3)}>
3
</Button>
</ButtonGroup> </ButtonGroup>
</td> </td>
</tr> </tr>

@ -136,7 +136,7 @@ export function Stats(props: IProps): React.ReactElement {
} }
return ( return (
<Accordion> <Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}> <AccordionSummary expandIcon={<ExpandMoreIcon />}>
<h2>Experience / Stats</h2> <h2>Experience / Stats</h2>
</AccordionSummary> </AccordionSummary>

@ -80,7 +80,7 @@ export function StockMarket(): React.ReactElement {
); );
} }
return ( return (
<Accordion> <Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}> <AccordionSummary expandIcon={<ExpandMoreIcon />}>
<h2>Stock Market</h2> <h2>Stock Market</h2>
</AccordionSummary> </AccordionSummary>

@ -28,9 +28,9 @@ export function TimeSkip(props: IProps): React.ReactElement {
} }
return ( return (
<Accordion> <Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}> <AccordionSummary expandIcon={<ExpandMoreIcon />}>
<h2>Sleeves</h2> <h2>Time skip</h2>
</AccordionSummary> </AccordionSummary>
<AccordionDetails> <AccordionDetails>
<Button onClick={timeskip(60 * 1000)}>1 minute</Button> <Button onClick={timeskip(60 * 1000)}>1 minute</Button>

@ -4,5 +4,5 @@ import { Faction } from "../Faction/Faction";
export declare function getNextNeurofluxLevel(): number; export declare function getNextNeurofluxLevel(): number;
export declare function hasAugmentationPrereqs(aug: Augmentation): boolean; export declare function hasAugmentationPrereqs(aug: Augmentation): boolean;
export declare function purchaseAugmentation(aug: Augmentation, fac: Faction, sing?: boolean): void; export declare function purchaseAugmentation(aug: Augmentation, fac: Faction, sing?: boolean): void;
export declare function displayFactionContent(factionName: string, initiallyOnAugmentationsPage: boolean = false);
export declare function joinFaction(faction: Faction): void; export declare function joinFaction(faction: Faction): void;
export declare function startHackingMission(faction: Faction): void;

@ -1,14 +1,9 @@
import React from "react";
import ReactDOM from "react-dom";
import { FactionRoot } from "./ui/Root";
import { Augmentations } from "../Augmentation/Augmentations"; import { Augmentations } from "../Augmentation/Augmentations";
import { PlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation"; import { PlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../Constants";
import { Engine } from "../engine";
import { Faction } from "./Faction"; import { Faction } from "./Faction";
import { Factions } from "./Factions"; import { Factions } from "./Factions";
import { HackingMission, setInMission } from "../Missions"; import { HackingMission, setInMission } from "../Missions";
@ -65,29 +60,6 @@ export function startHackingMission(faction) {
mission.init(); mission.init();
} }
//Displays the HTML content for a specific faction
export function displayFactionContent(factionName, initiallyOnAugmentationsPage = false) {
const faction = Factions[factionName];
if (faction == null) {
throw new Error(`Invalid factionName passed into displayFactionContent(): ${factionName}`);
}
if (!faction.isMember) {
throw new Error(`Not a member of this faction. Cannot display faction information`);
}
ReactDOM.render(
<FactionRoot
engine={Engine}
initiallyOnAugmentationsPage={initiallyOnAugmentationsPage}
faction={faction}
p={Player}
startHackingMissionFn={startHackingMission}
/>,
Engine.Display.content,
);
}
//Returns a boolean indicating whether the player has the prerequisites for the //Returns a boolean indicating whether the player has the prerequisites for the
//specified Augmentation //specified Augmentation
export function hasAugmentationPrereqs(aug) { export function hasAugmentationPrereqs(aug) {
@ -187,9 +159,6 @@ export function purchaseAugmentation(aug, fac, sing = false) {
); );
} }
} }
// Force a rerender of the Augmentations page
displayFactionContent(fac.name, true);
} else { } else {
dialogBoxCreate( dialogBoxCreate(
"Hmm, something went wrong when trying to purchase an Augmentation. " + "Hmm, something went wrong when trying to purchase an Augmentation. " +

@ -1,52 +1,37 @@
/** /**
* Root React Component for displaying a faction's "Purchase Augmentations" page * Root React Component for displaying a faction's "Purchase Augmentations" page
*/ */
import * as React from "react"; import React, { useState } from "react";
import { PurchaseableAugmentation } from "./PurchaseableAugmentation"; import { PurchaseableAugmentation } from "./PurchaseableAugmentation";
import { Augmentations } from "../../Augmentation/Augmentations"; import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Faction } from "../../Faction/Faction"; import { Faction } from "../../Faction/Faction";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { PurchaseAugmentationsOrderSetting } from "../../Settings/SettingEnums"; import { PurchaseAugmentationsOrderSetting } from "../../Settings/SettingEnums";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { StdButton } from "../../ui/React/StdButton"; import { StdButton } from "../../ui/React/StdButton";
import { use } from "../../ui/Context";
type IProps = { type IProps = {
faction: Faction; faction: Faction;
p: IPlayer;
routeToMainPage: () => void; routeToMainPage: () => void;
}; };
type IState = { export function AugmentationsPage(props: IProps): React.ReactElement {
rerenderFlag: boolean; const player = use.Player();
sortOrder: PurchaseAugmentationsOrderSetting;
};
const infoStyleMarkup = {
width: "70%",
};
export class AugmentationsPage extends React.Component<IProps, IState> {
// Flag for whether the player has a gang with this faction // Flag for whether the player has a gang with this faction
isPlayersGang: boolean; const isPlayersGang = player.inGang() && player.getGangName() === props.faction.name;
constructor(props: IProps) {
super(props);
this.isPlayersGang = props.p.inGang() && props.p.getGangName() === props.faction.name; const setRerender = useState(false)[1];
this.state = { function rerender(): void {
rerenderFlag: false, setRerender((old) => !old);
sortOrder: PurchaseAugmentationsOrderSetting.Default,
};
this.rerender = this.rerender.bind(this);
} }
getAugs(): string[] { function getAugs(): string[] {
if (this.isPlayersGang) { if (isPlayersGang) {
const augs: string[] = []; const augs: string[] = [];
for (const augName in Augmentations) { for (const augName in Augmentations) {
const aug = Augmentations[augName]; const aug = Augmentations[augName];
@ -57,25 +42,25 @@ export class AugmentationsPage extends React.Component<IProps, IState> {
return augs; return augs;
} else { } else {
return this.props.faction.augmentations.slice(); return props.faction.augmentations.slice();
} }
} }
getAugsSorted(): string[] { function getAugsSorted(): string[] {
switch (Settings.PurchaseAugmentationsOrder) { switch (Settings.PurchaseAugmentationsOrder) {
case PurchaseAugmentationsOrderSetting.Cost: { case PurchaseAugmentationsOrderSetting.Cost: {
return this.getAugsSortedByCost(); return getAugsSortedByCost();
} }
case PurchaseAugmentationsOrderSetting.Reputation: { case PurchaseAugmentationsOrderSetting.Reputation: {
return this.getAugsSortedByReputation(); return getAugsSortedByReputation();
} }
default: default:
return this.getAugsSortedByDefault(); return getAugsSortedByDefault();
} }
} }
getAugsSortedByCost(): string[] { function getAugsSortedByCost(): string[] {
const augs = this.getAugs(); const augs = getAugs();
augs.sort((augName1, augName2) => { augs.sort((augName1, augName2) => {
const aug1 = Augmentations[augName1], const aug1 = Augmentations[augName1],
aug2 = Augmentations[augName2]; aug2 = Augmentations[augName2];
@ -89,8 +74,8 @@ export class AugmentationsPage extends React.Component<IProps, IState> {
return augs; return augs;
} }
getAugsSortedByReputation(): string[] { function getAugsSortedByReputation(): string[] {
const augs = this.getAugs(); const augs = getAugs();
augs.sort((augName1, augName2) => { augs.sort((augName1, augName2) => {
const aug1 = Augmentations[augName1], const aug1 = Augmentations[augName1],
aug2 = Augmentations[augName2]; aug2 = Augmentations[augName2];
@ -103,88 +88,70 @@ export class AugmentationsPage extends React.Component<IProps, IState> {
return augs; return augs;
} }
getAugsSortedByDefault(): string[] { function getAugsSortedByDefault(): string[] {
return this.getAugs(); return getAugs();
} }
switchSortOrder(newOrder: PurchaseAugmentationsOrderSetting): void { function switchSortOrder(newOrder: PurchaseAugmentationsOrderSetting): void {
Settings.PurchaseAugmentationsOrder = newOrder; Settings.PurchaseAugmentationsOrder = newOrder;
this.rerender(); rerender();
} }
rerender(): void { const augs = getAugsSorted();
this.setState((prevState) => { const purchasable = augs.filter(
return { (aug: string) =>
rerenderFlag: !prevState.rerenderFlag, aug === AugmentationNames.NeuroFluxGovernor ||
}; (!player.augmentations.some((a) => a.name === aug) && !player.queuedAugmentations.some((a) => a.name === aug)),
}); );
}
render(): React.ReactNode { const purchaseableAugmentation = (aug: string): React.ReactNode => {
const augs = this.getAugsSorted(); return <PurchaseableAugmentation augName={aug} faction={props.faction} key={aug} p={player} rerender={rerender} />;
const purchasable = augs.filter( };
(aug: string) => aug === AugmentationNames.NeuroFluxGovernor ||
(!this.props.p.augmentations.some((a) => a.name === aug) &&
!this.props.p.queuedAugmentations.some((a) => a.name === aug)),
);
const purchaseableAugmentation = (aug: string): React.ReactNode => { const augListElems = purchasable.map((aug) => purchaseableAugmentation(aug));
return (
<PurchaseableAugmentation
augName={aug}
faction={this.props.faction}
key={aug}
p={this.props.p}
rerender={this.rerender}
/>
);
};
const augListElems = purchasable.map((aug) => purchaseableAugmentation(aug)); let ownedElem = <></>;
const owned = augs.filter((aug: string) => !purchasable.includes(aug));
let ownedElem = <></>; if (owned.length !== 0) {
const owned = augs.filter((aug: string) => !purchasable.includes(aug)); ownedElem = (
if (owned.length !== 0) { <>
ownedElem = (
<>
<br />
<h2>Purchased Augmentations</h2>
<p style={infoStyleMarkup}>This factions also offers these augmentations but you already own them.</p>
{owned.map((aug) => purchaseableAugmentation(aug))}
</>
);
}
return (
<div>
<StdButton onClick={this.props.routeToMainPage} text={"Back"} />
<h1>Faction Augmentations</h1>
<p style={infoStyleMarkup}>
These are all of the Augmentations that are available to purchase from {this.props.faction.name}.
Augmentations are powerful upgrades that will enhance your abilities.
</p>
<StdButton onClick={() => this.switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)} text={"Sort by Cost"} />
<StdButton
onClick={() => this.switchSortOrder(PurchaseAugmentationsOrderSetting.Reputation)}
text={"Sort by Reputation"}
/>
<StdButton
onClick={() => this.switchSortOrder(PurchaseAugmentationsOrderSetting.Default)}
text={"Sort by Default Order"}
/>
<br /> <br />
{augListElems} <h2>Purchased Augmentations</h2>
{ownedElem} <p>This factions also offers these augmentations but you already own them.</p>
<br /> {owned.map((aug) => purchaseableAugmentation(aug))}
<br /> </>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
</div>
); );
} }
return (
<div>
<StdButton onClick={props.routeToMainPage} text={"Back"} />
<h1>Faction Augmentations</h1>
<p>
These are all of the Augmentations that are available to purchase from {props.faction.name}. Augmentations are
powerful upgrades that will enhance your abilities.
</p>
<StdButton onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)} text={"Sort by Cost"} />
<StdButton
onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Reputation)}
text={"Sort by Reputation"}
/>
<StdButton
onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Default)}
text={"Sort by Default Order"}
/>
<br />
{augListElems}
{ownedElem}
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
</div>
);
} }

@ -1,58 +1,69 @@
/** /**
* React Component for the popup used to create a new gang. * React Component for the popup used to create a new gang.
*/ */
import React from "react"; import React from "react";
import { removePopup } from "../../ui/React/createPopup"; import { removePopup } from "../../ui/React/createPopup";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { StdButton } from "../../ui/React/StdButton";
import { StdButton } from "../../ui/React/StdButton"; import { IRouter } from "../../ui/Router";
import { IEngine } from "../../IEngine"; import { IPlayer } from "../../PersonObjects/IPlayer";
interface ICreateGangPopupProps { interface ICreateGangPopupProps {
popupId: string; popupId: string;
facName: string; facName: string;
p: IPlayer; player: IPlayer;
engine: IEngine; router: IRouter;
} }
export function CreateGangPopup(props: ICreateGangPopupProps): React.ReactElement { export function CreateGangPopup(props: ICreateGangPopupProps): React.ReactElement {
const player = props.player;
const combatGangText = "This is a COMBAT gang. Members in this gang will have different tasks than HACKING gangs. " + const router = props.router;
const combatGangText =
"This is a COMBAT gang. Members in this gang will have different tasks than HACKING gangs. " +
"Compared to hacking gangs, progression with combat gangs can be more difficult as territory management " + "Compared to hacking gangs, progression with combat gangs can be more difficult as territory management " +
"is more important. However, well-managed combat gangs can progress faster than hacking ones."; "is more important. However, well-managed combat gangs can progress faster than hacking ones.";
const hackingGangText = "This is a HACKING gang. Members in this gang will have different tasks than COMBAT gangs. " + const hackingGangText =
"This is a HACKING gang. Members in this gang will have different tasks than COMBAT gangs. " +
"Compared to combat gangs, progression with hacking gangs is more straightforward as territory warfare " + "Compared to combat gangs, progression with hacking gangs is more straightforward as territory warfare " +
"is not as important."; "is not as important.";
function isHacking(): boolean { function isHacking(): boolean {
return ["NiteSec", "The Black Hand"].includes(props.facName); return ["NiteSec", "The Black Hand"].includes(props.facName);
} }
function createGang(): void { function createGang(): void {
props.p.startGang(props.facName, isHacking()); player.startGang(props.facName, isHacking());
removePopup(props.popupId); removePopup(props.popupId);
props.engine.loadGangContent(); router.toGang();
} }
function onKeyUp(event: React.KeyboardEvent): void { function onKeyUp(event: React.KeyboardEvent): void {
if (event.keyCode === 13) createGang(); if (event.keyCode === 13) createGang();
} }
return ( return (
<> <>
Would you like to create a new Gang with {props.facName}? Would you like to create a new Gang with {props.facName}?
<br/> <br />
<br/> <br />
Note that this will prevent you from creating a Gang with any other Faction until this BitNode is destroyed. It also resets your reputation with this faction. Note that this will prevent you from creating a Gang with any other Faction until this BitNode is destroyed. It
<br/> also resets your reputation with this faction.
<br/> <br />
{ (isHacking()) ? hackingGangText : combatGangText } <br />
<br/> {isHacking() ? hackingGangText : combatGangText}
<br/> <br />
Other than hacking vs combat, there are NO differences between the Factions you can create a Gang with, and each of these Factions have all Augmentations available. <br />
<div className="popup-box-input-div" > Other than hacking vs combat, there are NO differences between the Factions you can create a Gang with, and each
<StdButton onClick={createGang} onKeyUp={onKeyUp} text="Create Gang" style={{float: "right"}} autoFocus={true}/> of these Factions have all Augmentations available.
</div> <div className="popup-box-input-div">
</> <StdButton
); onClick={createGang}
} onKeyUp={onKeyUp}
text="Create Gang"
style={{ float: "right" }}
autoFocus={true}
/>
</div>
</>
);
}

@ -1,65 +0,0 @@
import React, { useState } from "react";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import { Factions } from "../Factions";
import { displayFactionContent, joinFaction } from "../FactionHelpers";
interface IProps {
player: IPlayer;
engine: IEngine;
}
export function FactionList(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
function openFaction(faction: string): void {
props.engine.loadFactionContent();
displayFactionContent(faction);
}
function acceptInvitation(event: React.MouseEvent<HTMLElement>, faction: string): void {
if (!event.isTrusted) return;
joinFaction(Factions[faction]);
setRerender((x) => !x);
}
return (
<>
<h1>Factions</h1>
<p>Lists all factions you have joined</p>
<br />
<ul>
{props.player.factions.map((faction: string) => (
<li key={faction}>
<a
className="a-link-button"
onClick={() => openFaction(faction)}
style={{ padding: "4px", margin: "4px", display: "inline-block" }}
>
{faction}
</a>
</li>
))}
</ul>
<br />
<h1>Outstanding Faction Invitations</h1>
<p style={{ width: "70%" }}>
Lists factions you have been invited to. You can accept these faction invitations at any time.
</p>
<ul>
{props.player.factionInvitations.map((faction: string) => (
<li key={faction} style={{ padding: "6px", margin: "6px" }}>
<p style={{ display: "inline", margin: "4px", padding: "4px" }}>{faction}</p>
<a
className="a-link-button"
onClick={(e) => acceptInvitation(e, faction)}
style={{ display: "inline", margin: "4px", padding: "4px" }}
>
Accept Faction Invitation
</a>
</li>
))}
</ul>
</>
);
}

@ -3,7 +3,7 @@
* This is the component for displaying a single faction's UI, not the list of all * This is the component for displaying a single faction's UI, not the list of all
* accessible factions * accessible factions
*/ */
import * as React from "react"; import React, { useState } from "react";
import { AugmentationsPage } from "./AugmentationsPage"; import { AugmentationsPage } from "./AugmentationsPage";
import { DonateOption } from "./DonateOption"; import { DonateOption } from "./DonateOption";
@ -11,28 +11,18 @@ import { Info } from "./Info";
import { Option } from "./Option"; import { Option } from "./Option";
import { CONSTANTS } from "../../Constants"; import { CONSTANTS } from "../../Constants";
import { IEngine } from "../../IEngine";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers"; import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { Faction } from "../../Faction/Faction"; import { Faction } from "../../Faction/Faction";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { createSleevePurchasesFromCovenantPopup } from "../../PersonObjects/Sleeve/SleeveCovenantPurchases"; import { createSleevePurchasesFromCovenantPopup } from "../../PersonObjects/Sleeve/SleeveCovenantPurchases";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags"; import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { createPopup } from "../../ui/React/createPopup"; import { createPopup } from "../../ui/React/createPopup";
import { use } from "../../ui/Context";
import { CreateGangPopup } from "./CreateGangPopup"; import { CreateGangPopup } from "./CreateGangPopup";
type IProps = { type IProps = {
engine: IEngine;
initiallyOnAugmentationsPage?: boolean;
faction: Faction; faction: Faction;
p: IPlayer;
startHackingMissionFn: (faction: Faction) => void;
};
type IState = {
rerenderFlag: boolean;
purchasingAugs: boolean;
}; };
// Info text for all options on the UI // Info text for all options on the UI
@ -72,89 +62,66 @@ const GangNames = [
"The Black Hand", "The Black Hand",
]; ];
export class FactionRoot extends React.Component<IProps, IState> { export function FactionRoot(props: IProps): React.ReactElement {
constructor(props: IProps) { const faction = props.faction;
super(props);
this.state = { const player = use.Player();
rerenderFlag: false, const router = use.Router();
purchasingAugs: props.initiallyOnAugmentationsPage ? props.initiallyOnAugmentationsPage : false, const [, setRerenderFlag] = useState(false);
}; const [purchasingAugs, setPurchasingAugs] = useState(false);
this.manageGang = this.manageGang.bind(this); function manageGang(faction: Faction): void {
this.rerender = this.rerender.bind(this);
this.routeToMain = this.routeToMain.bind(this);
this.routeToPurchaseAugs = this.routeToPurchaseAugs.bind(this);
this.sleevePurchases = this.sleevePurchases.bind(this);
this.startFieldWork = this.startFieldWork.bind(this);
this.startHackingContracts = this.startHackingContracts.bind(this);
this.startHackingMission = this.startHackingMission.bind(this);
this.startSecurityWork = this.startSecurityWork.bind(this);
}
manageGang(): void {
// If player already has a gang, just go to the gang UI // If player already has a gang, just go to the gang UI
if (this.props.p.inGang()) { if (player.inGang()) {
return this.props.engine.loadGangContent(); return router.toGang();
} }
const popupId = "create-gang-popup"; const popupId = "create-gang-popup";
createPopup(popupId, CreateGangPopup, { createPopup(popupId, CreateGangPopup, {
popupId: popupId, popupId: popupId,
facName: this.props.faction.name, facName: faction.name,
p: this.props.p, player: player,
engine: this.props.engine, router: router,
}); });
} }
rerender(): void { function rerender(): void {
this.setState((prevState) => { setRerenderFlag((old) => !old);
return {
rerenderFlag: !prevState.rerenderFlag,
};
});
} }
// Route to the main faction page // Route to the main faction page
routeToMain(): void { function routeToMain(): void {
this.setState({ purchasingAugs: false }); setPurchasingAugs(false);
} }
// Route to the purchase augmentation UI for this faction // Route to the purchase augmentation UI for this faction
routeToPurchaseAugs(): void { function routeToPurchaseAugs(): void {
this.setState({ purchasingAugs: true }); setPurchasingAugs(true);
} }
sleevePurchases(): void { function sleevePurchases(): void {
createSleevePurchasesFromCovenantPopup(this.props.p); createSleevePurchasesFromCovenantPopup(player);
} }
startFieldWork(): void { function startFieldWork(faction: Faction): void {
this.props.p.startFactionFieldWork(this.props.faction); player.startFactionFieldWork(router, faction);
} }
startHackingContracts(): void { function startHackingContracts(faction: Faction): void {
this.props.p.startFactionHackWork(this.props.faction); player.startFactionHackWork(router, faction);
} }
startHackingMission(): void { function startHackingMission(faction: Faction): void {
const fac = this.props.faction; player.singularityStopWork();
this.props.p.singularityStopWork(); router.toHackingMission(faction);
this.props.engine.loadMissionContent();
this.props.startHackingMissionFn(fac);
} }
startSecurityWork(): void { function startSecurityWork(faction: Faction): void {
this.props.p.startFactionSecurityWork(this.props.faction); player.startFactionSecurityWork(router, faction);
} }
render(): React.ReactNode { function MainPage({ faction }: { faction: Faction }): React.ReactElement {
return this.state.purchasingAugs ? this.renderAugmentationsPage() : this.renderMainPage(); const p = player;
}
renderMainPage(): React.ReactNode {
const p = this.props.p;
const faction = this.props.faction;
const factionInfo = faction.getInfo(); const factionInfo = faction.getInfo();
// We have a special flag for whether the player this faction is the player's // We have a special flag for whether the player this faction is the player's
@ -181,49 +148,51 @@ export class FactionRoot extends React.Component<IProps, IState> {
<div className="faction-container"> <div className="faction-container">
<h1>{faction.name}</h1> <h1>{faction.name}</h1>
<Info faction={faction} factionInfo={factionInfo} /> <Info faction={faction} factionInfo={factionInfo} />
{canAccessGang && <Option buttonText={"Manage Gang"} infoText={gangInfo} onClick={this.manageGang} />} {canAccessGang && <Option buttonText={"Manage Gang"} infoText={gangInfo} onClick={() => manageGang(faction)} />}
{!isPlayersGang && factionInfo.offerHackingMission && ( {!isPlayersGang && factionInfo.offerHackingMission && (
<Option buttonText={"Hacking Mission"} infoText={hackingMissionInfo} onClick={this.startHackingMission} /> <Option
buttonText={"Hacking Mission"}
infoText={hackingMissionInfo}
onClick={() => startHackingMission(faction)}
/>
)} )}
{!isPlayersGang && factionInfo.offerHackingWork && ( {!isPlayersGang && factionInfo.offerHackingWork && (
<Option <Option
buttonText={"Hacking Contracts"} buttonText={"Hacking Contracts"}
infoText={hackingContractsInfo} infoText={hackingContractsInfo}
onClick={this.startHackingContracts} onClick={() => startHackingContracts(faction)}
/> />
)} )}
{!isPlayersGang && factionInfo.offerFieldWork && ( {!isPlayersGang && factionInfo.offerFieldWork && (
<Option buttonText={"Field Work"} infoText={fieldWorkInfo} onClick={this.startFieldWork} /> <Option buttonText={"Field Work"} infoText={fieldWorkInfo} onClick={() => startFieldWork(faction)} />
)} )}
{!isPlayersGang && factionInfo.offerSecurityWork && ( {!isPlayersGang && factionInfo.offerSecurityWork && (
<Option buttonText={"Security Work"} infoText={securityWorkInfo} onClick={this.startSecurityWork} /> <Option buttonText={"Security Work"} infoText={securityWorkInfo} onClick={() => startSecurityWork(faction)} />
)} )}
{!isPlayersGang && factionInfo.offersWork() && ( {!isPlayersGang && factionInfo.offersWork() && (
<DonateOption <DonateOption
faction={this.props.faction} faction={faction}
p={this.props.p} p={player}
rerender={this.rerender} rerender={rerender}
favorToDonate={favorToDonate} favorToDonate={favorToDonate}
disabled={!canDonate} disabled={!canDonate}
/> />
)} )}
<Option buttonText={"Purchase Augmentations"} infoText={augmentationsInfo} onClick={this.routeToPurchaseAugs} /> <Option buttonText={"Purchase Augmentations"} infoText={augmentationsInfo} onClick={routeToPurchaseAugs} />
{canPurchaseSleeves && ( {canPurchaseSleeves && (
<Option <Option
buttonText={"Purchase & Upgrade Duplicate Sleeves"} buttonText={"Purchase & Upgrade Duplicate Sleeves"}
infoText={sleevePurchasesInfo} infoText={sleevePurchasesInfo}
onClick={this.sleevePurchases} onClick={sleevePurchases}
/> />
)} )}
</div> </div>
); );
} }
renderAugmentationsPage(): React.ReactNode { return purchasingAugs ? (
return ( <AugmentationsPage faction={faction} routeToMainPage={routeToMain} />
<> ) : (
<AugmentationsPage faction={this.props.faction} p={this.props.p} routeToMainPage={this.routeToMain} /> <MainPage faction={faction} />
</> );
);
}
} }

@ -0,0 +1,81 @@
import React, { useState, useEffect } from "react";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IRouter } from "../../ui/Router";
import { Factions } from "../Factions";
import { Faction } from "../Faction";
import { joinFaction } from "../FactionHelpers";
import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box";
import Link from "@mui/material/Link";
import Button from "@mui/material/Button";
import TableBody from "@mui/material/TableBody";
import { Table, TableCell } from "../../ui/React/Table";
import TableRow from "@mui/material/TableRow";
interface IProps {
player: IPlayer;
router: IRouter;
}
export function FactionsRoot(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
useEffect(() => {
const id = setInterval(rerender, 1000);
return () => clearInterval(id);
}, []);
function openFaction(faction: Faction): void {
props.router.toFaction(faction);
}
function acceptInvitation(event: React.MouseEvent<HTMLButtonElement, MouseEvent>, faction: string): void {
if (!event.isTrusted) return;
joinFaction(Factions[faction]);
setRerender((x) => !x);
}
return (
<>
<Typography variant="h5" color="primary">
Factions
</Typography>
<Typography>Lists all factions you have joined</Typography>
<br />
<Box display="flex" flexDirection="column">
{props.player.factions.map((faction: string) => (
<Link key={faction} variant="h6" onClick={() => openFaction(Factions[faction])}>
{faction}
</Link>
))}
</Box>
<br />
{props.player.factionInvitations.length > 0 && (
<>
<Typography variant="h5" color="primary">
Outstanding Faction Invitations
</Typography>
<Typography>
Lists factions you have been invited to. You can accept these faction invitations at any time.
</Typography>
<Table size="small" padding="none">
<TableBody>
{props.player.factionInvitations.map((faction: string) => (
<TableRow key={faction}>
<TableCell>
<Typography noWrap>{faction}</Typography>
</TableCell>
<TableCell align="right">
<Button onClick={(e) => acceptInvitation(e, faction)}>Join!</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</>
)}
</>
);
}

@ -12,6 +12,7 @@ interface IProps {
player: IPlayer; player: IPlayer;
faction: Faction; faction: Faction;
aug: Augmentation; aug: Augmentation;
rerender: () => void;
popupId: string; popupId: string;
} }
@ -24,6 +25,7 @@ export function PurchaseAugmentationPopup(props: IProps): React.ReactElement {
} }
purchaseAugmentation(props.aug, props.faction); purchaseAugmentation(props.aug, props.faction);
props.rerender();
removePopup(props.popupId); removePopup(props.popupId);
} }

@ -7,7 +7,6 @@ import * as React from "react";
import { getNextNeurofluxLevel, hasAugmentationPrereqs, purchaseAugmentation } from "../FactionHelpers"; import { getNextNeurofluxLevel, hasAugmentationPrereqs, purchaseAugmentation } from "../FactionHelpers";
import { PurchaseAugmentationPopup } from "./PurchaseAugmentationPopup"; import { PurchaseAugmentationPopup } from "./PurchaseAugmentationPopup";
import { Augmentation } from "../../Augmentation/Augmentation";
import { Augmentations } from "../../Augmentation/Augmentations"; import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Faction } from "../../Faction/Faction"; import { Faction } from "../../Faction/Faction";
@ -28,63 +27,56 @@ type IProps = {
rerender: () => void; rerender: () => void;
}; };
export class PurchaseableAugmentation extends React.Component<IProps, any> { export function PurchaseableAugmentation(props: IProps): React.ReactElement {
aug: Augmentation; const aug = Augmentations[props.augName];
if (aug == null) throw new Error(`aug ${props.augName} does not exists`);
constructor(props: IProps) { function getMoneyCost(): number {
super(props); return aug.baseCost * props.faction.getInfo().augmentationPriceMult;
const aug = Augmentations[this.props.augName];
if (aug == null) throw new Error(`aug ${this.props.augName} does not exists`);
this.aug = aug;
this.handleClick = this.handleClick.bind(this);
} }
getMoneyCost(): number { function getRepCost(): number {
return this.aug.baseCost * this.props.faction.getInfo().augmentationPriceMult; return aug.baseRepRequirement * props.faction.getInfo().augmentationRepRequirementMult;
} }
getRepCost(): number { function handleClick(): void {
return this.aug.baseRepRequirement * this.props.faction.getInfo().augmentationRepRequirementMult;
}
handleClick(): void {
if (!Settings.SuppressBuyAugmentationConfirmation) { if (!Settings.SuppressBuyAugmentationConfirmation) {
const popupId = "purchase-augmentation-popup"; const popupId = "purchase-augmentation-popup";
createPopup(popupId, PurchaseAugmentationPopup, { createPopup(popupId, PurchaseAugmentationPopup, {
aug: this.aug, aug: aug,
faction: this.props.faction, faction: props.faction,
player: this.props.p, player: props.p,
rerender: props.rerender,
popupId: popupId, popupId: popupId,
}); });
} else { } else {
purchaseAugmentation(this.aug, this.props.faction); purchaseAugmentation(aug, props.faction);
props.rerender();
} }
} }
// Whether the player has the prerequisite Augmentations // Whether the player has the prerequisite Augmentations
hasPrereqs(): boolean { function hasPrereqs(): boolean {
return hasAugmentationPrereqs(this.aug); return hasAugmentationPrereqs(aug);
} }
// Whether the player has enough rep for this Augmentation // Whether the player has enough rep for this Augmentation
hasReputation(): boolean { function hasReputation(): boolean {
return this.props.faction.playerReputation >= this.getRepCost(); return props.faction.playerReputation >= getRepCost();
} }
// Whether the player has this augmentations (purchased OR installed) // Whether the player has this augmentations (purchased OR installed)
owned(): boolean { function owned(): boolean {
let owned = false; let owned = false;
for (const queuedAug of this.props.p.queuedAugmentations) { for (const queuedAug of props.p.queuedAugmentations) {
if (queuedAug.name === this.props.augName) { if (queuedAug.name === props.augName) {
owned = true; owned = true;
break; break;
} }
} }
for (const installedAug of this.props.p.augmentations) { for (const installedAug of props.p.augmentations) {
if (installedAug.name === this.props.augName) { if (installedAug.name === props.augName) {
owned = true; owned = true;
break; break;
} }
@ -93,96 +85,94 @@ export class PurchaseableAugmentation extends React.Component<IProps, any> {
return owned; return owned;
} }
render(): React.ReactNode { if (aug == null) {
if (this.aug == null) { console.error(
console.error( `Invalid Augmentation when trying to create PurchaseableAugmentation display element: ${props.augName}`,
`Invalid Augmentation when trying to create PurchaseableAugmentation display element: ${this.props.augName}`,
);
return null;
}
const moneyCost = this.getMoneyCost();
const repCost = this.getRepCost();
// Determine UI properties
let disabled = false;
let status: JSX.Element = <></>;
let color = "";
if (!this.hasPrereqs()) {
disabled = true;
status = <>LOCKED (Requires {this.aug.prereqs.map((aug) => AugFormat(aug))} as prerequisite)</>;
color = "red";
} else if (this.aug.name !== AugmentationNames.NeuroFluxGovernor && (this.aug.owned || this.owned())) {
disabled = true;
} else if (this.hasReputation()) {
status = (
<>
UNLOCKED (at {Reputation(repCost)} faction reputation) - <Money money={moneyCost} player={this.props.p} />
</>
);
} else {
disabled = true;
status = (
<>
LOCKED (Requires {Reputation(repCost)} faction reputation - <Money money={moneyCost} player={this.props.p} />)
</>
);
color = "red";
}
const txtStyle: IMap<string> = {
display: "inline-block",
};
if (color !== "") {
txtStyle.color = color;
}
// Determine button txt
let btnTxt = this.aug.name;
if (this.aug.name === AugmentationNames.NeuroFluxGovernor) {
btnTxt += ` - Level ${getNextNeurofluxLevel()}`;
}
let tooltip = <></>;
if (typeof this.aug.info === "string")
tooltip = (
<>
<span dangerouslySetInnerHTML={{ __html: this.aug.info }} />
<br />
<br />
{this.aug.stats}
</>
);
else
tooltip = (
<>
{this.aug.info}
<br />
<br />
{this.aug.stats}
</>
);
return (
<li key={this.aug.name}>
<span
style={{
margin: "4px",
padding: "4px",
}}
>
<StdButton
disabled={disabled}
onClick={this.handleClick}
style={{
display: "inline-block",
}}
text={btnTxt}
tooltip={tooltip}
/>
<p style={txtStyle}>{status}</p>
</span>
</li>
); );
return <></>;
} }
const moneyCost = getMoneyCost();
const repCost = getRepCost();
// Determine UI properties
let disabled = false;
let status: JSX.Element = <></>;
let color = "";
if (!hasPrereqs()) {
disabled = true;
status = <>LOCKED (Requires {aug.prereqs.map((aug) => AugFormat(aug))} as prerequisite)</>;
color = "red";
} else if (aug.name !== AugmentationNames.NeuroFluxGovernor && (aug.owned || owned())) {
disabled = true;
} else if (hasReputation()) {
status = (
<>
UNLOCKED (at {Reputation(repCost)} faction reputation) - <Money money={moneyCost} player={props.p} />
</>
);
} else {
disabled = true;
status = (
<>
LOCKED (Requires {Reputation(repCost)} faction reputation - <Money money={moneyCost} player={props.p} />)
</>
);
color = "red";
}
const txtStyle: IMap<string> = {
display: "inline-block",
};
if (color !== "") {
txtStyle.color = color;
}
// Determine button txt
let btnTxt = aug.name;
if (aug.name === AugmentationNames.NeuroFluxGovernor) {
btnTxt += ` - Level ${getNextNeurofluxLevel()}`;
}
let tooltip = <></>;
if (typeof aug.info === "string")
tooltip = (
<>
<span dangerouslySetInnerHTML={{ __html: aug.info }} />
<br />
<br />
{aug.stats}
</>
);
else
tooltip = (
<>
{aug.info}
<br />
<br />
{aug.stats}
</>
);
return (
<li key={aug.name}>
<span
style={{
margin: "4px",
padding: "4px",
}}
>
<StdButton
disabled={disabled}
onClick={handleClick}
style={{
display: "inline-block",
}}
text={btnTxt}
tooltip={tooltip}
/>
<p style={txtStyle}>{status}</p>
</span>
</li>
);
} }

@ -207,10 +207,10 @@ function setTheme() {
/^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/i.test(FconfSettings.THEME_BACKGROUND_COLOR) && /^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/i.test(FconfSettings.THEME_BACKGROUND_COLOR) &&
/^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/i.test(FconfSettings.THEME_PROMPT_COLOR) /^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/i.test(FconfSettings.THEME_PROMPT_COLOR)
) { ) {
document.body.style.setProperty("--my-highlight-color", FconfSettings.THEME_HIGHLIGHT_COLOR); // document.body.style.setProperty("--my-highlight-color", FconfSettings.THEME_HIGHLIGHT_COLOR);
document.body.style.setProperty("--my-font-color", FconfSettings.THEME_FONT_COLOR); // document.body.style.setProperty("--my-font-color", FconfSettings.THEME_FONT_COLOR);
document.body.style.setProperty("--my-background-color", FconfSettings.THEME_BACKGROUND_COLOR); // document.body.style.setProperty("--my-background-color", FconfSettings.THEME_BACKGROUND_COLOR);
document.body.style.setProperty("--my-prompt-color", FconfSettings.THEME_PROMPT_COLOR); // document.body.style.setProperty("--my-prompt-color", FconfSettings.THEME_PROMPT_COLOR);
} }
} }

@ -2,6 +2,7 @@ import { GangMemberUpgrade } from "./GangMemberUpgrade";
import { GangMember } from "./GangMember"; import { GangMember } from "./GangMember";
import { WorkerScript } from "../Netscript/WorkerScript"; import { WorkerScript } from "../Netscript/WorkerScript";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { IAscensionResult } from "./IAscensionResult";
export interface IGang { export interface IGang {
facName: string; facName: string;
@ -37,8 +38,9 @@ export interface IGang {
getWantedPenalty(): number; getWantedPenalty(): number;
calculatePower(): number; calculatePower(): number;
killMember(member: GangMember): void; killMember(member: GangMember): void;
ascendMember(member: GangMember, workerScript: WorkerScript): void; ascendMember(member: GangMember, workerScript: WorkerScript): IAscensionResult;
getDiscount(): number; getDiscount(): number;
getAllTaskNames(): string[]; getAllTaskNames(): string[];
getUpgradeCost(upg: GangMemberUpgrade): number; getUpgradeCost(upg: GangMemberUpgrade): number;
toJSON(): any;
} }

@ -2,20 +2,19 @@
* React Component for all the gang stuff. * React Component for all the gang stuff.
*/ */
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { ManagementSubpage } from "./ManagementSubpage"; import { ManagementSubpage } from "./ManagementSubpage";
import { TerritorySubpage } from "./TerritorySubpage"; import { TerritorySubpage } from "./TerritorySubpage";
import { IEngine } from "../../IEngine"; import { use } from "../../ui/Context";
import { Factions } from "../../Faction/Factions";
import { Gang } from "../Gang"; import { Gang } from "../Gang";
import { displayFactionContent } from "../../Faction/FactionHelpers";
interface IProps { interface IProps {
gang: Gang; gang: Gang;
player: IPlayer;
engine: IEngine;
} }
export function Root(props: IProps): React.ReactElement { export function GangRoot(props: IProps): React.ReactElement {
const player = use.Player();
const router = use.Router();
const [management, setManagement] = useState(true); const [management, setManagement] = useState(true);
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
@ -25,8 +24,7 @@ export function Root(props: IProps): React.ReactElement {
}, []); }, []);
function back(): void { function back(): void {
props.engine.loadFactionContent(); router.toFaction(Factions[props.gang.facName]);
displayFactionContent(props.gang.facName);
} }
return ( return (
@ -48,11 +46,7 @@ export function Root(props: IProps): React.ReactElement {
> >
Gang Territory Gang Territory
</a> </a>
{management ? ( {management ? <ManagementSubpage gang={props.gang} player={player} /> : <TerritorySubpage gang={props.gang} />}
<ManagementSubpage gang={props.gang} player={props.player} />
) : (
<TerritorySubpage gang={props.gang} />
)}
</div> </div>
); );
} }

@ -55,7 +55,9 @@ export function GangStats(props: IProps): React.ReactElement {
</p> </p>
<br /> <br />
<div> <div>
<p style={{ display: "inline-block" }}>Money gain rate: {MoneyRate(5 * props.gang.moneyGainRate)}</p> <p style={{ display: "inline-block" }}>
Money gain rate: <MoneyRate money={5 * props.gang.moneyGainRate} />
</p>
</div> </div>
<br /> <br />
<p className="tooltip" style={{ display: "inline-block" }}> <p className="tooltip" style={{ display: "inline-block" }}>

@ -28,7 +28,7 @@ export function TaskSelector(props: IProps): React.ReactElement {
const tasks = props.gang.getAllTaskNames(); const tasks = props.gang.getAllTaskNames();
const data = [ const data = [
[`Money:`, MoneyRate(5 * props.member.calculateMoneyGain(props.gang))], [`Money:`, <MoneyRate money={5 * props.member.calculateMoneyGain(props.gang)} />],
[`Respect:`, `${numeralWrapper.formatRespect(5 * props.member.calculateRespectGain(props.gang))} / sec`], [`Respect:`, `${numeralWrapper.formatRespect(5 * props.member.calculateRespectGain(props.gang))} / sec`],
[`Wanted Level:`, `${numeralWrapper.formatWanted(5 * props.member.calculateWantedLevelGain(props.gang))} / sec`], [`Wanted Level:`, `${numeralWrapper.formatWanted(5 * props.member.calculateWantedLevelGain(props.gang))} / sec`],
[`Total Respect:`, `${numeralWrapper.formatRespect(props.member.earnedRespect)}`], [`Total Respect:`, `${numeralWrapper.formatRespect(props.member.earnedRespect)}`],
@ -46,7 +46,9 @@ export function TaskSelector(props: IProps): React.ReactElement {
</option> </option>
))} ))}
</select> </select>
<div>{StatsTable(data)}</div> <div>
<StatsTable rows={data} />
</div>
</> </>
); );
} }

@ -0,0 +1,13 @@
import React, { useEffect } from "react";
import { startHackingMission } from "../../Faction/FactionHelpers";
import { Faction } from "../../Faction/Faction";
interface IProps {
faction: Faction;
}
export function HackingMissionRoot(props: IProps): React.ReactElement {
useEffect(() => startHackingMission(props.faction));
return <div id="mission-container"></div>;
}

@ -144,7 +144,8 @@ export function HacknetNodeElem(props: IProps): React.ReactElement {
<div className={"row"}> <div className={"row"}>
<p>Production:</p> <p>Production:</p>
<span className={"text money-gold"}> <span className={"text money-gold"}>
<Money money={node.totalMoneyGenerated} player={props.player} /> ({MoneyRate(node.moneyGainRatePerSecond)}) <Money money={node.totalMoneyGenerated} player={props.player} /> (
<MoneyRate money={node.moneyGainRatePerSecond} />)
</span> </span>
</div> </div>
<div className={"row"}> <div className={"row"}>

@ -25,7 +25,7 @@ export function PlayerInfo(props: IProps): React.ReactElement {
if (hasServers) { if (hasServers) {
prod = HashRate(props.totalProduction); prod = HashRate(props.totalProduction);
} else { } else {
prod = MoneyRate(props.totalProduction); prod = <MoneyRate money={props.totalProduction} />;
} }
return ( return (

@ -30,4 +30,5 @@ export interface IEngine {
loadMissionContent: () => void; loadMissionContent: () => void;
loadResleevingContent: () => void; loadResleevingContent: () => void;
loadGameOptionsContent: () => void; loadGameOptionsContent: () => void;
load: (save: string) => void;
} }

@ -1,51 +0,0 @@
import { Page, routing } from ".././ui/navigationTracking";
import { Root } from "./ui/Root";
import { IPlayer } from "../PersonObjects/IPlayer";
import { IEngine } from "../IEngine";
import * as React from "react";
import * as ReactDOM from "react-dom";
let container: HTMLElement;
(function () {
function setContainer(): void {
const c = document.getElementById("infiltration-container");
if (c === null) throw new Error("huh?");
container = c;
document.removeEventListener("DOMContentLoaded", setContainer);
}
document.addEventListener("DOMContentLoaded", setContainer);
})();
function calcDifficulty(player: IPlayer, startingDifficulty: number): number {
const totalStats = player.strength + player.defense + player.dexterity + player.agility + player.charisma;
const difficulty = startingDifficulty - Math.pow(totalStats, 0.9) / 250 - player.intelligence / 1600;
if (difficulty < 0) return 0;
if (difficulty > 3) return 3;
return difficulty;
}
export function displayInfiltrationContent(
engine: IEngine,
player: IPlayer,
location: string,
startingDifficulty: number,
maxLevel: number,
): void {
if (!routing.isOn(Page.Infiltration)) return;
const difficulty = calcDifficulty(player, startingDifficulty);
ReactDOM.render(
<Root
Engine={engine}
Player={player}
Location={location}
StartingDifficulty={startingDifficulty}
Difficulty={difficulty}
MaxLevel={maxLevel}
/>,
container,
);
}

@ -1,5 +1,4 @@
import { IPlayer } from "../../PersonObjects/IPlayer"; import { use } from "../../ui/Context";
import { IEngine } from "../../IEngine";
import React, { useState } from "react"; import React, { useState } from "react";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import { Countdown } from "./Countdown"; import { Countdown } from "./Countdown";
@ -14,8 +13,6 @@ import { WireCuttingGame } from "./WireCuttingGame";
import { Victory } from "./Victory"; import { Victory } from "./Victory";
interface IProps { interface IProps {
Player: IPlayer;
Engine: IEngine;
StartingDifficulty: number; StartingDifficulty: number;
Difficulty: number; Difficulty: number;
MaxLevel: number; MaxLevel: number;
@ -40,6 +37,8 @@ const minigames = [
]; ];
export function Game(props: IProps): React.ReactElement { export function Game(props: IProps): React.ReactElement {
const player = use.Player();
const router = use.Router();
const [level, setLevel] = useState(1); const [level, setLevel] = useState(1);
const [stage, setStage] = useState(Stage.Countdown); const [stage, setStage] = useState(Stage.Countdown);
const [results, setResults] = useState(""); const [results, setResults] = useState("");
@ -89,12 +88,10 @@ export function Game(props: IProps): React.ReactElement {
pushResult(false); pushResult(false);
// Kill the player immediately if they use automation, so // Kill the player immediately if they use automation, so
// it's clear they're not meant to // it's clear they're not meant to
const damage = options?.automated ? props.Player.hp : props.StartingDifficulty * 3; const damage = options?.automated ? player.hp : props.StartingDifficulty * 3;
if (props.Player.takeDamage(damage)) { if (player.takeDamage(damage)) {
const menu = document.getElementById("mainmenu-container"); router.toCity();
if (menu === null) throw new Error("mainmenu-container not found"); return;
menu.style.visibility = "visible";
props.Engine.loadLocationContent();
} }
setupNextGame(); setupNextGame();
} }
@ -112,8 +109,6 @@ export function Game(props: IProps): React.ReactElement {
case Stage.Sell: case Stage.Sell:
stageComponent = ( stageComponent = (
<Victory <Victory
Player={props.Player}
Engine={props.Engine}
StartingDifficulty={props.StartingDifficulty} StartingDifficulty={props.StartingDifficulty}
Difficulty={props.Difficulty} Difficulty={props.Difficulty}
MaxLevel={props.MaxLevel} MaxLevel={props.MaxLevel}

@ -0,0 +1,51 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import React, { useState } from "react";
import { Intro } from "./Intro";
import { Game } from "./Game";
import { Location } from "../../Locations/Location";
import { use } from "../../ui/Context";
interface IProps {
location: Location;
}
function calcDifficulty(player: IPlayer, startingDifficulty: number): number {
const totalStats = player.strength + player.defense + player.dexterity + player.agility + player.charisma;
const difficulty = startingDifficulty - Math.pow(totalStats, 0.9) / 250 - player.intelligence / 1600;
if (difficulty < 0) return 0;
if (difficulty > 3) return 3;
return difficulty;
}
export function InfiltrationRoot(props: IProps): React.ReactElement {
const player = use.Player();
const router = use.Router();
const [start, setStart] = useState(false);
if (props.location.infiltrationData === undefined) throw new Error("Trying to do infiltration on invalid location.");
const startingDifficulty = props.location.infiltrationData.startingSecurityLevel;
const difficulty = calcDifficulty(player, startingDifficulty);
function cancel(): void {
router.toCity();
}
if (!start) {
return (
<Intro
Location={props.location}
Difficulty={difficulty}
MaxLevel={props.location.infiltrationData.maxClearanceLevel}
start={() => setStart(true)}
cancel={cancel}
/>
);
}
return (
<Game
StartingDifficulty={startingDifficulty}
Difficulty={difficulty}
MaxLevel={props.location.infiltrationData.maxClearanceLevel}
/>
);
}

@ -1,13 +1,10 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import React from "react"; import React from "react";
import { StdButton } from "../../ui/React/StdButton"; import { StdButton } from "../../ui/React/StdButton";
import { Location } from "../../Locations/Location";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
interface IProps { interface IProps {
Player: IPlayer; Location: Location;
Engine: IEngine;
Location: string;
Difficulty: number; Difficulty: number;
MaxLevel: number; MaxLevel: number;
start: () => void; start: () => void;
@ -51,11 +48,12 @@ function coloredArrow(difficulty: number): JSX.Element {
} }
export function Intro(props: IProps): React.ReactElement { export function Intro(props: IProps): React.ReactElement {
console.log(props);
return ( return (
<> <>
<Grid container spacing={3}> <Grid container spacing={3}>
<Grid item xs={10}> <Grid item xs={10}>
<h1>Infiltrating {props.Location}</h1> <h1>Infiltrating {props.Location.name}</h1>
</Grid> </Grid>
<Grid item xs={10}> <Grid item xs={10}>
<h2>Maximum level: {props.MaxLevel}</h2> <h2>Maximum level: {props.MaxLevel}</h2>

@ -1,49 +0,0 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import React, { useState } from "react";
import { Intro } from "./Intro";
import { Game } from "./Game";
interface IProps {
Player: IPlayer;
Engine: IEngine;
Location: string;
StartingDifficulty: number;
Difficulty: number;
MaxLevel: number;
}
export function Root(props: IProps): React.ReactElement {
const [start, setStart] = useState(false);
function cancel(): void {
const menu = document.getElementById("mainmenu-container");
if (menu === null) throw new Error("mainmenu-container not found");
menu.style.visibility = "visible";
props.Engine.loadLocationContent();
}
if (!start) {
return (
<Intro
Player={props.Player}
Engine={props.Engine}
Location={props.Location}
Difficulty={props.Difficulty}
MaxLevel={props.MaxLevel}
start={() => setStart(true)}
cancel={cancel}
/>
);
}
return (
<Game
Player={props.Player}
Engine={props.Engine}
StartingDifficulty={props.StartingDifficulty}
Difficulty={props.Difficulty}
MaxLevel={props.MaxLevel}
/>
);
}

@ -1,5 +1,3 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import { Factions } from "../../Faction/Factions"; import { Factions } from "../../Faction/Factions";
import React, { useState } from "react"; import React, { useState } from "react";
import { StdButton } from "../../ui/React/StdButton"; import { StdButton } from "../../ui/React/StdButton";
@ -7,23 +5,21 @@ import Grid from "@mui/material/Grid";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { Reputation } from "../../ui/React/Reputation"; import { Reputation } from "../../ui/React/Reputation";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers"; import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { use } from "../../ui/Context";
interface IProps { interface IProps {
Player: IPlayer;
Engine: IEngine;
StartingDifficulty: number; StartingDifficulty: number;
Difficulty: number; Difficulty: number;
MaxLevel: number; MaxLevel: number;
} }
export function Victory(props: IProps): React.ReactElement { export function Victory(props: IProps): React.ReactElement {
const player = use.Player();
const router = use.Router();
const [faction, setFaction] = useState("none"); const [faction, setFaction] = useState("none");
function quitInfiltration(): void { function quitInfiltration(): void {
const menu = document.getElementById("mainmenu-container"); router.toCity();
if (!menu) throw new Error("mainmenu-container somehow null");
menu.style.visibility = "visible";
props.Engine.loadLocationContent();
} }
const levelBonus = props.MaxLevel * Math.pow(1.01, props.MaxLevel); const levelBonus = props.MaxLevel * Math.pow(1.01, props.MaxLevel);
@ -43,8 +39,8 @@ export function Victory(props: IProps): React.ReactElement {
BitNodeMultipliers.InfiltrationMoney; BitNodeMultipliers.InfiltrationMoney;
function sell(): void { function sell(): void {
props.Player.gainMoney(moneyGain); player.gainMoney(moneyGain);
props.Player.recordMoneySource(moneyGain, "infiltration"); player.recordMoneySource(moneyGain, "infiltration");
quitInfiltration(); quitInfiltration();
} }
@ -70,7 +66,7 @@ export function Victory(props: IProps): React.ReactElement {
<option key={"none"} value={"none"}> <option key={"none"} value={"none"}>
{"none"} {"none"}
</option> </option>
{props.Player.factions {player.factions
.filter((f) => Factions[f].getInfo().offersWork()) .filter((f) => Factions[f].getInfo().offersWork())
.map((f) => ( .map((f) => (
<option key={f} value={f}> <option key={f} value={f}>

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

@ -1,494 +0,0 @@
import { Engine } from "./engine";
import { Player } from "./Player";
import { Settings } from "./Settings/Settings";
import { LiteratureNames } from "./Literature/data/LiteratureNames";
import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners";
import { createElement } from "../utils/uiHelpers/createElement";
import { createPopup } from "../utils/uiHelpers/createPopup";
import { removeElementById } from "../utils/uiHelpers/removeElementById";
// Ordered array of keys to Interactive Tutorial Steps
const orderedITutorialSteps = [
"Start",
"GoToCharacterPage", // Click on 'Stats' page
"CharacterPage", // Introduction to 'Stats' page
"CharacterGoToTerminalPage", // Go back to Terminal
"TerminalIntro", // Introduction to Terminal
"TerminalHelp", // Using 'help' Terminal command
"TerminalLs", // Using 'ls' Terminal command
"TerminalScan", // Using 'scan' Terminal command
"TerminalScanAnalyze1", // Using 'scan-analyze' Terminal command
"TerminalScanAnalyze2", // Using 'scan-analyze 3' Terminal command
"TerminalConnect", // Connecting to n00dles
"TerminalAnalyze", // Analyzing n00dles
"TerminalNuke", // NUKE n00dles
"TerminalManualHack", // Hack n00dles
"TerminalHackingMechanics", // Explanation of hacking mechanics
"TerminalCreateScript", // Create a script using 'nano'
"TerminalTypeScript", // Script Editor page - Type script and then save & close
"TerminalFree", // Using 'Free' Terminal command
"TerminalRunScript", // Running script using 'run' Terminal command
"TerminalGoToActiveScriptsPage",
"ActiveScriptsPage",
"ActiveScriptsToTerminal",
"TerminalTailScript",
"GoToHacknetNodesPage",
"HacknetNodesIntroduction",
"HacknetNodesGoToWorldPage",
"WorldDescription",
"TutorialPageInfo",
"End",
];
// Create an 'enum' for the Steps
const iTutorialSteps = {};
for (let i = 0; i < orderedITutorialSteps.length; ++i) {
iTutorialSteps[orderedITutorialSteps[i]] = i;
}
const ITutorial = {
currStep: 0, // iTutorialSteps.Start
isRunning: false,
// Keeps track of whether each step has been done
stepIsDone: {},
};
function iTutorialStart() {
// Initialize Interactive Tutorial state by settings 'done' for each state to false
ITutorial.stepIsDone = {};
for (let i = 0; i < orderedITutorialSteps.length; ++i) {
ITutorial.stepIsDone[i] = false;
}
Engine.loadTerminalContent();
// Don't autosave during this interactive tutorial
Engine.Counters.autoSaveCounter = Infinity;
ITutorial.currStep = 0;
ITutorial.isRunning = true;
document.getElementById("interactive-tutorial-container").style.display = "block";
// Exit tutorial button
const exitButton = clearEventListeners("interactive-tutorial-exit");
exitButton.addEventListener("click", function () {
iTutorialEnd();
return false;
});
// Back button
const backButton = clearEventListeners("interactive-tutorial-back");
backButton.addEventListener("click", function () {
iTutorialPrevStep();
return false;
});
// Next button
const nextButton = clearEventListeners("interactive-tutorial-next");
nextButton.addEventListener("click", function () {
iTutorialNextStep();
return false;
});
iTutorialEvaluateStep();
}
function iTutorialEvaluateStep() {
if (!ITutorial.isRunning) {
return;
}
// Disable and clear main menu
// const terminalMainMenu = clearEventListeners("terminal-menu-link");
// const statsMainMenu = clearEventListeners("stats-menu-link");
// const activeScriptsMainMenu = clearEventListeners("active-scripts-menu-link");
// const hacknetMainMenu = clearEventListeners("hacknet-nodes-menu-link");
// const cityMainMenu = clearEventListeners("city-menu-link");
// const tutorialMainMenu = clearEventListeners("tutorial-menu-link");
// terminalMainMenu.removeAttribute("class");
// statsMainMenu.removeAttribute("class");
// activeScriptsMainMenu.removeAttribute("class");
// hacknetMainMenu.removeAttribute("class");
// cityMainMenu.removeAttribute("class");
// tutorialMainMenu.removeAttribute("class");
// Interactive Tutorial Next button
const nextBtn = document.getElementById("interactive-tutorial-next");
switch (ITutorial.currStep) {
case iTutorialSteps.Start:
Engine.loadTerminalContent();
iTutorialSetText(
"Welcome to Bitburner, a cyberpunk-themed incremental RPG! " +
"The game takes place in a dark, dystopian future... The year is 2077...<br><br>" +
"This tutorial will show you the basics of the game. " +
"You may skip the tutorial at any time.",
);
nextBtn.style.display = "inline-block";
break;
case iTutorialSteps.GoToCharacterPage:
Engine.loadTerminalContent();
iTutorialSetText(
"Let's start by heading to the Stats page. Click the <code class='interactive-tutorial-tab flashing-button'>Stats</code> tab on " +
"the main navigation menu (left-hand side of the screen)",
);
nextBtn.style.display = "none";
break;
case iTutorialSteps.CharacterPage:
Engine.loadCharacterContent();
iTutorialSetText(
"The <code class='interactive-tutorial-tab'>Stats</code> page shows a lot of important information about your progress, " +
"such as your skills, money, and bonuses. ",
);
nextBtn.style.display = "inline-block";
break;
case iTutorialSteps.CharacterGoToTerminalPage:
Engine.loadCharacterContent();
iTutorialSetText(
"Let's head to your computer's terminal by clicking the <code class='interactive-tutorial-tab flashing-button'>Terminal</code> tab on the " +
"main navigation menu.",
);
nextBtn.style.display = "none";
break;
case iTutorialSteps.TerminalIntro:
Engine.loadTerminalContent();
iTutorialSetText(
"The <code class='interactive-tutorial-tab'>Terminal</code> is used to interface with your home computer as well as " +
"all of the other machines around the world.",
);
nextBtn.style.display = "inline-block";
break;
case iTutorialSteps.TerminalHelp:
Engine.loadTerminalContent();
iTutorialSetText(
"Let's try it out. Start by entering the <code class='interactive-tutorial-command'>help</code> command into the <code class='interactive-tutorial-tab'>Terminal</code> " +
"(Don't forget to press Enter after typing the command)",
);
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalLs:
Engine.loadTerminalContent();
iTutorialSetText(
"The <code class='interactive-tutorial-command'>help</code> command displays a list of all available <code class='interactive-tutorial-tab'>Terminal</code> commands, how to use them, " +
"and a description of what they do. <br><br>Let's try another command. Enter the <code class='interactive-tutorial-command'>ls</code> command.",
);
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalScan:
Engine.loadTerminalContent();
iTutorialSetText(
" <code class='interactive-tutorial-command'>ls</code> is a basic command that shows files " +
"on the computer. Right now, it shows that you have a program called <code class='interactive-tutorial-command'>NUKE.exe</code> on your computer. " +
"We'll get to what this does later. <br><br>Using your home computer's terminal, you can connect " +
"to other machines throughout the world. Let's do that now by first entering " +
"the <code class='interactive-tutorial-command'>scan</code> command.",
);
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalScanAnalyze1:
Engine.loadTerminalContent();
iTutorialSetText(
"The <code class='interactive-tutorial-command'>scan</code> command shows all available network connections. In other words, " +
"it displays a list of all servers that can be connected to from your " +
"current machine. A server is identified by its hostname. <br><br> " +
"That's great and all, but there's so many servers. Which one should you go to? " +
"The <code class='interactive-tutorial-command'>scan-analyze</code> command gives some more detailed information about servers on the " +
"network. Try it now!",
);
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalScanAnalyze2:
Engine.loadTerminalContent();
iTutorialSetText(
"You just ran <code class='interactive-tutorial-command'>scan-analyze</code> with a depth of one. This command shows more detailed " +
"information about each server that you can connect to (servers that are a distance of " +
"one node away). <br><br> It is also possible to run <code class='interactive-tutorial-command'>scan-analyze</code> with " +
"a higher depth. Let's try a depth of two with the following command: <code class='interactive-tutorial-command'>scan-analyze 2</code>.",
);
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalConnect:
Engine.loadTerminalContent();
iTutorialSetText(
"Now you can see information about all servers that are up to two nodes away, as well " +
"as figure out how to navigate to those servers through the network. You can only connect to " +
"a server that is one node away. To connect to a machine, use the <code class='interactive-tutorial-command'>connect [hostname]</code> command.<br><br>" +
"From the results of the <code class='interactive-tutorial-command'>scan-analyze</code> command, we can see that the <code class='interactive-tutorial-command'>n00dles</code> server is " +
"only one node away. Let's connect so it now using: <code class='interactive-tutorial-command'>connect n00dles</code>",
);
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalAnalyze:
Engine.loadTerminalContent();
iTutorialSetText(
"You are now connected to another machine! What can you do now? You can hack it!<br><br> In the year 2077, currency has " +
"become digital and decentralized. People and corporations store their money " +
"on servers and computers. Using your hacking abilities, you can hack servers " +
"to steal money and gain experience. <br><br> " +
"Before you try to hack a server, you should run diagnostics using the <code class='interactive-tutorial-command'>analyze</code> command.",
);
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalNuke:
Engine.loadTerminalContent();
iTutorialSetText(
"When the <code class='interactive-tutorial-command'>analyze</code> command finishes running it will show useful information " +
"about hacking the server. <br><br> For this server, the required hacking skill is only <span class='character-hack-cell'>1</span>, " +
"which means you can hack it right now. However, in order to hack a server " +
"you must first gain root access. The <code class='interactive-tutorial-command'>NUKE.exe</code> program that we saw earlier on your " +
"home computer is a virus that will grant you root access to a machine if there are enough " +
"open ports.<br><br> The <code class='interactive-tutorial-command'>analyze</code> results shows that there do not need to be any open ports " +
"on this machine for the NUKE virus to work, so go ahead and run the virus using the " +
"<code class='interactive-tutorial-command'>run NUKE.exe</code> command.",
);
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalManualHack:
Engine.loadTerminalContent();
iTutorialSetText(
"You now have root access! You can hack the server using the <code class='interactive-tutorial-command'>hack</code> command. " +
"Try doing that now.",
);
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalHackingMechanics:
Engine.loadTerminalContent();
iTutorialSetText(
"You are now attempting to hack the server. Performing a hack takes time and " +
"only has a certain percentage chance " +
"of success. This time and success chance is determined by a variety of factors, including " +
"your hacking skill and the server's security level.<br><br>" +
"If your attempt to hack the server is successful, you will steal a certain percentage " +
"of the server's total money. This percentage is affected by your hacking skill and " +
"the server's security level.<br><br>The amount of money on a server is not limitless. So, if " +
"you constantly hack a server and deplete its money, then you will encounter " +
"diminishing returns in your hacking.",
);
nextBtn.style.display = "inline-block";
break;
case iTutorialSteps.TerminalCreateScript:
Engine.loadTerminalContent();
iTutorialSetText(
"Hacking is the core mechanic of the game and is necessary for progressing. However, " +
"you don't want to be hacking manually the entire time. You can automate your hacking " +
"by writing scripts!<br><br>To create a new script or edit an existing one, you can use the <code class='interactive-tutorial-command'>nano</code> " +
"command. Scripts must end with the <code class='interactive-tutorial-command'>.script</code> extension. Let's make a script now by " +
"entering <code class='interactive-tutorial-command'>nano n00dles.script</code> after the hack command finishes running (Sidenote: Pressing ctrl + c" +
" will end a command like hack early)",
);
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalTypeScript:
Engine.loadScriptEditorContent("n00dles.script", "");
iTutorialSetText(
"This is the script editor. You can use it to program your scripts. Scripts are " +
"written in a simplified version of javascript. Copy and paste the following code into the script editor: <br><br>" +
"<pre class='interactive-tutorial-code'>" +
"while(true) {\n" +
" hack('n00dles');\n" +
"}</pre>" +
"For anyone with basic programming experience, this code should be straightforward. " +
"This script will continuously hack the <code class='interactive-tutorial-command'>n00dles</code> server.<br><br>" +
"To save and close the script editor, press the button in the bottom left, or press ctrl + b.",
);
nextBtn.style.display = "none"; // next step triggered in saveAndCloseScriptEditor() (Script.js)
break;
case iTutorialSteps.TerminalFree:
Engine.loadTerminalContent();
iTutorialSetText(
"Now we'll run the script. Scripts require a certain amount of RAM to run, and can be " +
"run on any machine which you have root access to. Different servers have different " +
"amounts of RAM. You can also purchase more RAM for your home server.<br><br>To check how much " +
"RAM is available on this machine, enter the <code class='interactive-tutorial-command'>free</code> command.",
);
nextBtn.style.display = "none"; // next step triggered by terminal commmand
break;
case iTutorialSteps.TerminalRunScript:
Engine.loadTerminalContent();
iTutorialSetText(
"We have 4GB of free RAM on this machine, which is enough to run our " +
"script. Let's run our script using <code class='interactive-tutorial-command'>run n00dles.script</code>.",
);
nextBtn.style.display = "none"; // next step triggered by terminal commmand
break;
case iTutorialSteps.TerminalGoToActiveScriptsPage:
Engine.loadTerminalContent();
iTutorialSetText(
"Your script is now running! " +
"It will continuously run in the background and will automatically stop if " +
"the code ever completes (the <code class='interactive-tutorial-command'>n00dles.script</code> will never complete because it " +
"runs an infinite loop). <br><br>These scripts can passively earn you income and hacking experience. " +
"Your scripts will also earn money and experience while you are offline, although at a " +
"slightly slower rate. <br><br> " +
"Let's check out some statistics for our running scripts by clicking the " +
"<code class='interactive-tutorial-tab flashing-button'>Active Scripts</code> link in the main navigation menu.",
);
nextBtn.style.display = "none";
break;
case iTutorialSteps.ActiveScriptsPage:
Engine.loadActiveScriptsContent();
iTutorialSetText(
"This page displays information about all of your scripts that are " +
"running across every server. You can use this to gauge how well " +
"your scripts are doing. Let's go back to the <code class='interactive-tutorial-tab flashing-button'>Terminal</code>",
);
nextBtn.style.display = "none";
break;
case iTutorialSteps.ActiveScriptsToTerminal:
Engine.loadTerminalContent();
iTutorialSetText(
"One last thing about scripts, each active script contains logs that detail " +
"what it's doing. We can check these logs using the <code class='interactive-tutorial-command'>tail</code> command. Do that " +
"now for the script we just ran by typing <code class='interactive-tutorial-command'>tail n00dles.script</code>",
);
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalTailScript:
Engine.loadTerminalContent();
iTutorialSetText(
"The log for this script won't show much right now (it might show nothing at all) because it " +
"just started running...but check back again in a few minutes! <br><br>" +
"This covers the basics of hacking. To learn more about writing " +
"scripts, select the <code class='interactive-tutorial-tab'>Tutorial</code> link in the " +
"main navigation menu to look at the documentation. " +
"<strong style='background-color:#444;'>If you are an experienced JavaScript " +
"developer, I would highly suggest you check out the section on " +
"NetscriptJS/Netscript 2.0, it's faster and more powerful.</strong><br><br>For now, let's move on to something else!",
);
nextBtn.style.display = "inline-block";
break;
case iTutorialSteps.GoToHacknetNodesPage:
Engine.loadTerminalContent();
iTutorialSetText(
"Hacking is not the only way to earn money. One other way to passively " +
"earn money is by purchasing and upgrading Hacknet Nodes. Let's go to " +
"the <code class='interactive-tutorial-tab flashing-button'>Hacknet</code> page through the main navigation menu now.",
);
nextBtn.style.display = "none";
break;
case iTutorialSteps.HacknetNodesIntroduction:
Engine.loadHacknetNodesContent();
iTutorialSetText(
"here you can purchase new Hacknet Nodes and upgrade your " + "existing ones. Let's purchase a new one now.",
);
nextBtn.style.display = "none"; // Next step triggered by purchaseHacknet() (HacknetNode.js)
break;
case iTutorialSteps.HacknetNodesGoToWorldPage:
Engine.loadHacknetNodesContent();
iTutorialSetText(
"You just purchased a Hacknet Node! This Hacknet Node will passively " +
"earn you money over time, both online and offline. When you get enough " +
" money, you can upgrade " +
"your newly-purchased Hacknet Node below.<br><br>" +
"Let's go to the <code class='interactive-tutorial-tab flashing-button'>City</code> page through the main navigation menu.",
);
nextBtn.style.display = "none";
break;
case iTutorialSteps.WorldDescription:
Engine.loadLocationContent();
iTutorialSetText(
"This page lists all of the different locations you can currently " +
"travel to. Each location has something that you can do. " +
"There's a lot of content out in the world, make sure " +
"you explore and discover!<br><br>" +
"Lastly, click on the <code class='interactive-tutorial-tab flashing-button'>Tutorial</code> link in the main navigation menu.",
);
nextBtn.style.display = "none";
break;
case iTutorialSteps.TutorialPageInfo:
Engine.loadTutorialContent();
iTutorialSetText(
"This page contains a lot of different documentation about the game's " +
"content and mechanics. <strong style='background-color:#444;'> I know it's a lot, but I highly suggest you read " +
"(or at least skim) through this before you start playing</strong>. That's the end of the tutorial. " +
"Hope you enjoy the game!",
);
nextBtn.style.display = "inline-block";
nextBtn.innerHTML = "Finish Tutorial";
break;
case iTutorialSteps.End:
iTutorialEnd();
break;
default:
throw new Error("Invalid tutorial step");
}
if (ITutorial.stepIsDone[ITutorial.currStep] === true) {
nextBtn.style.display = "inline-block";
}
}
// Go to the next step and evaluate it
function iTutorialNextStep() {
ITutorial.stepIsDone[ITutorial.currStep] = true;
if (ITutorial.currStep < iTutorialSteps.End) {
ITutorial.currStep += 1;
}
iTutorialEvaluateStep();
}
// Go to previous step and evaluate
function iTutorialPrevStep() {
if (ITutorial.currStep > iTutorialSteps.Start) {
ITutorial.currStep -= 1;
}
iTutorialEvaluateStep();
}
function iTutorialEnd() {
// Re-enable auto save
if (Settings.AutosaveInterval === 0) {
Engine.Counters.autoSaveCounter = Infinity;
} else {
Engine.Counters.autoSaveCounter = Settings.AutosaveInterval * 5;
}
Engine.init();
ITutorial.currStep = iTutorialSteps.End;
ITutorial.isRunning = false;
document.getElementById("interactive-tutorial-container").style.display = "none";
// Create a popup with final introductory stuff
const popupId = "interactive-tutorial-ending-popup";
const txt = createElement("p", {
innerHTML:
"If you are new to the game, the following links may be useful for you!<br><br>" +
"<a class='a-link-button' href='https://bitburner.readthedocs.io/en/latest/guidesandtips/gettingstartedguideforbeginnerprogrammers.html' target='_blank'>Getting Started Guide</a>" +
"<a class='a-link-button' href='https://bitburner.readthedocs.io/en/latest/' target='_blank'>Documentation</a><br><br>" +
"The Beginner's Guide to Hacking was added to your home computer! It contains some tips/pointers for starting out with the game. " +
"To read it, go to Terminal and enter<br><br>cat " +
LiteratureNames.HackersStartingHandbook,
});
const gotitBtn = createElement("a", {
class: "a-link-button",
float: "right",
padding: "6px",
innerText: "Got it!",
clickListener: () => {
removeElementById(popupId);
},
});
createPopup(popupId, [txt, gotitBtn]);
Player.getHomeComputer().messages.push(LiteratureNames.HackersStartingHandbook);
}
let textBox = null;
(function () {
function set() {
textBox = document.getElementById("interactive-tutorial-text");
document.removeEventListener("DOMContentLoaded", set);
}
document.addEventListener("DOMContentLoaded", set);
})();
function iTutorialSetText(txt) {
textBox.innerHTML = txt;
textBox.parentElement.scrollTop = 0; // this resets scroll position
}
export { iTutorialSteps, iTutorialEnd, iTutorialStart, iTutorialNextStep, ITutorial };

170
src/InteractiveTutorial.ts Normal file

@ -0,0 +1,170 @@
import { Player } from "./Player";
import { LiteratureNames } from "./Literature/data/LiteratureNames";
import { ITutorialEvents } from "./ui/InteractiveTutorial/ITutorialEvents";
import { createElement } from "../utils/uiHelpers/createElement";
import { createPopup } from "../utils/uiHelpers/createPopup";
import { removeElementById } from "../utils/uiHelpers/removeElementById";
// Ordered array of keys to Interactive Tutorial Steps
enum iTutorialSteps {
Start,
GoToCharacterPage, // Click on 'Stats' page
CharacterPage, // Introduction to 'Stats' page
CharacterGoToTerminalPage, // Go back to Terminal
TerminalIntro, // Introduction to Terminal
TerminalHelp, // Using 'help' Terminal command
TerminalLs, // Using 'ls' Terminal command
TerminalScan, // Using 'scan' Terminal command
TerminalScanAnalyze1, // Using 'scan-analyze' Terminal command
TerminalScanAnalyze2, // Using 'scan-analyze 3' Terminal command
TerminalConnect, // Connecting to n00dles
TerminalAnalyze, // Analyzing n00dles
TerminalNuke, // NUKE n00dles
TerminalManualHack, // Hack n00dles
TerminalHackingMechanics, // Explanation of hacking mechanics
TerminalGoHome, // Go home before creating a script.
TerminalCreateScript, // Create a script using 'nano'
TerminalTypeScript, // Script Editor page - Type script and then save & close
TerminalFree, // Using 'Free' Terminal command
TerminalRunScript, // Running script using 'run' Terminal command
TerminalGoToActiveScriptsPage,
ActiveScriptsPage,
ActiveScriptsToTerminal,
TerminalTailScript,
GoToHacknetNodesPage,
HacknetNodesIntroduction,
HacknetNodesGoToWorldPage,
WorldDescription,
TutorialPageInfo,
End,
}
const ITutorial: {
currStep: iTutorialSteps;
isRunning: boolean;
stepIsDone: {
[iTutorialSteps.Start]: boolean;
[iTutorialSteps.GoToCharacterPage]: boolean;
[iTutorialSteps.CharacterPage]: boolean;
[iTutorialSteps.CharacterGoToTerminalPage]: boolean;
[iTutorialSteps.TerminalIntro]: boolean;
[iTutorialSteps.TerminalHelp]: boolean;
[iTutorialSteps.TerminalLs]: boolean;
[iTutorialSteps.TerminalScan]: boolean;
[iTutorialSteps.TerminalScanAnalyze1]: boolean;
[iTutorialSteps.TerminalScanAnalyze2]: boolean;
[iTutorialSteps.TerminalConnect]: boolean;
[iTutorialSteps.TerminalAnalyze]: boolean;
[iTutorialSteps.TerminalNuke]: boolean;
[iTutorialSteps.TerminalManualHack]: boolean;
[iTutorialSteps.TerminalHackingMechanics]: boolean;
[iTutorialSteps.TerminalGoHome]: boolean;
[iTutorialSteps.TerminalCreateScript]: boolean;
[iTutorialSteps.TerminalTypeScript]: boolean;
[iTutorialSteps.TerminalFree]: boolean;
[iTutorialSteps.TerminalRunScript]: boolean;
[iTutorialSteps.TerminalGoToActiveScriptsPage]: boolean;
[iTutorialSteps.ActiveScriptsPage]: boolean;
[iTutorialSteps.ActiveScriptsToTerminal]: boolean;
[iTutorialSteps.TerminalTailScript]: boolean;
[iTutorialSteps.GoToHacknetNodesPage]: boolean;
[iTutorialSteps.HacknetNodesIntroduction]: boolean;
[iTutorialSteps.HacknetNodesGoToWorldPage]: boolean;
[iTutorialSteps.WorldDescription]: boolean;
[iTutorialSteps.TutorialPageInfo]: boolean;
[iTutorialSteps.End]: boolean;
};
} = {
currStep: iTutorialSteps.Start,
isRunning: false,
// Keeps track of whether each step has been done
stepIsDone: {
[iTutorialSteps.Start]: false,
[iTutorialSteps.GoToCharacterPage]: false,
[iTutorialSteps.CharacterPage]: false,
[iTutorialSteps.CharacterGoToTerminalPage]: false,
[iTutorialSteps.TerminalIntro]: false,
[iTutorialSteps.TerminalHelp]: false,
[iTutorialSteps.TerminalLs]: false,
[iTutorialSteps.TerminalScan]: false,
[iTutorialSteps.TerminalScanAnalyze1]: false,
[iTutorialSteps.TerminalScanAnalyze2]: false,
[iTutorialSteps.TerminalConnect]: false,
[iTutorialSteps.TerminalAnalyze]: false,
[iTutorialSteps.TerminalNuke]: false,
[iTutorialSteps.TerminalManualHack]: false,
[iTutorialSteps.TerminalHackingMechanics]: false,
[iTutorialSteps.TerminalGoHome]: false,
[iTutorialSteps.TerminalCreateScript]: false,
[iTutorialSteps.TerminalTypeScript]: false,
[iTutorialSteps.TerminalFree]: false,
[iTutorialSteps.TerminalRunScript]: false,
[iTutorialSteps.TerminalGoToActiveScriptsPage]: false,
[iTutorialSteps.ActiveScriptsPage]: false,
[iTutorialSteps.ActiveScriptsToTerminal]: false,
[iTutorialSteps.TerminalTailScript]: false,
[iTutorialSteps.GoToHacknetNodesPage]: false,
[iTutorialSteps.HacknetNodesIntroduction]: false,
[iTutorialSteps.HacknetNodesGoToWorldPage]: false,
[iTutorialSteps.WorldDescription]: false,
[iTutorialSteps.TutorialPageInfo]: false,
[iTutorialSteps.End]: false,
},
};
function iTutorialStart(): void {
ITutorial.isRunning = true;
}
// Go to the next step and evaluate it
function iTutorialNextStep(): void {
ITutorial.stepIsDone[ITutorial.currStep] = true;
if (ITutorial.currStep < iTutorialSteps.End) {
ITutorial.currStep += 1;
}
if (ITutorial.currStep === iTutorialSteps.End) iTutorialEnd();
ITutorialEvents.emit();
}
// Go to previous step and evaluate
function iTutorialPrevStep(): void {
if (ITutorial.currStep > iTutorialSteps.Start) {
ITutorial.currStep -= 1;
}
ITutorialEvents.emit();
}
function iTutorialEnd(): void {
ITutorial.isRunning = false;
// Create a popup with final introductory stuff
const popupId = "interactive-tutorial-ending-popup";
const txt = createElement("p", {
innerHTML:
"If you are new to the game, the following links may be useful for you!<br><br>" +
"<a class='a-link-button' href='https://bitburner.readthedocs.io/en/latest/guidesandtips/gettingstartedguideforbeginnerprogrammers.html' target='_blank'>Getting Started Guide</a>" +
"<a class='a-link-button' href='https://bitburner.readthedocs.io/en/latest/' target='_blank'>Documentation</a><br><br>" +
"The Beginner's Guide to Hacking was added to your home computer! It contains some tips/pointers for starting out with the game. " +
"To read it, go to Terminal and enter<br><br>cat " +
LiteratureNames.HackersStartingHandbook,
});
const gotitBtn = createElement("a", {
class: "a-link-button",
float: "right",
padding: "6px",
innerText: "Got it!",
clickListener: () => {
removeElementById(popupId);
},
});
createPopup(popupId, [txt, gotitBtn]);
Player.getHomeComputer().messages.push(LiteratureNames.HackersStartingHandbook);
ITutorialEvents.emit();
}
export { iTutorialSteps, iTutorialEnd, iTutorialStart, iTutorialNextStep, ITutorial, iTutorialPrevStep };

@ -6,114 +6,135 @@
import * as React from "react"; import * as React from "react";
import { City } from "../City"; import { City } from "../City";
import { Cities } from "../Cities";
import { LocationName } from "../data/LocationNames"; import { LocationName } from "../data/LocationNames";
import { Locations } from "../Locations";
import { Location } from "../Location";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { StdButton } from "../../ui/React/StdButton"; import { StdButton } from "../../ui/React/StdButton";
import { use } from "../../ui/Context";
import { IRouter } from "../../ui/Router";
type IProps = { type IProps = {
city: City; city: City;
enterLocation: (to: LocationName) => void;
}; };
export class LocationCity extends React.Component<IProps, any> { function toLocation(router: IRouter, location: Location): void {
asciiCity(): React.ReactNode { if (location.name === LocationName.TravelAgency) {
const LocationLetter = (location: LocationName): JSX.Element => { router.toTravel();
if (location) } else if (location.name === LocationName.WorldStockExchange) {
return ( router.toStockMarket();
<span } else {
key={location} router.toLocation(location);
className="tooltip"
style={{
color: "white",
whiteSpace: "nowrap",
margin: "0px",
padding: "0px",
cursor: "pointer",
}}
onClick={this.props.enterLocation.bind(this, location)}
>
<b>X</b>
</span>
);
return <span>*</span>;
};
const locationLettersRegex = /[A-Z]/g;
const letterMap: any = {
A: 0,
B: 1,
C: 2,
D: 3,
E: 4,
F: 5,
G: 6,
H: 7,
I: 8,
J: 9,
K: 10,
L: 11,
M: 12,
N: 13,
O: 14,
P: 15,
Q: 16,
R: 17,
S: 18,
T: 19,
U: 20,
V: 21,
W: 22,
X: 23,
Y: 24,
Z: 25,
};
const lineElems = (s: string): JSX.Element[] => {
const elems: any[] = [];
const matches: any[] = [];
let match: any;
while ((match = locationLettersRegex.exec(s)) !== null) {
matches.push(match);
}
if (matches.length === 0) {
elems.push(s);
return elems;
}
for (let i = 0; i < matches.length; i++) {
const startI = i === 0 ? 0 : matches[i - 1].index + 1;
const endI = matches[i].index;
elems.push(s.slice(startI, endI));
const locationI = letterMap[s[matches[i].index]];
elems.push(LocationLetter(this.props.city.locations[locationI]));
}
elems.push(s.slice(matches[matches.length - 1].index + 1));
return elems;
};
const elems: JSX.Element[] = [];
const lines = this.props.city.asciiArt.split("\n");
for (const i in lines) {
elems.push(<pre key={i}>{lineElems(lines[i])}</pre>);
}
return <div className="noselect">{elems}</div>;
}
listCity(): React.ReactNode {
const locationButtons = this.props.city.locations.map((locName) => {
return (
<li key={locName}>
<StdButton onClick={this.props.enterLocation.bind(this, locName)} text={locName} />
</li>
);
});
return <ul>{locationButtons}</ul>;
}
render(): React.ReactNode {
return <>{Settings.DisableASCIIArt ? this.listCity() : this.asciiCity()}</>;
} }
} }
function LocationLetter(location: Location): React.ReactElement {
const router = use.Router();
if (!location) return <span>*</span>;
return (
<span
aria-label={location.name}
key={location.name}
className="tooltip"
style={{
color: "white",
whiteSpace: "nowrap",
margin: "0px",
padding: "0px",
cursor: "pointer",
}}
onClick={() => toLocation(router, location)}
>
<b>X</b>
</span>
);
}
function ASCIICity(props: IProps): React.ReactElement {
const locationLettersRegex = /[A-Z]/g;
const letterMap: any = {
A: 0,
B: 1,
C: 2,
D: 3,
E: 4,
F: 5,
G: 6,
H: 7,
I: 8,
J: 9,
K: 10,
L: 11,
M: 12,
N: 13,
O: 14,
P: 15,
Q: 16,
R: 17,
S: 18,
T: 19,
U: 20,
V: 21,
W: 22,
X: 23,
Y: 24,
Z: 25,
};
const lineElems = (s: string): JSX.Element[] => {
const elems: any[] = [];
const matches: any[] = [];
let match: any;
while ((match = locationLettersRegex.exec(s)) !== null) {
matches.push(match);
}
if (matches.length === 0) {
elems.push(s);
return elems;
}
for (let i = 0; i < matches.length; i++) {
const startI = i === 0 ? 0 : matches[i - 1].index + 1;
const endI = matches[i].index;
elems.push(s.slice(startI, endI));
const locationI = letterMap[s[matches[i].index]];
elems.push(LocationLetter(Locations[props.city.locations[locationI]]));
}
elems.push(s.slice(matches[matches.length - 1].index + 1));
return elems;
};
const elems: JSX.Element[] = [];
const lines = props.city.asciiArt.split("\n");
for (const i in lines) {
elems.push(<pre key={i}>{lineElems(lines[i])}</pre>);
}
return <div className="noselect">{elems}</div>;
}
function ListCity(props: IProps): React.ReactElement {
const router = use.Router();
const locationButtons = props.city.locations.map((locName) => {
return (
<li key={locName}>
<StdButton onClick={() => toLocation(router, Locations[locName])} text={locName} />
</li>
);
});
return <ul>{locationButtons}</ul>;
}
export function LocationCity(): React.ReactElement {
const player = use.Player();
const city = Cities[player.city];
return (
<div className="noselect">
<h2>{city.name}</h2>
{Settings.DisableASCIIArt ? <ListCity city={city} /> : <ASCIICity city={city} />}
</div>
);
}

@ -3,418 +3,347 @@
* *
* This subcomponent renders all of the buttons for applying to jobs at a company * This subcomponent renders all of the buttons for applying to jobs at a company
*/ */
import * as React from "react"; import React, { useState } from "react";
import { ApplyToJobButton } from "./ApplyToJobButton"; import { ApplyToJobButton } from "./ApplyToJobButton";
import { Location } from "../Location";
import { Locations } from "../Locations"; import { Locations } from "../Locations";
import { LocationName } from "../data/LocationNames"; import { LocationName } from "../data/LocationNames";
import { IEngine } from "../../IEngine";
import { Companies } from "../../Company/Companies"; import { Companies } from "../../Company/Companies";
import { Company } from "../../Company/Company";
import { CompanyPosition } from "../../Company/CompanyPosition"; import { CompanyPosition } from "../../Company/CompanyPosition";
import { CompanyPositions } from "../../Company/CompanyPositions"; import { CompanyPositions } from "../../Company/CompanyPositions";
import * as posNames from "../../Company/data/companypositionnames"; import * as posNames from "../../Company/data/companypositionnames";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { StdButton } from "../../ui/React/StdButton"; import { StdButton } from "../../ui/React/StdButton";
import { Reputation } from "../../ui/React/Reputation"; import { Reputation } from "../../ui/React/Reputation";
import { Favor } from "../../ui/React/Favor"; import { Favor } from "../../ui/React/Favor";
import { createPopup } from "../../ui/React/createPopup"; import { createPopup } from "../../ui/React/createPopup";
import { use } from "../../ui/Context";
import { QuitJobPopup } from "../../Company/ui/QuitJobPopup"; import { QuitJobPopup } from "../../Company/ui/QuitJobPopup";
type IProps = { type IProps = {
engine: IEngine;
locName: LocationName; locName: LocationName;
p: IPlayer;
}; };
type IState = { export function CompanyLocation(props: IProps): React.ReactElement {
employedHere: boolean; const p = use.Player();
}; const router = use.Router();
const setRerender = useState(false)[1];
const blockStyleMarkup = { function rerender(): void {
display: "block", setRerender((old) => !old);
}; }
export class CompanyLocation extends React.Component<IProps, IState> {
/** /**
* We'll keep a reference to the Company that this component is being rendered for, * We'll keep a reference to the Company that this component is being rendered for,
* so we don't have to look it up every time * so we don't have to look it up every time
*/ */
company: Company; const company = Companies[props.locName];
if (company == null) throw new Error(`CompanyLocation component constructed with invalid company: ${props.locName}`);
/**
* Reference to the Location that this component is being rendered for
*/
const location = Locations[props.locName];
if (location == null) {
throw new Error(`CompanyLocation component constructed with invalid location: ${props.locName}`);
}
/**
* Name of company position that player holds, if applicable
*/
const jobTitle = p.jobs[props.locName] ? p.jobs[props.locName] : null;
/** /**
* CompanyPosition object for the job that the player holds at this company * CompanyPosition object for the job that the player holds at this company
* (if he has one) * (if he has one)
*/ */
companyPosition: CompanyPosition | null = null; const companyPosition = jobTitle ? CompanyPositions[jobTitle] : null;
/** p.location = props.locName;
* Stores button styling that sets them all to block display
*/
btnStyle: any;
/** function applyForAgentJob(e: React.MouseEvent<HTMLElement>): void {
* Reference to the Location that this component is being rendered for
*/
location: Location;
/**
* Name of company position that player holds, if applicable
*/
jobTitle: string | null = null;
constructor(props: IProps) {
super(props);
this.btnStyle = { display: "block" };
this.quit = this.quit.bind(this);
this.applyForAgentJob = this.applyForAgentJob.bind(this);
this.applyForBusinessConsultantJob = this.applyForBusinessConsultantJob.bind(this);
this.applyForBusinessJob = this.applyForBusinessJob.bind(this);
this.applyForEmployeeJob = this.applyForEmployeeJob.bind(this);
this.applyForItJob = this.applyForItJob.bind(this);
this.applyForPartTimeEmployeeJob = this.applyForPartTimeEmployeeJob.bind(this);
this.applyForPartTimeWaiterJob = this.applyForPartTimeWaiterJob.bind(this);
this.applyForSecurityJob = this.applyForSecurityJob.bind(this);
this.applyForSoftwareConsultantJob = this.applyForSoftwareConsultantJob.bind(this);
this.applyForSoftwareJob = this.applyForSoftwareJob.bind(this);
this.applyForWaiterJob = this.applyForWaiterJob.bind(this);
this.startInfiltration = this.startInfiltration.bind(this);
this.work = this.work.bind(this);
this.location = Locations[props.locName];
if (this.location == null) {
throw new Error(`CompanyLocation component constructed with invalid location: ${props.locName}`);
}
this.company = Companies[props.locName];
if (this.company == null) {
throw new Error(`CompanyLocation component constructed with invalid company: ${props.locName}`);
}
this.state = {
employedHere: false,
};
this.props.p.location = props.locName;
this.checkIfEmployedHere(false);
}
applyForAgentJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
} }
this.props.p.applyForAgentJob(); p.applyForAgentJob();
this.checkIfEmployedHere(true); rerender();
} }
applyForBusinessConsultantJob(e: React.MouseEvent<HTMLElement>): void { function applyForBusinessConsultantJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
} }
this.props.p.applyForBusinessConsultantJob(); p.applyForBusinessConsultantJob();
this.checkIfEmployedHere(true); rerender();
} }
applyForBusinessJob(e: React.MouseEvent<HTMLElement>): void { function applyForBusinessJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
} }
this.props.p.applyForBusinessJob(); p.applyForBusinessJob();
this.checkIfEmployedHere(true); rerender();
} }
applyForEmployeeJob(e: React.MouseEvent<HTMLElement>): void { function applyForEmployeeJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
} }
this.props.p.applyForEmployeeJob(); p.applyForEmployeeJob();
this.checkIfEmployedHere(true); rerender();
} }
applyForItJob(e: React.MouseEvent<HTMLElement>): void { function applyForItJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
} }
this.props.p.applyForItJob(); p.applyForItJob();
this.checkIfEmployedHere(true); rerender();
} }
applyForPartTimeEmployeeJob(e: React.MouseEvent<HTMLElement>): void { function applyForPartTimeEmployeeJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
} }
this.props.p.applyForPartTimeEmployeeJob(); p.applyForPartTimeEmployeeJob();
this.checkIfEmployedHere(true); rerender();
} }
applyForPartTimeWaiterJob(e: React.MouseEvent<HTMLElement>): void { function applyForPartTimeWaiterJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
} }
this.props.p.applyForPartTimeWaiterJob(); p.applyForPartTimeWaiterJob();
this.checkIfEmployedHere(true); rerender();
} }
applyForSecurityJob(e: React.MouseEvent<HTMLElement>): void { function applyForSecurityJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
} }
this.props.p.applyForSecurityJob(); p.applyForSecurityJob();
this.checkIfEmployedHere(true); rerender();
} }
applyForSoftwareConsultantJob(e: React.MouseEvent<HTMLElement>): void { function applyForSoftwareConsultantJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
} }
this.props.p.applyForSoftwareConsultantJob(); p.applyForSoftwareConsultantJob();
this.checkIfEmployedHere(true); rerender();
} }
applyForSoftwareJob(e: React.MouseEvent<HTMLElement>): void { function applyForSoftwareJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
} }
this.props.p.applyForSoftwareJob(); p.applyForSoftwareJob();
this.checkIfEmployedHere(true); rerender();
} }
applyForWaiterJob(e: React.MouseEvent<HTMLElement>): void { function applyForWaiterJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
} }
this.props.p.applyForWaiterJob(); p.applyForWaiterJob();
this.checkIfEmployedHere(true); rerender();
} }
checkIfEmployedHere(updateState = false): void { function startInfiltration(e: React.MouseEvent<HTMLElement>): void {
this.jobTitle = this.props.p.jobs[this.props.locName];
if (this.jobTitle != null) {
this.companyPosition = CompanyPositions[this.jobTitle];
}
if (updateState) {
this.setState({
employedHere: this.jobTitle != null,
});
}
}
startInfiltration(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
} }
const loc = this.location; const loc = location;
if (!loc.infiltrationData) { if (!loc.infiltrationData)
console.error(`trying to start infiltration at ${this.props.locName} but the infiltrationData is null`); throw new Error(`trying to start infiltration at ${props.locName} but the infiltrationData is null`);
return;
}
this.props.engine.loadInfiltrationContent( router.toInfiltration(loc);
this.props.locName,
loc.infiltrationData.startingSecurityLevel,
loc.infiltrationData.maxClearanceLevel,
);
const data = loc.infiltrationData;
if (data == null) {
return;
}
} }
work(e: React.MouseEvent<HTMLElement>): void { function work(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
} }
const pos = this.companyPosition; const pos = companyPosition;
if (pos instanceof CompanyPosition) { if (pos instanceof CompanyPosition) {
if (pos.isPartTimeJob() || pos.isSoftwareConsultantJob() || pos.isBusinessConsultantJob()) { if (pos.isPartTimeJob() || pos.isSoftwareConsultantJob() || pos.isBusinessConsultantJob()) {
this.props.p.startWorkPartTime(this.props.locName); p.startWorkPartTime(router, props.locName);
} else { } else {
this.props.p.startWork(this.props.locName); p.startWork(router, props.locName);
} }
router.toWork();
} }
} }
quit(e: React.MouseEvent<HTMLElement>): void { function quit(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) return; if (!e.isTrusted) return;
const popupId = `quit-job-popup`; const popupId = `quit-job-popup`;
createPopup(popupId, QuitJobPopup, { createPopup(popupId, QuitJobPopup, {
locName: this.props.locName, locName: props.locName,
company: this.company, company: company,
player: this.props.p, player: p,
onQuit: () => this.checkIfEmployedHere(true), onQuit: rerender,
popupId: popupId, popupId: popupId,
}); });
} }
render(): React.ReactNode { const isEmployedHere = jobTitle != null;
const isEmployedHere = this.jobTitle != null; const favorGain = company.getFavorGain();
const favorGain = this.company.getFavorGain();
return ( return (
<div> <div>
{isEmployedHere && ( {isEmployedHere && (
<div> <div>
<p>Job Title: {this.jobTitle}</p> <p>Job Title: {jobTitle}</p>
<br /> <br />
<p style={blockStyleMarkup}>-------------------------</p> <p style={{ display: "block" }}>-------------------------</p>
<br /> <br />
<p className={"tooltip"}> <p className={"tooltip"}>
Company reputation: {Reputation(this.company.playerReputation)} Company reputation: {Reputation(company.playerReputation)}
<span className={"tooltiptext"}> <span className={"tooltiptext"}>
You will earn {Favor(favorGain[0])} company favor upon resetting after installing Augmentations You will earn {Favor(favorGain[0])} company favor upon resetting after installing Augmentations
</span> </span>
</p> </p>
<br /> <br />
<br /> <br />
<p style={blockStyleMarkup}>-------------------------</p> <p style={{ display: "block" }}>-------------------------</p>
<br /> <br />
<p className={"tooltip"}> <p className={"tooltip"}>
Company Favor: {Favor(this.company.favor)} Company Favor: {Favor(company.favor)}
<span className={"tooltiptext"}> <span className={"tooltiptext"}>
Company favor increases the rate at which you earn reputation for this company by 1% per favor. Company Company favor increases the rate at which you earn reputation for this company by 1% per favor. Company
favor is gained whenever you reset after installing Augmentations. The amount of favor you gain depends favor is gained whenever you reset after installing Augmentations. The amount of favor you gain depends on
on how much reputation you have with the comapny. how much reputation you have with the comapny.
</span> </span>
</p> </p>
<br /> <br />
<br /> <br />
<p style={blockStyleMarkup}>-------------------------</p> <p style={{ display: "block" }}>-------------------------</p>
<br /> <br />
<StdButton onClick={this.work} text={"Work"} /> <StdButton onClick={work} text={"Work"} />
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;
<StdButton onClick={this.quit} text={"Quit"} /> <StdButton onClick={quit} text={"Quit"} />
</div> </div>
)} )}
{this.company.hasAgentPositions() && ( {company.hasAgentPositions() && (
<ApplyToJobButton <ApplyToJobButton
company={this.company} company={company}
entryPosType={CompanyPositions[posNames.AgentCompanyPositions[0]]} entryPosType={CompanyPositions[posNames.AgentCompanyPositions[0]]}
onClick={this.applyForAgentJob} onClick={applyForAgentJob}
p={this.props.p} p={p}
style={this.btnStyle} style={{ display: "block" }}
text={"Apply for Agent Job"} text={"Apply for Agent Job"}
/> />
)} )}
{this.company.hasBusinessConsultantPositions() && ( {company.hasBusinessConsultantPositions() && (
<ApplyToJobButton <ApplyToJobButton
company={this.company} company={company}
entryPosType={CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]]} entryPosType={CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]]}
onClick={this.applyForBusinessConsultantJob} onClick={applyForBusinessConsultantJob}
p={this.props.p} p={p}
style={this.btnStyle} style={{ display: "block" }}
text={"Apply for Business Consultant Job"} text={"Apply for Business Consultant Job"}
/> />
)} )}
{this.company.hasBusinessPositions() && ( {company.hasBusinessPositions() && (
<ApplyToJobButton <ApplyToJobButton
company={this.company} company={company}
entryPosType={CompanyPositions[posNames.BusinessCompanyPositions[0]]} entryPosType={CompanyPositions[posNames.BusinessCompanyPositions[0]]}
onClick={this.applyForBusinessJob} onClick={applyForBusinessJob}
p={this.props.p} p={p}
style={this.btnStyle} style={{ display: "block" }}
text={"Apply for Business Job"} text={"Apply for Business Job"}
/> />
)} )}
{this.company.hasEmployeePositions() && ( {company.hasEmployeePositions() && (
<ApplyToJobButton <ApplyToJobButton
company={this.company} company={company}
entryPosType={CompanyPositions[posNames.MiscCompanyPositions[1]]} entryPosType={CompanyPositions[posNames.MiscCompanyPositions[1]]}
onClick={this.applyForEmployeeJob} onClick={applyForEmployeeJob}
p={this.props.p} p={p}
style={this.btnStyle} style={{ display: "block" }}
text={"Apply to be an Employee"} text={"Apply to be an Employee"}
/> />
)} )}
{this.company.hasEmployeePositions() && ( {company.hasEmployeePositions() && (
<ApplyToJobButton <ApplyToJobButton
company={this.company} company={company}
entryPosType={CompanyPositions[posNames.PartTimeCompanyPositions[1]]} entryPosType={CompanyPositions[posNames.PartTimeCompanyPositions[1]]}
onClick={this.applyForPartTimeEmployeeJob} onClick={applyForPartTimeEmployeeJob}
p={this.props.p} p={p}
style={this.btnStyle} style={{ display: "block" }}
text={"Apply to be a part-time Employee"} text={"Apply to be a part-time Employee"}
/> />
)} )}
{this.company.hasITPositions() && ( {company.hasITPositions() && (
<ApplyToJobButton <ApplyToJobButton
company={this.company} company={company}
entryPosType={CompanyPositions[posNames.ITCompanyPositions[0]]} entryPosType={CompanyPositions[posNames.ITCompanyPositions[0]]}
onClick={this.applyForItJob} onClick={applyForItJob}
p={this.props.p} p={p}
style={this.btnStyle} style={{ display: "block" }}
text={"Apply for IT Job"} text={"Apply for IT Job"}
/> />
)} )}
{this.company.hasSecurityPositions() && ( {company.hasSecurityPositions() && (
<ApplyToJobButton <ApplyToJobButton
company={this.company} company={company}
entryPosType={CompanyPositions[posNames.SecurityCompanyPositions[2]]} entryPosType={CompanyPositions[posNames.SecurityCompanyPositions[2]]}
onClick={this.applyForSecurityJob} onClick={applyForSecurityJob}
p={this.props.p} p={p}
style={this.btnStyle} style={{ display: "block" }}
text={"Apply for Security Job"} text={"Apply for Security Job"}
/> />
)} )}
{this.company.hasSoftwareConsultantPositions() && ( {company.hasSoftwareConsultantPositions() && (
<ApplyToJobButton <ApplyToJobButton
company={this.company} company={company}
entryPosType={CompanyPositions[posNames.SoftwareConsultantCompanyPositions[0]]} entryPosType={CompanyPositions[posNames.SoftwareConsultantCompanyPositions[0]]}
onClick={this.applyForSoftwareConsultantJob} onClick={applyForSoftwareConsultantJob}
p={this.props.p} p={p}
style={this.btnStyle} style={{ display: "block" }}
text={"Apply for Software Consultant Job"} text={"Apply for Software Consultant Job"}
/> />
)} )}
{this.company.hasSoftwarePositions() && ( {company.hasSoftwarePositions() && (
<ApplyToJobButton <ApplyToJobButton
company={this.company} company={company}
entryPosType={CompanyPositions[posNames.SoftwareCompanyPositions[0]]} entryPosType={CompanyPositions[posNames.SoftwareCompanyPositions[0]]}
onClick={this.applyForSoftwareJob} onClick={applyForSoftwareJob}
p={this.props.p} p={p}
style={this.btnStyle} style={{ display: "block" }}
text={"Apply for Software Job"} text={"Apply for Software Job"}
/> />
)} )}
{this.company.hasWaiterPositions() && ( {company.hasWaiterPositions() && (
<ApplyToJobButton <ApplyToJobButton
company={this.company} company={company}
entryPosType={CompanyPositions[posNames.MiscCompanyPositions[0]]} entryPosType={CompanyPositions[posNames.MiscCompanyPositions[0]]}
onClick={this.applyForWaiterJob} onClick={applyForWaiterJob}
p={this.props.p} p={p}
style={this.btnStyle} style={{ display: "block" }}
text={"Apply to be a Waiter"} text={"Apply to be a Waiter"}
/> />
)} )}
{this.company.hasWaiterPositions() && ( {company.hasWaiterPositions() && (
<ApplyToJobButton <ApplyToJobButton
company={this.company} company={company}
entryPosType={CompanyPositions[posNames.PartTimeCompanyPositions[0]]} entryPosType={CompanyPositions[posNames.PartTimeCompanyPositions[0]]}
onClick={this.applyForPartTimeWaiterJob} onClick={applyForPartTimeWaiterJob}
p={this.props.p} p={p}
style={this.btnStyle} style={{ display: "block" }}
text={"Apply to be a part-time Waiter"} text={"Apply to be a part-time Waiter"}
/> />
)} )}
{this.location.infiltrationData != null && ( {location.infiltrationData != null && (
<StdButton onClick={this.startInfiltration} style={this.btnStyle} text={"Infiltrate Company"} /> <StdButton onClick={startInfiltration} style={{ display: "block" }} text={"Infiltrate Company"} />
)} )}
<br /> <br />
<br /> <br />
<br /> <br />
<br /> <br />
<br /> <br />
</div> </div>
); );
}
} }

@ -12,16 +12,13 @@ import { HospitalLocation } from "./HospitalLocation";
import { SlumsLocation } from "./SlumsLocation"; import { SlumsLocation } from "./SlumsLocation";
import { SpecialLocation } from "./SpecialLocation"; import { SpecialLocation } from "./SpecialLocation";
import { TechVendorLocation } from "./TechVendorLocation"; import { TechVendorLocation } from "./TechVendorLocation";
import { TravelAgencyLocation } from "./TravelAgencyLocation"; import { TravelAgencyRoot } from "./TravelAgencyRoot";
import { UniversityLocation } from "./UniversityLocation"; import { UniversityLocation } from "./UniversityLocation";
import { CasinoLocation } from "./CasinoLocation"; import { CasinoLocation } from "./CasinoLocation";
import { Location } from "../Location"; import { Location } from "../Location";
import { LocationType } from "../LocationTypeEnum"; import { LocationType } from "../LocationTypeEnum";
import { CityName } from "../data/CityNames";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { SpecialServerIps } from "../../Server/SpecialServerIps"; import { SpecialServerIps } from "../../Server/SpecialServerIps";
@ -29,100 +26,73 @@ import { getServer, isBackdoorInstalled } from "../../Server/ServerHelpers";
import { StdButton } from "../../ui/React/StdButton"; import { StdButton } from "../../ui/React/StdButton";
import { CorruptableText } from "../../ui/React/CorruptableText"; import { CorruptableText } from "../../ui/React/CorruptableText";
import { use } from "../../ui/Context";
type IProps = { type IProps = {
engine: IEngine;
loc: Location; loc: Location;
p: IPlayer;
returnToCity: () => void;
travel: (to: CityName) => void;
}; };
export class GenericLocation extends React.Component<IProps, any> { export function GenericLocation({ loc }: IProps): React.ReactElement {
/** const router = use.Router();
* Stores button styling that sets them all to block display const player = use.Player();
*/
btnStyle: any;
constructor(props: IProps) {
super(props);
this.btnStyle = { display: "block" };
}
/** /**
* Determine what needs to be rendered for this location based on the locations * Determine what needs to be rendered for this location based on the locations
* type. Returns an array of React components that should be rendered * type. Returns an array of React components that should be rendered
*/ */
getLocationSpecificContent(): React.ReactNode[] { function getLocationSpecificContent(): React.ReactNode[] {
const content: React.ReactNode[] = []; const content: React.ReactNode[] = [];
if (this.props.loc.types.includes(LocationType.Company)) { if (loc.types.includes(LocationType.Company)) {
content.push( content.push(<CompanyLocation key={"companylocation"} locName={loc.name} />);
<CompanyLocation
engine={this.props.engine}
key={"companylocation"}
locName={this.props.loc.name}
p={this.props.p}
/>,
);
} }
if (this.props.loc.types.includes(LocationType.Gym)) { if (loc.types.includes(LocationType.Gym)) {
content.push(<GymLocation key={"gymlocation"} loc={this.props.loc} p={this.props.p} />); content.push(<GymLocation key={"gymlocation"} router={router} loc={loc} p={player} />);
} }
if (this.props.loc.types.includes(LocationType.Hospital)) { if (loc.types.includes(LocationType.Hospital)) {
content.push(<HospitalLocation key={"hospitallocation"} p={this.props.p} />); content.push(<HospitalLocation key={"hospitallocation"} p={player} />);
} }
if (this.props.loc.types.includes(LocationType.Slums)) { if (loc.types.includes(LocationType.Slums)) {
content.push(<SlumsLocation key={"slumslocation"} p={this.props.p} />); content.push(<SlumsLocation key={"slumslocation"} />);
} }
if (this.props.loc.types.includes(LocationType.Special)) { if (loc.types.includes(LocationType.Special)) {
content.push( content.push(<SpecialLocation key={"speciallocation"} loc={loc} />);
<SpecialLocation engine={this.props.engine} key={"speciallocation"} loc={this.props.loc} p={this.props.p} />,
);
} }
if (this.props.loc.types.includes(LocationType.TechVendor)) { if (loc.types.includes(LocationType.TechVendor)) {
content.push(<TechVendorLocation key={"techvendorlocation"} loc={this.props.loc} p={this.props.p} />); content.push(<TechVendorLocation key={"techvendorlocation"} loc={loc} p={player} />);
} }
if (this.props.loc.types.includes(LocationType.TravelAgency)) { if (loc.types.includes(LocationType.TravelAgency)) {
content.push(<TravelAgencyLocation key={"travelagencylocation"} p={this.props.p} travel={this.props.travel} />); content.push(<TravelAgencyRoot key={"travelagencylocation"} p={player} router={router} />);
} }
if (this.props.loc.types.includes(LocationType.University)) { if (loc.types.includes(LocationType.University)) {
content.push(<UniversityLocation key={"universitylocation"} loc={this.props.loc} p={this.props.p} />); content.push(<UniversityLocation key={"universitylocation"} loc={loc} />);
} }
if (this.props.loc.types.includes(LocationType.Casino)) { if (loc.types.includes(LocationType.Casino)) {
content.push(<CasinoLocation key={"casinoLocation"} p={this.props.p} />); content.push(<CasinoLocation key={"casinoLocation"} p={player} />);
} }
return content; return content;
} }
render(): React.ReactNode { const locContent: React.ReactNode[] = getLocationSpecificContent();
const locContent: React.ReactNode[] = this.getLocationSpecificContent(); const ip = SpecialServerIps.getIp(loc.name);
const ip = SpecialServerIps.getIp(this.props.loc.name); const server = getServer(ip);
const server = getServer(ip); const backdoorInstalled = server !== null && isBackdoorInstalled(server);
const backdoorInstalled = server !== null && isBackdoorInstalled(server);
return ( return (
<div> <div>
<StdButton onClick={this.props.returnToCity} style={this.btnStyle} text={"Return to World"} /> <StdButton onClick={() => router.toCity()} style={{ display: "block" }} text={"Return to World"} />
<h1 className="noselect"> <h1 className="noselect">
{backdoorInstalled && !Settings.DisableTextEffects ? ( {backdoorInstalled && !Settings.DisableTextEffects ? <CorruptableText content={loc.name} /> : loc.name}
<CorruptableText content={this.props.loc.name} /> </h1>
) : ( {locContent}
this.props.loc.name </div>
)} );
</h1>
{locContent}
</div>
);
}
} }

@ -15,10 +15,12 @@ import { SpecialServerIps } from "../../Server/SpecialServerIps";
import { StdButton } from "../../ui/React/StdButton"; import { StdButton } from "../../ui/React/StdButton";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { IRouter } from "../../ui/Router";
type IProps = { type IProps = {
loc: Location; loc: Location;
p: IPlayer; p: IPlayer;
router: IRouter;
}; };
export class GymLocation extends React.Component<IProps, any> { export class GymLocation extends React.Component<IProps, any> {
@ -50,7 +52,7 @@ export class GymLocation extends React.Component<IProps, any> {
train(stat: string): void { train(stat: string): void {
const loc = this.props.loc; const loc = this.props.loc;
this.props.p.startClass(this.calculateCost(), loc.expMult, stat); this.props.p.startClass(this.props.router, this.calculateCost(), loc.expMult, stat);
} }
trainStrength(): void { trainStrength(): void {

@ -1,152 +0,0 @@
/**
* Root React Component for displaying overall Location UI
*/
import * as React from "react";
import { LocationCity } from "./City";
import { GenericLocation } from "./GenericLocation";
import { Cities } from "../Cities";
import { Locations } from "../Locations";
import { LocationType } from "../LocationTypeEnum";
import { CityName } from "../data/CityNames";
import { LocationName } from "../data/LocationNames";
import { CONSTANTS } from "../../Constants";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { dialogBoxCreate } from "../../../utils/DialogBox";
type IProps = {
initiallyInCity?: boolean;
engine: IEngine;
p: IPlayer;
};
type IState = {
city: CityName;
inCity: boolean;
location: LocationName;
};
export class LocationRoot extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
city: props.p.city,
inCity: props.initiallyInCity == null ? true : props.initiallyInCity,
location: props.p.location,
};
this.enterLocation = this.enterLocation.bind(this);
this.returnToCity = this.returnToCity.bind(this);
this.travel = this.travel.bind(this);
}
enterLocation(to: LocationName): void {
this.props.p.gotoLocation(to);
this.setState({
inCity: false,
location: to,
});
}
/**
* Click listener for a button that lets the player go from a specific location
* back to the city
*/
returnToCity(): void {
this.setState({
inCity: true,
});
}
/**
* Render UI for a city
*/
renderCity(): React.ReactNode {
const city = Cities[this.state.city];
if (city == null) {
throw new Error(`Invalid city when rendering UI: ${this.state.city}`);
}
return (
<div className="noselect">
<h2>{this.state.city}</h2>
<LocationCity city={city} enterLocation={this.enterLocation} />
</div>
);
}
/**
* Render UI for a specific location
*/
renderLocation(): React.ReactNode {
const loc = Locations[this.state.location];
if (loc == null) {
throw new Error(`Invalid location when rendering UI: ${this.state.location}`);
}
if (loc.types.includes(LocationType.StockMarket)) {
this.props.engine.loadStockMarketContent();
}
return (
<GenericLocation
engine={this.props.engine}
loc={loc}
p={this.props.p}
returnToCity={this.returnToCity}
travel={this.travel}
/>
);
}
/**
* Travel to a different city
* @param {CityName} to - Destination city
*/
travel(to: CityName): void {
const p = this.props.p;
const cost = CONSTANTS.TravelCost;
if (!p.canAfford(cost)) {
dialogBoxCreate(`You cannot afford to travel to ${to}`);
return;
}
p.loseMoney(cost);
p.travel(to);
dialogBoxCreate(<span className="noselect">You are now in {to}!</span>);
// Dynamically update main menu
if (p.firstTimeTraveled === false) {
p.firstTimeTraveled = true;
const travelTab = document.getElementById("travel-tab");
const worldHeader = document.getElementById("world-menu-header");
if (travelTab != null && worldHeader !== null) {
travelTab.style.display = "list-item";
worldHeader.click();
worldHeader.click();
}
}
if (this.props.p.travel(to)) {
this.setState({
inCity: true,
city: to,
});
}
}
render(): React.ReactNode {
if (this.state.inCity) {
return this.renderCity();
} else {
return this.renderLocation();
}
}
}

@ -6,225 +6,209 @@
import * as React from "react"; import * as React from "react";
import { Crimes } from "../../Crime/Crimes"; import { Crimes } from "../../Crime/Crimes";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { AutoupdatingStdButton } from "../../ui/React/AutoupdatingStdButton"; import { AutoupdatingStdButton } from "../../ui/React/AutoupdatingStdButton";
import { use } from "../../ui/Context";
type IProps = { export function SlumsLocation(): React.ReactElement {
p: IPlayer; const player = use.Player();
}; const router = use.Router();
function shoplift(e: React.MouseEvent<HTMLElement>): void {
export class SlumsLocation extends React.Component<IProps, any> {
/**
* Stores button styling that sets them all to block display
*/
btnStyle: any;
constructor(props: IProps) {
super(props);
this.btnStyle = { display: "block" };
this.shoplift = this.shoplift.bind(this);
this.robStore = this.robStore.bind(this);
this.mug = this.mug.bind(this);
this.larceny = this.larceny.bind(this);
this.dealDrugs = this.dealDrugs.bind(this);
this.bondForgery = this.bondForgery.bind(this);
this.traffickArms = this.traffickArms.bind(this);
this.homicide = this.homicide.bind(this);
this.grandTheftAuto = this.grandTheftAuto.bind(this);
this.kidnap = this.kidnap.bind(this);
this.assassinate = this.assassinate.bind(this);
this.heist = this.heist.bind(this);
}
shoplift(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
} }
Crimes.Shoplift.commit(this.props.p); Crimes.Shoplift.commit(router, player);
} }
robStore(e: React.MouseEvent<HTMLElement>): void { function robStore(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
} }
Crimes.RobStore.commit(this.props.p); Crimes.RobStore.commit(router, player);
} }
mug(e: React.MouseEvent<HTMLElement>): void { function mug(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
} }
Crimes.Mug.commit(this.props.p); Crimes.Mug.commit(router, player);
} }
larceny(e: React.MouseEvent<HTMLElement>): void { function larceny(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
} }
Crimes.Larceny.commit(this.props.p); Crimes.Larceny.commit(router, player);
} }
dealDrugs(e: React.MouseEvent<HTMLElement>): void { function dealDrugs(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
} }
Crimes.DealDrugs.commit(this.props.p); Crimes.DealDrugs.commit(router, player);
} }
bondForgery(e: React.MouseEvent<HTMLElement>): void { function bondForgery(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
} }
Crimes.BondForgery.commit(this.props.p); Crimes.BondForgery.commit(router, player);
} }
traffickArms(e: React.MouseEvent<HTMLElement>): void { function traffickArms(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
} }
Crimes.TraffickArms.commit(this.props.p); Crimes.TraffickArms.commit(router, player);
} }
homicide(e: React.MouseEvent<HTMLElement>): void { function homicide(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
} }
Crimes.Homicide.commit(this.props.p); Crimes.Homicide.commit(router, player);
} }
grandTheftAuto(e: React.MouseEvent<HTMLElement>): void { function grandTheftAuto(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
} }
Crimes.GrandTheftAuto.commit(this.props.p); Crimes.GrandTheftAuto.commit(router, player);
} }
kidnap(e: React.MouseEvent<HTMLElement>): void { function kidnap(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
} }
Crimes.Kidnap.commit(this.props.p); Crimes.Kidnap.commit(router, player);
} }
assassinate(e: React.MouseEvent<HTMLElement>): void { function assassinate(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
} }
Crimes.Assassination.commit(this.props.p); Crimes.Assassination.commit(router, player);
} }
heist(e: React.MouseEvent<HTMLElement>): void { function heist(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
} }
Crimes.Heist.commit(this.props.p); Crimes.Heist.commit(router, player);
} }
render(): React.ReactNode { const shopliftChance = Crimes.Shoplift.successRate(player);
const shopliftChance = Crimes.Shoplift.successRate(this.props.p); const robStoreChance = Crimes.RobStore.successRate(player);
const robStoreChance = Crimes.RobStore.successRate(this.props.p); const mugChance = Crimes.Mug.successRate(player);
const mugChance = Crimes.Mug.successRate(this.props.p); const larcenyChance = Crimes.Larceny.successRate(player);
const larcenyChance = Crimes.Larceny.successRate(this.props.p); const drugsChance = Crimes.DealDrugs.successRate(player);
const drugsChance = Crimes.DealDrugs.successRate(this.props.p); const bondChance = Crimes.BondForgery.successRate(player);
const bondChance = Crimes.BondForgery.successRate(this.props.p); const armsChance = Crimes.TraffickArms.successRate(player);
const armsChance = Crimes.TraffickArms.successRate(this.props.p); const homicideChance = Crimes.Homicide.successRate(player);
const homicideChance = Crimes.Homicide.successRate(this.props.p); const gtaChance = Crimes.GrandTheftAuto.successRate(player);
const gtaChance = Crimes.GrandTheftAuto.successRate(this.props.p); const kidnapChance = Crimes.Kidnap.successRate(player);
const kidnapChance = Crimes.Kidnap.successRate(this.props.p); const assassinateChance = Crimes.Assassination.successRate(player);
const assassinateChance = Crimes.Assassination.successRate(this.props.p); const heistChance = Crimes.Heist.successRate(player);
const heistChance = Crimes.Heist.successRate(this.props.p);
return ( return (
<div> <div>
<AutoupdatingStdButton <AutoupdatingStdButton
intervalTime={5e3} label={`Shoplift (${numeralWrapper.formatPercentage(shopliftChance)} chance of success)`}
onClick={this.shoplift} intervalTime={5e3}
style={this.btnStyle} onClick={shoplift}
text={`Shoplift (${numeralWrapper.formatPercentage(shopliftChance)} chance of success)`} style={{ display: "block" }}
tooltip={"Attempt to shoplift from a low-end retailer"} text={`Shoplift (${numeralWrapper.formatPercentage(shopliftChance)} chance of success)`}
/> tooltip={"Attempt to shoplift from a low-end retailer"}
<AutoupdatingStdButton />
intervalTime={5e3} <AutoupdatingStdButton
onClick={this.robStore} label={`Rob store (${numeralWrapper.formatPercentage(robStoreChance)} chance of success)`}
style={this.btnStyle} intervalTime={5e3}
text={`Rob store (${numeralWrapper.formatPercentage(robStoreChance)} chance of success)`} onClick={robStore}
tooltip={"Attempt to commit armed robbery on a high-end store"} style={{ display: "block" }}
/> text={`Rob store (${numeralWrapper.formatPercentage(robStoreChance)} chance of success)`}
<AutoupdatingStdButton tooltip={"Attempt to commit armed robbery on a high-end store"}
intervalTime={5e3} />
onClick={this.mug} <AutoupdatingStdButton
style={this.btnStyle} label={`Mug someone (${numeralWrapper.formatPercentage(mugChance)} chance of success)`}
text={`Mug someone (${numeralWrapper.formatPercentage(mugChance)} chance of success)`} intervalTime={5e3}
tooltip={"Attempt to mug a random person on the street"} onClick={mug}
/> style={{ display: "block" }}
<AutoupdatingStdButton text={`Mug someone (${numeralWrapper.formatPercentage(mugChance)} chance of success)`}
intervalTime={5e3} tooltip={"Attempt to mug a random person on the street"}
onClick={this.larceny} />
style={this.btnStyle} <AutoupdatingStdButton
text={`Larceny (${numeralWrapper.formatPercentage(larcenyChance)} chance of success)`} label={`Larceny (${numeralWrapper.formatPercentage(larcenyChance)} chance of success)`}
tooltip={"Attempt to rob property from someone's house"} intervalTime={5e3}
/> onClick={larceny}
<AutoupdatingStdButton style={{ display: "block" }}
intervalTime={5e3} text={`Larceny (${numeralWrapper.formatPercentage(larcenyChance)} chance of success)`}
onClick={this.dealDrugs} tooltip={"Attempt to rob property from someone's house"}
style={this.btnStyle} />
text={`Deal Drugs (${numeralWrapper.formatPercentage(drugsChance)} chance of success)`} <AutoupdatingStdButton
tooltip={"Attempt to deal drugs"} label={`Deal Drugs (${numeralWrapper.formatPercentage(drugsChance)} chance of success)`}
/> intervalTime={5e3}
<AutoupdatingStdButton onClick={dealDrugs}
intervalTime={5e3} style={{ display: "block" }}
onClick={this.bondForgery} text={`Deal Drugs (${numeralWrapper.formatPercentage(drugsChance)} chance of success)`}
style={this.btnStyle} tooltip={"Attempt to deal drugs"}
text={`Bond Forgery (${numeralWrapper.formatPercentage(bondChance)} chance of success)`} />
tooltip={"Attempt to forge corporate bonds"} <AutoupdatingStdButton
/> label={`Bond Forgery (${numeralWrapper.formatPercentage(bondChance)} chance of success)`}
<AutoupdatingStdButton intervalTime={5e3}
intervalTime={5e3} onClick={bondForgery}
onClick={this.traffickArms} style={{ display: "block" }}
style={this.btnStyle} text={`Bond Forgery (${numeralWrapper.formatPercentage(bondChance)} chance of success)`}
text={`Traffick illegal Arms (${numeralWrapper.formatPercentage(armsChance)} chance of success)`} tooltip={"Attempt to forge corporate bonds"}
tooltip={"Attempt to smuggle illegal arms into the city"} />
/> <AutoupdatingStdButton
<AutoupdatingStdButton label={`Traffick illegal Arms (${numeralWrapper.formatPercentage(armsChance)} chance of success)`}
intervalTime={5e3} intervalTime={5e3}
onClick={this.homicide} onClick={traffickArms}
style={this.btnStyle} style={{ display: "block" }}
text={`Homicide (${numeralWrapper.formatPercentage(homicideChance)} chance of success)`} text={`Traffick illegal Arms (${numeralWrapper.formatPercentage(armsChance)} chance of success)`}
tooltip={"Attempt to murder a random person on the street"} tooltip={"Attempt to smuggle illegal arms into the city"}
/> />
<AutoupdatingStdButton <AutoupdatingStdButton
intervalTime={5e3} label={`Homicide (${numeralWrapper.formatPercentage(homicideChance)} chance of success)`}
onClick={this.grandTheftAuto} intervalTime={5e3}
style={this.btnStyle} onClick={homicide}
text={`Grand theft Auto (${numeralWrapper.formatPercentage(gtaChance)} chance of success)`} style={{ display: "block" }}
tooltip={"Attempt to commit grand theft auto"} text={`Homicide (${numeralWrapper.formatPercentage(homicideChance)} chance of success)`}
/> tooltip={"Attempt to murder a random person on the street"}
<AutoupdatingStdButton />
intervalTime={5e3} <AutoupdatingStdButton
onClick={this.kidnap} label={`Grand theft Auto (${numeralWrapper.formatPercentage(gtaChance)} chance of success)`}
style={this.btnStyle} intervalTime={5e3}
text={`Kidnap and Ransom (${numeralWrapper.formatPercentage(kidnapChance)} chance of success)`} onClick={grandTheftAuto}
tooltip={"Attempt to kidnap and ransom a high-profile-target"} style={{ display: "block" }}
/> text={`Grand theft Auto (${numeralWrapper.formatPercentage(gtaChance)} chance of success)`}
<AutoupdatingStdButton tooltip={"Attempt to commit grand theft auto"}
intervalTime={5e3} />
onClick={this.assassinate} <AutoupdatingStdButton
style={this.btnStyle} label={`Kidnap and Ransom (${numeralWrapper.formatPercentage(kidnapChance)} chance of success)`}
text={`Assassinate (${numeralWrapper.formatPercentage(assassinateChance)} chance of success)`} intervalTime={5e3}
tooltip={"Attempt to assassinate a high-profile target"} onClick={kidnap}
/> style={{ display: "block" }}
<AutoupdatingStdButton text={`Kidnap and Ransom (${numeralWrapper.formatPercentage(kidnapChance)} chance of success)`}
intervalTime={5e3} tooltip={"Attempt to kidnap and ransom a high-profile-target"}
onClick={this.heist} />
style={this.btnStyle} <AutoupdatingStdButton
text={`Heist (${numeralWrapper.formatPercentage(heistChance)} chance of success)`} label={`Assassinate (${numeralWrapper.formatPercentage(assassinateChance)} chance of success)`}
tooltip={"Attempt to pull off the ultimate heist"} intervalTime={5e3}
/> onClick={assassinate}
</div> style={{ display: "block" }}
); text={`Assassinate (${numeralWrapper.formatPercentage(assassinateChance)} chance of success)`}
} tooltip={"Attempt to assassinate a high-profile target"}
/>
<AutoupdatingStdButton
label={`Heist (${numeralWrapper.formatPercentage(heistChance)} chance of success)`}
intervalTime={5e3}
onClick={heist}
style={{ display: "block" }}
text={`Heist (${numeralWrapper.formatPercentage(heistChance)} chance of success)`}
tooltip={"Attempt to pull off the ultimate heist"}
/>
</div>
);
} }

@ -10,15 +10,14 @@
* This subcomponent creates all of the buttons for interacting with those special * This subcomponent creates all of the buttons for interacting with those special
* properties * properties
*/ */
import * as React from "react"; import React, { useState } from "react";
import { Location } from "../Location"; import { Location } from "../Location";
import { CreateCorporationPopup } from "../../Corporation/ui/CreateCorporationPopup"; import { CreateCorporationPopup } from "../../Corporation/ui/CreateCorporationPopup";
import { createPopup } from "../../ui/React/createPopup"; import { createPopup } from "../../ui/React/createPopup";
import { LocationName } from "../data/LocationNames"; import { LocationName } from "../data/LocationNames";
import { IEngine } from "../../IEngine"; import { use } from "../../ui/Context";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { AutoupdatingStdButton } from "../../ui/React/AutoupdatingStdButton"; import { AutoupdatingStdButton } from "../../ui/React/AutoupdatingStdButton";
import { StdButton } from "../../ui/React/StdButton"; import { StdButton } from "../../ui/React/StdButton";
@ -26,43 +25,21 @@ import { StdButton } from "../../ui/React/StdButton";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { dialogBoxCreate } from "../../../utils/DialogBox";
type IProps = { type IProps = {
engine: IEngine;
loc: Location; loc: Location;
p: IPlayer;
}; };
type IState = { export function SpecialLocation(props: IProps): React.ReactElement {
inBladeburner: boolean; const player = use.Player();
}; const router = use.Router();
const setRerender = useState(false)[1];
export class SpecialLocation extends React.Component<IProps, IState> { const inBladeburner = player.inBladeburner();
/**
* Stores button styling that sets them all to block display
*/
btnStyle: any;
constructor(props: IProps) {
super(props);
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);
this.state = {
inBladeburner: this.props.p.inBladeburner(),
};
}
/** /**
* Click handler for "Create Corporation" button at Sector-12 City Hall * Click handler for "Create Corporation" button at Sector-12 City Hall
*/ */
createCorporationPopup(): void { function createCorporationPopup(): void {
const popupId = `create-start-corporation-popup`; const popupId = `create-start-corporation-popup`;
createPopup(popupId, CreateCorporationPopup, { createPopup(popupId, CreateCorporationPopup, {
player: this.props.p, player: player,
popupId: popupId, popupId: popupId,
}); });
} }
@ -70,19 +47,17 @@ export class SpecialLocation extends React.Component<IProps, IState> {
/** /**
* Click handler for Bladeburner button at Sector-12 NSA * Click handler for Bladeburner button at Sector-12 NSA
*/ */
handleBladeburner(): void { function handleBladeburner(): void {
const p = this.props.p; const p = player;
if (p.inBladeburner()) { if (p.inBladeburner()) {
// Enter Bladeburner division // Enter Bladeburner division
this.props.engine.loadBladeburnerContent(); router.toBladeburner();
} else { } else {
// Apply for Bladeburner division // Apply for Bladeburner division
if (p.strength >= 100 && p.defense >= 100 && p.dexterity >= 100 && p.agility >= 100) { if (p.strength >= 100 && p.defense >= 100 && p.dexterity >= 100 && p.agility >= 100) {
p.startBladeburner({ new: true }); p.startBladeburner({ new: true });
dialogBoxCreate("You have been accepted into the Bladeburner division!"); dialogBoxCreate("You have been accepted into the Bladeburner division!");
this.setState({ setRerender((old) => !old);
inBladeburner: true,
});
const worldHeader = document.getElementById("world-menu-header"); const worldHeader = document.getElementById("world-menu-header");
if (worldHeader instanceof HTMLElement) { if (worldHeader instanceof HTMLElement) {
@ -98,28 +73,28 @@ export class SpecialLocation extends React.Component<IProps, IState> {
/** /**
* Click handler for Resleeving button at New Tokyo VitaLife * Click handler for Resleeving button at New Tokyo VitaLife
*/ */
handleResleeving(): void { function handleResleeving(): void {
this.props.engine.loadResleevingContent(); router.toResleeves();
} }
renderBladeburner(): React.ReactNode { function renderBladeburner(): React.ReactElement {
if (!this.props.p.canAccessBladeburner()) { if (!player.canAccessBladeburner()) {
return null; return <></>;
} }
const text = this.state.inBladeburner ? "Enter Bladeburner Headquarters" : "Apply to Bladeburner Division"; const text = inBladeburner ? "Enter Bladeburner Headquarters" : "Apply to Bladeburner Division";
return <StdButton onClick={this.handleBladeburner} style={this.btnStyle} text={text} />; return <StdButton onClick={handleBladeburner} style={{ display: "block" }} text={text} />;
} }
renderNoodleBar(): React.ReactNode { function renderNoodleBar(): React.ReactElement {
function EatNoodles(): void { function EatNoodles(): void {
dialogBoxCreate(<>You ate some delicious noodles and feel refreshed.</>); dialogBoxCreate(<>You ate some delicious noodles and feel refreshed.</>);
} }
return <StdButton onClick={EatNoodles} style={this.btnStyle} text={"Eat noodles"} />; return <StdButton onClick={EatNoodles} style={{ display: "block" }} text={"Eat noodles"} />;
} }
renderCreateCorporation(): React.ReactNode { function renderCreateCorporation(): React.ReactElement {
if (!this.props.p.canAccessCorporation()) { if (!player.canAccessCorporation()) {
return ( return (
<> <>
<p> <p>
@ -130,38 +105,36 @@ export class SpecialLocation extends React.Component<IProps, IState> {
} }
return ( return (
<AutoupdatingStdButton <AutoupdatingStdButton
disabled={!this.props.p.canAccessCorporation() || this.props.p.hasCorporation()} disabled={!player.canAccessCorporation() || player.hasCorporation()}
onClick={this.createCorporationPopup} onClick={createCorporationPopup}
style={this.btnStyle} style={{ display: "block" }}
text={"Create a Corporation"} text={"Create a Corporation"}
/> />
); );
} }
renderResleeving(): React.ReactNode { function renderResleeving(): React.ReactElement {
if (!this.props.p.canAccessResleeving()) { if (!player.canAccessResleeving()) {
return null; return <></>;
} }
return <StdButton onClick={this.handleResleeving} style={this.btnStyle} text={"Re-Sleeve"} />; return <StdButton onClick={handleResleeving} style={{ display: "block" }} text={"Re-Sleeve"} />;
} }
render(): React.ReactNode { switch (props.loc.name) {
switch (this.props.loc.name) { case LocationName.NewTokyoVitaLife: {
case LocationName.NewTokyoVitaLife: { return renderResleeving();
return this.renderResleeving();
}
case LocationName.Sector12CityHall: {
return this.renderCreateCorporation();
}
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;
} }
case LocationName.Sector12CityHall: {
return renderCreateCorporation();
}
case LocationName.Sector12NSA: {
return renderBladeburner();
}
case LocationName.NewTokyoNoodleBar: {
return renderNoodleBar();
}
default:
console.error(`Location ${props.loc.name} doesn't have any special properties`);
return <></>;
} }
} }

@ -10,28 +10,43 @@ import { TravelConfirmationPopup } from "./TravelConfirmationPopup";
import { CONSTANTS } from "../../Constants"; import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { IRouter } from "../../ui/Router";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { StdButton } from "../../ui/React/StdButton"; import { StdButton } from "../../ui/React/StdButton";
import { createPopup } from "../../ui/React/createPopup"; import { createPopup } from "../../ui/React/createPopup";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { WorldMap } from "../../ui/React/WorldMap"; import { WorldMap } from "../../ui/React/WorldMap";
import { dialogBoxCreate } from "../../../utils/DialogBox";
type IProps = { type IProps = {
p: IPlayer; p: IPlayer;
travel: (to: CityName) => void; router: IRouter;
}; };
function createTravelPopup(p: IPlayer, city: string, travel: () => void): void { function travel(p: IPlayer, router: IRouter, to: CityName): void {
const cost = CONSTANTS.TravelCost;
if (!p.canAfford(cost)) {
dialogBoxCreate(`You cannot afford to travel to ${to}`);
return;
}
p.loseMoney(cost);
p.travel(to);
dialogBoxCreate(<span className="noselect">You are now in {to}!</span>);
router.toCity();
}
function createTravelPopup(p: IPlayer, router: IRouter, city: CityName): void {
if (Settings.SuppressTravelConfirmation) { if (Settings.SuppressTravelConfirmation) {
travel(); travel(p, router, city);
return; return;
} }
const popupId = `travel-confirmation`; const popupId = `travel-confirmation`;
createPopup(popupId, TravelConfirmationPopup, { createPopup(popupId, TravelConfirmationPopup, {
player: p, player: p,
city: city, city: city,
travel: travel, travel: () => travel(p, router, city),
popupId: popupId, popupId: popupId,
}); });
} }
@ -45,7 +60,7 @@ function ASCIIWorldMap(props: IProps): React.ReactElement {
</p> </p>
<WorldMap <WorldMap
currentCity={props.p.city} currentCity={props.p.city}
onTravel={(city: CityName) => createTravelPopup(props.p, city, () => props.travel(city))} onTravel={(city: CityName) => createTravelPopup(props.p, props.router, city)}
/> />
</div> </div>
); );
@ -66,7 +81,7 @@ function ListWorldMap(props: IProps): React.ReactElement {
return ( return (
<StdButton <StdButton
key={city} key={city}
onClick={() => createTravelPopup(props.p, city, () => props.travel(match[1]))} onClick={() => createTravelPopup(props.p, props.router, city as CityName)}
style={{ display: "block" }} style={{ display: "block" }}
text={`Travel to ${city}`} text={`Travel to ${city}`}
/> />
@ -76,10 +91,15 @@ function ListWorldMap(props: IProps): React.ReactElement {
); );
} }
export function TravelAgencyLocation(props: IProps): React.ReactElement { export function TravelAgencyRoot(props: IProps): React.ReactElement {
if (Settings.DisableASCIIArt) { return (
return <ListWorldMap p={props.p} travel={props.travel} />; <>
} else { <h1>Travel Agency</h1>
return <ASCIIWorldMap p={props.p} travel={props.travel} />; {Settings.DisableASCIIArt ? (
} <ListWorldMap p={props.p} router={props.router} />
) : (
<ASCIIWorldMap p={props.p} router={props.router} />
)}
</>
);
} }

@ -8,154 +8,133 @@ import * as React from "react";
import { Location } from "../Location"; import { Location } from "../Location";
import { CONSTANTS } from "../../Constants"; import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { getServer } from "../../Server/ServerHelpers"; import { getServer } from "../../Server/ServerHelpers";
import { Server } from "../../Server/Server"; import { Server } from "../../Server/Server";
import { SpecialServerIps } from "../../Server/SpecialServerIps"; import { SpecialServerIps } from "../../Server/SpecialServerIps";
import { StdButton } from "../../ui/React/StdButton"; import { StdButton } from "../../ui/React/StdButton";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { use } from "../../ui/Context";
type IProps = { type IProps = {
loc: Location; loc: Location;
p: IPlayer;
}; };
export class UniversityLocation extends React.Component<IProps, any> { export function UniversityLocation(props: IProps): React.ReactElement {
/** const player = use.Player();
* Stores button styling that sets them all to block display const router = use.Router();
*/
btnStyle: any;
constructor(props: IProps) { function calculateCost(): number {
super(props); const ip = SpecialServerIps.getIp(props.loc.name);
this.btnStyle = { display: "block" };
this.take = this.take.bind(this);
this.study = this.study.bind(this);
this.dataStructures = this.dataStructures.bind(this);
this.networks = this.networks.bind(this);
this.algorithms = this.algorithms.bind(this);
this.management = this.management.bind(this);
this.leadership = this.leadership.bind(this);
this.calculateCost = this.calculateCost.bind(this);
}
calculateCost(): number {
const ip = SpecialServerIps.getIp(this.props.loc.name);
const server = getServer(ip); const server = getServer(ip);
if (server == null || !server.hasOwnProperty("backdoorInstalled")) return this.props.loc.costMult; if (server == null || !server.hasOwnProperty("backdoorInstalled")) return props.loc.costMult;
const discount = (server as Server).backdoorInstalled ? 0.9 : 1; const discount = (server as Server).backdoorInstalled ? 0.9 : 1;
return this.props.loc.costMult * discount; return props.loc.costMult * discount;
} }
take(stat: string): void { function take(stat: string): void {
const loc = this.props.loc; const loc = props.loc;
this.props.p.startClass(this.calculateCost(), loc.expMult, stat); player.startClass(router, calculateCost(), loc.expMult, stat);
} }
study(): void { function study(): void {
this.take(CONSTANTS.ClassStudyComputerScience); take(CONSTANTS.ClassStudyComputerScience);
} }
dataStructures(): void { function dataStructures(): void {
this.take(CONSTANTS.ClassDataStructures); take(CONSTANTS.ClassDataStructures);
} }
networks(): void { function networks(): void {
this.take(CONSTANTS.ClassNetworks); take(CONSTANTS.ClassNetworks);
} }
algorithms(): void { function algorithms(): void {
this.take(CONSTANTS.ClassAlgorithms); take(CONSTANTS.ClassAlgorithms);
} }
management(): void { function management(): void {
this.take(CONSTANTS.ClassManagement); take(CONSTANTS.ClassManagement);
} }
leadership(): void { function leadership(): void {
this.take(CONSTANTS.ClassLeadership); take(CONSTANTS.ClassLeadership);
} }
render(): React.ReactNode { const costMult: number = calculateCost();
const costMult: number = this.calculateCost();
const dataStructuresCost = CONSTANTS.ClassDataStructuresBaseCost * costMult; const dataStructuresCost = CONSTANTS.ClassDataStructuresBaseCost * costMult;
const networksCost = CONSTANTS.ClassNetworksBaseCost * costMult; const networksCost = CONSTANTS.ClassNetworksBaseCost * costMult;
const algorithmsCost = CONSTANTS.ClassAlgorithmsBaseCost * costMult; const algorithmsCost = CONSTANTS.ClassAlgorithmsBaseCost * costMult;
const managementCost = CONSTANTS.ClassManagementBaseCost * costMult; const managementCost = CONSTANTS.ClassManagementBaseCost * costMult;
const leadershipCost = CONSTANTS.ClassLeadershipBaseCost * costMult; const leadershipCost = CONSTANTS.ClassLeadershipBaseCost * costMult;
const earnHackingExpTooltip = `Gain hacking experience!`; const earnHackingExpTooltip = `Gain hacking experience!`;
const earnCharismaExpTooltip = `Gain charisma experience!`; const earnCharismaExpTooltip = `Gain charisma experience!`;
return ( return (
<div> <div>
<StdButton <StdButton
onClick={this.study} onClick={study}
style={this.btnStyle} style={{ display: "block" }}
text={`Study Computer Science (free)`} text={`Study Computer Science (free)`}
tooltip={earnHackingExpTooltip} tooltip={earnHackingExpTooltip}
/> />
<StdButton <StdButton
onClick={this.dataStructures} onClick={dataStructures}
style={this.btnStyle} style={{ display: "block" }}
text={ text={
<> <>
Take Data Structures course ( Take Data Structures course (
<Money money={dataStructuresCost} player={this.props.p} /> / sec) <Money money={dataStructuresCost} player={player} /> / sec)
</> </>
} }
tooltip={earnHackingExpTooltip} tooltip={earnHackingExpTooltip}
/> />
<StdButton <StdButton
onClick={this.networks} onClick={networks}
style={this.btnStyle} style={{ display: "block" }}
text={ text={
<> <>
Take Networks course ( Take Networks course (
<Money money={networksCost} player={this.props.p} /> / sec) <Money money={networksCost} player={player} /> / sec)
</> </>
} }
tooltip={earnHackingExpTooltip} tooltip={earnHackingExpTooltip}
/> />
<StdButton <StdButton
onClick={this.algorithms} onClick={algorithms}
style={this.btnStyle} style={{ display: "block" }}
text={ text={
<> <>
Take Algorithms course ( Take Algorithms course (
<Money money={algorithmsCost} player={this.props.p} /> / sec) <Money money={algorithmsCost} player={player} /> / sec)
</> </>
} }
tooltip={earnHackingExpTooltip} tooltip={earnHackingExpTooltip}
/> />
<StdButton <StdButton
onClick={this.management} onClick={management}
style={this.btnStyle} style={{ display: "block" }}
text={ text={
<> <>
Take Management course ( Take Management course (
<Money money={managementCost} player={this.props.p} /> / sec) <Money money={managementCost} player={player} /> / sec)
</> </>
} }
tooltip={earnCharismaExpTooltip} tooltip={earnCharismaExpTooltip}
/> />
<StdButton <StdButton
onClick={this.leadership} onClick={leadership}
style={this.btnStyle} style={{ display: "block" }}
text={ text={
<> <>
Take Leadership course ( Take Leadership course (
<Money money={leadershipCost} player={this.props.p} /> / sec) <Money money={leadershipCost} player={player} /> / sec)
</> </>
} }
tooltip={earnCharismaExpTooltip} tooltip={earnCharismaExpTooltip}
/> />
</div> </div>
); );
}
} }

@ -1,6 +1,4 @@
import { CONSTANTS } from "./Constants"; import { CONSTANTS } from "./Constants";
import { Engine } from "./engine";
import { displayFactionContent } from "./Faction/FactionHelpers";
import { Player } from "./Player"; import { Player } from "./Player";
import { dialogBoxCreate } from "../utils/DialogBox"; import { dialogBoxCreate } from "../utils/DialogBox";
@ -12,6 +10,7 @@ import { getRandomInt } from "../utils/helpers/getRandomInt";
import { isString } from "../utils/helpers/isString"; import { isString } from "../utils/helpers/isString";
import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners"; import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners";
import { Router } from "./ui/GameRoot";
// For some reason `jsplumb` needs to be imported exactly like this, // For some reason `jsplumb` needs to be imported exactly like this,
// lowercase p, and later in the code used as `jsPlumb` uppercase P. wtf. // lowercase p, and later in the code used as `jsPlumb` uppercase P. wtf.
@ -1187,7 +1186,7 @@ HackingMission.prototype.process = function (numCycles = 1) {
}); });
// Update timer and check if player lost // Update timer and check if player lost
this.time -= storedCycles * Engine._idleSpeed; this.time -= storedCycles * CONSTANTS._idleSpeed;
if (this.time <= 0) { if (this.time <= 0) {
this.finishMission(false); this.finishMission(false);
return; return;
@ -1595,18 +1594,7 @@ HackingMission.prototype.finishMission = function (win) {
} else { } else {
dialogBoxCreate("Mission lost/forfeited! You did not gain any faction reputation."); dialogBoxCreate("Mission lost/forfeited! You did not gain any faction reputation.");
} }
Router.toFaction();
// Clear mission container
var container = document.getElementById("mission-container");
while (container.firstChild) {
container.removeChild(container.firstChild);
}
// Return to Faction page
document.getElementById("mainmenu-container").style.visibility = "visible";
document.getElementById("character-overview-wrapper").style.visibility = "visible";
Engine.loadFactionContent();
displayFactionContent(this.faction.name);
}; };
export { HackingMission, inMission, setInMission, currMission }; export { HackingMission, inMission, setInMission, currMission };

@ -3,4 +3,4 @@
*/ */
import { EventEmitter } from "../utils/EventEmitter"; import { EventEmitter } from "../utils/EventEmitter";
export const WorkerScriptStartStopEventEmitter = new EventEmitter(); export const WorkerScriptStartStopEventEmitter = new EventEmitter<[]>();

@ -116,7 +116,7 @@ function removeWorkerScript(workerScript: WorkerScript, rerenderUi = true): void
} }
if (rerenderUi) { if (rerenderUi) {
WorkerScriptStartStopEventEmitter.emitEvent(); WorkerScriptStartStopEventEmitter.emit();
} }
} else { } else {
console.error(`Invalid argument passed into removeWorkerScript():`); console.error(`Invalid argument passed into removeWorkerScript():`);

@ -119,13 +119,7 @@ import { SpecialServerIps } from "./Server/SpecialServerIps";
import { SourceFileFlags } from "./SourceFile/SourceFileFlags"; import { SourceFileFlags } from "./SourceFile/SourceFileFlags";
import { buyStock, sellStock, shortStock, sellShort } from "./StockMarket/BuyingAndSelling"; import { buyStock, sellStock, shortStock, sellShort } from "./StockMarket/BuyingAndSelling";
import { influenceStockThroughServerHack, influenceStockThroughServerGrow } from "./StockMarket/PlayerInfluencing"; import { influenceStockThroughServerHack, influenceStockThroughServerGrow } from "./StockMarket/PlayerInfluencing";
import { import { StockMarket, SymbolToStockMap, placeOrder, cancelOrder } from "./StockMarket/StockMarket";
StockMarket,
SymbolToStockMap,
placeOrder,
cancelOrder,
displayStockMarketContent,
} from "./StockMarket/StockMarket";
import { getBuyTransactionCost, getSellTransactionGain } from "./StockMarket/StockMarketHelpers"; import { getBuyTransactionCost, getSellTransactionGain } from "./StockMarket/StockMarketHelpers";
import { OrderTypes } from "./StockMarket/data/OrderTypes"; import { OrderTypes } from "./StockMarket/data/OrderTypes";
import { PositionTypes } from "./StockMarket/data/PositionTypes"; import { PositionTypes } from "./StockMarket/data/PositionTypes";
@ -142,7 +136,8 @@ import { Interpreter } from "./JSInterpreter";
import { NetscriptPort } from "./NetscriptPort"; import { NetscriptPort } from "./NetscriptPort";
import { SleeveTaskType } from "./PersonObjects/Sleeve/SleeveTaskTypesEnum"; import { SleeveTaskType } from "./PersonObjects/Sleeve/SleeveTaskTypesEnum";
import { findSleevePurchasableAugs } from "./PersonObjects/Sleeve/SleeveHelpers"; import { findSleevePurchasableAugs } from "./PersonObjects/Sleeve/SleeveHelpers";
import { Exploit } from "./Exploits/Exploit.ts"; import { Exploit } from "./Exploits/Exploit";
import { Router } from "./ui/GameRoot";
import { numeralWrapper } from "./ui/numeralFormat"; import { numeralWrapper } from "./ui/numeralFormat";
import { setTimeoutRef } from "./utils/SetTimeoutRef"; import { setTimeoutRef } from "./utils/SetTimeoutRef";
@ -1365,7 +1360,7 @@ function NetscriptFunctions(workerScript) {
for (let i = server.runningScripts.length - 1; i >= 0; --i) { for (let i = server.runningScripts.length - 1; i >= 0; --i) {
killWorkerScript(server.runningScripts[i], server.ip, false); killWorkerScript(server.runningScripts[i], server.ip, false);
} }
WorkerScriptStartStopEventEmitter.emitEvent(); WorkerScriptStartStopEventEmitter.emit();
workerScript.log( workerScript.log(
"killall", "killall",
`Killing all scripts on '${server.hostname}'. May take a few minutes for the scripts to die.`, `Killing all scripts on '${server.hostname}'. May take a few minutes for the scripts to die.`,
@ -1964,18 +1959,14 @@ function NetscriptFunctions(workerScript) {
updateDynamicRam("buyStock", getRamCost("buyStock")); updateDynamicRam("buyStock", getRamCost("buyStock"));
checkTixApiAccess("buyStock"); checkTixApiAccess("buyStock");
const stock = getStockFromSymbol(symbol, "buyStock"); const stock = getStockFromSymbol(symbol, "buyStock");
const res = buyStock(stock, shares, workerScript, { const res = buyStock(stock, shares, workerScript, {});
rerenderFn: displayStockMarketContent,
});
return res ? stock.price : 0; return res ? stock.price : 0;
}, },
sellStock: function (symbol, shares) { sellStock: function (symbol, shares) {
updateDynamicRam("sellStock", getRamCost("sellStock")); updateDynamicRam("sellStock", getRamCost("sellStock"));
checkTixApiAccess("sellStock"); checkTixApiAccess("sellStock");
const stock = getStockFromSymbol(symbol, "sellStock"); const stock = getStockFromSymbol(symbol, "sellStock");
const res = sellStock(stock, shares, workerScript, { const res = sellStock(stock, shares, workerScript, {});
rerenderFn: displayStockMarketContent,
});
return res ? stock.price : 0; return res ? stock.price : 0;
}, },
@ -1991,9 +1982,7 @@ function NetscriptFunctions(workerScript) {
} }
} }
const stock = getStockFromSymbol(symbol, "shortStock"); const stock = getStockFromSymbol(symbol, "shortStock");
const res = shortStock(stock, shares, workerScript, { const res = shortStock(stock, shares, workerScript, {});
rerenderFn: displayStockMarketContent,
});
return res ? stock.price : 0; return res ? stock.price : 0;
}, },
@ -2009,9 +1998,7 @@ function NetscriptFunctions(workerScript) {
} }
} }
const stock = getStockFromSymbol(symbol, "sellShort"); const stock = getStockFromSymbol(symbol, "sellShort");
const res = sellShort(stock, shares, workerScript, { const res = sellShort(stock, shares, workerScript, {});
rerenderFn: displayStockMarketContent,
});
return res ? stock.price : 0; return res ? stock.price : 0;
}, },
@ -2168,7 +2155,6 @@ function NetscriptFunctions(workerScript) {
Player.has4SData = true; Player.has4SData = true;
Player.loseMoney(getStockMarket4SDataCost()); Player.loseMoney(getStockMarket4SDataCost());
workerScript.log("purchase4SMarketData", "Purchased 4S Market Data"); workerScript.log("purchase4SMarketData", "Purchased 4S Market Data");
displayStockMarketContent();
return true; return true;
}, },
purchase4SMarketDataTixApi: function () { purchase4SMarketDataTixApi: function () {
@ -2188,7 +2174,6 @@ function NetscriptFunctions(workerScript) {
Player.has4SDataTixApi = true; Player.has4SDataTixApi = true;
Player.loseMoney(getStockMarket4STixApiCost()); Player.loseMoney(getStockMarket4STixApiCost());
workerScript.log("purchase4SMarketDataTixApi", "Purchased 4S Market Data TIX API"); workerScript.log("purchase4SMarketDataTixApi", "Purchased 4S Market Data TIX API");
displayStockMarketContent();
return true; return true;
}, },
getPurchasedServerLimit: function () { getPurchasedServerLimit: function () {
@ -2910,7 +2895,7 @@ function NetscriptFunctions(workerScript) {
workerScript.log("universityCourse", `Invalid class name: ${className}.`); workerScript.log("universityCourse", `Invalid class name: ${className}.`);
return false; return false;
} }
Player.startClass(costMult, expMult, task); Player.startClass(Router, costMult, expMult, task);
workerScript.log("universityCourse", `Started ${task} at ${universityName}`); workerScript.log("universityCourse", `Started ${task} at ${universityName}`);
return true; return true;
}, },
@ -2987,19 +2972,19 @@ function NetscriptFunctions(workerScript) {
switch (stat.toLowerCase()) { switch (stat.toLowerCase()) {
case "strength".toLowerCase(): case "strength".toLowerCase():
case "str".toLowerCase(): case "str".toLowerCase():
Player.startClass(costMult, expMult, CONSTANTS.ClassGymStrength); Player.startClass(Router, costMult, expMult, CONSTANTS.ClassGymStrength);
break; break;
case "defense".toLowerCase(): case "defense".toLowerCase():
case "def".toLowerCase(): case "def".toLowerCase():
Player.startClass(costMult, expMult, CONSTANTS.ClassGymDefense); Player.startClass(Router, costMult, expMult, CONSTANTS.ClassGymDefense);
break; break;
case "dexterity".toLowerCase(): case "dexterity".toLowerCase():
case "dex".toLowerCase(): case "dex".toLowerCase():
Player.startClass(costMult, expMult, CONSTANTS.ClassGymDexterity); Player.startClass(Router, costMult, expMult, CONSTANTS.ClassGymDexterity);
break; break;
case "agility".toLowerCase(): case "agility".toLowerCase():
case "agi".toLowerCase(): case "agi".toLowerCase():
Player.startClass(costMult, expMult, CONSTANTS.ClassGymAgility); Player.startClass(Router, costMult, expMult, CONSTANTS.ClassGymAgility);
break; break;
default: default:
workerScript.log("gymWorkout", `Invalid stat: ${stat}.`); workerScript.log("gymWorkout", `Invalid stat: ${stat}.`);
@ -3435,9 +3420,9 @@ function NetscriptFunctions(workerScript) {
} }
if (companyPosition.isPartTimeJob()) { if (companyPosition.isPartTimeJob()) {
Player.startWorkPartTime(companyName); Player.startWorkPartTime(Router, companyName);
} else { } else {
Player.startWork(companyName); Player.startWork(Router, companyName);
} }
workerScript.log("workForCompany", `Began working at '${Player.companyName}' as a '${companyPositionName}'`); workerScript.log("workForCompany", `Began working at '${Player.companyName}' as a '${companyPositionName}'`);
return true; return true;
@ -3676,7 +3661,7 @@ function NetscriptFunctions(workerScript) {
workerScript.log("workForFaction", `Faction '${fac.name}' do not need help with hacking contracts.`); workerScript.log("workForFaction", `Faction '${fac.name}' do not need help with hacking contracts.`);
return false; return false;
} }
Player.startFactionHackWork(fac); Player.startFactionHackWork(Router, fac);
workerScript.log("workForFaction", `Started carrying out hacking contracts for '${fac.name}'`); workerScript.log("workForFaction", `Started carrying out hacking contracts for '${fac.name}'`);
return true; return true;
case "field": case "field":
@ -3686,7 +3671,7 @@ function NetscriptFunctions(workerScript) {
workerScript.log("workForFaction", `Faction '${fac.name}' do not need help with field missions.`); workerScript.log("workForFaction", `Faction '${fac.name}' do not need help with field missions.`);
return false; return false;
} }
Player.startFactionFieldWork(fac); Player.startFactionFieldWork(Router, fac);
workerScript.log("workForFaction", `Started carrying out field missions for '${fac.name}'`); workerScript.log("workForFaction", `Started carrying out field missions for '${fac.name}'`);
return true; return true;
case "security": case "security":
@ -3696,7 +3681,7 @@ function NetscriptFunctions(workerScript) {
workerScript.log("workForFaction", `Faction '${fac.name}' do not need help with security work.`); workerScript.log("workForFaction", `Faction '${fac.name}' do not need help with security work.`);
return false; return false;
} }
Player.startFactionSecurityWork(fac); Player.startFactionSecurityWork(Router, fac);
workerScript.log("workForFaction", `Started carrying out security work for '${fac.name}'`); workerScript.log("workForFaction", `Started carrying out security work for '${fac.name}'`);
return true; return true;
default: default:
@ -3797,7 +3782,7 @@ function NetscriptFunctions(workerScript) {
return false; return false;
} }
Player.startCreateProgramWork(p.name, p.create.time, p.create.level); Player.startCreateProgramWork(Router, p.name, p.create.time, p.create.level);
workerScript.log("createProgram", `Began creating program: '${name}'`); workerScript.log("createProgram", `Began creating program: '${name}'`);
return true; return true;
}, },
@ -3822,7 +3807,7 @@ function NetscriptFunctions(workerScript) {
throw makeRuntimeErrorMsg("commitCrime", `Invalid crime: '${crimeRoughName}'`); throw makeRuntimeErrorMsg("commitCrime", `Invalid crime: '${crimeRoughName}'`);
} }
workerScript.log("commitCrime", `Attempting to commit ${crime.name}...`); workerScript.log("commitCrime", `Attempting to commit ${crime.name}...`);
return crime.commit(Player, 1, { workerscript: workerScript }); return crime.commit(Router, Player, 1, { workerscript: workerScript });
}, },
getCrimeChance: function (crimeRoughName) { getCrimeChance: function (crimeRoughName) {
updateDynamicRam("getCrimeChance", getRamCost("getCrimeChance")); updateDynamicRam("getCrimeChance", getRamCost("getCrimeChance"));

@ -9,7 +9,6 @@ import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStart
import { generateNextPid } from "./Netscript/Pid"; import { generateNextPid } from "./Netscript/Pid";
import { CONSTANTS } from "./Constants"; import { CONSTANTS } from "./Constants";
import { Engine } from "./engine";
import { Interpreter } from "./JSInterpreter"; import { Interpreter } from "./JSInterpreter";
import { isScriptErrorMessage, makeRuntimeRejectMsg } from "./NetscriptEvaluator"; import { isScriptErrorMessage, makeRuntimeRejectMsg } from "./NetscriptEvaluator";
import { NetscriptFunctions } from "./NetscriptFunctions"; import { NetscriptFunctions } from "./NetscriptFunctions";
@ -45,7 +44,7 @@ export function prestigeWorkerScripts() {
killWorkerScript(ws); killWorkerScript(ws);
} }
WorkerScriptStartStopEventEmitter.emitEvent(); WorkerScriptStartStopEventEmitter.emit();
workerScripts.clear(); workerScripts.clear();
} }
@ -501,7 +500,7 @@ export function createAndAddWorkerScript(runningScriptObj, server, parent) {
// Add the WorkerScript to the global pool // Add the WorkerScript to the global pool
workerScripts.set(pid, s); workerScripts.set(pid, s);
WorkerScriptStartStopEventEmitter.emitEvent(); WorkerScriptStartStopEventEmitter.emit();
// Start the script's execution // Start the script's execution
let p = null; // Script's resulting promise let p = null; // Script's resulting promise
@ -586,7 +585,7 @@ export function createAndAddWorkerScript(runningScriptObj, server, parent) {
* Updates the online running time stat of all running scripts * Updates the online running time stat of all running scripts
*/ */
export function updateOnlineScriptTimes(numCycles = 1) { export function updateOnlineScriptTimes(numCycles = 1) {
var time = (numCycles * Engine._idleSpeed) / 1000; //seconds var time = (numCycles * CONSTANTS._idleSpeed) / 1000; //seconds
for (const ws of workerScripts.values()) { for (const ws of workerScripts.values()) {
ws.scriptRef.onlineRunningTime += time; ws.scriptRef.onlineRunningTime += time;
} }

@ -25,6 +25,7 @@ import { ICorporation } from "../Corporation/ICorporation";
import { IGang } from "../Gang/IGang"; import { IGang } from "../Gang/IGang";
import { IBladeburner } from "../Bladeburner/IBladeburner"; import { IBladeburner } from "../Bladeburner/IBladeburner";
import { ICodingContractReward } from "../CodingContracts"; import { ICodingContractReward } from "../CodingContracts";
import { IRouter } from "../ui/Router";
export interface IPlayer { export interface IPlayer {
// Class members // Class members
@ -121,8 +122,33 @@ export interface IPlayer {
bladeburner_analysis_mult: number; bladeburner_analysis_mult: number;
bladeburner_success_chance_mult: number; bladeburner_success_chance_mult: number;
workRepGained: number; createProgramName: string;
timeWorkedCreateProgram: number;
crimeType: string;
timeNeededToCompleteWork: number;
focus: boolean; focus: boolean;
className: string;
currentWorkFactionName: string;
workType: string;
currentWorkFactionDescription: string;
timeWorked: number;
workMoneyGained: number;
workMoneyGainRate: number;
workRepGained: number;
workRepGainRate: number;
workHackExpGained: number;
workHackExpGainRate: number;
workStrExpGained: number;
workStrExpGainRate: number;
workDefExpGained: number;
workDefExpGainRate: number;
workDexExpGained: number;
workDexExpGainRate: number;
workAgiExpGained: number;
workAgiExpGainRate: number;
workChaExpGained: number;
workChaExpGainRate: number;
workMoneyLossRate: number;
// Methods // Methods
applyForAgentJob(sing?: boolean): boolean | void; applyForAgentJob(sing?: boolean): boolean | void;
@ -175,9 +201,10 @@ export interface IPlayer {
setMoney(amt: number): void; setMoney(amt: number): void;
singularityStopWork(): void; singularityStopWork(): void;
startBladeburner(p: any): void; startBladeburner(p: any): void;
startClass(costMult: number, expMult: number, className: string): void; startClass(router: IRouter, costMult: number, expMult: number, className: string): void;
startCorporation(corpName: string, additionalShares?: number): void; startCorporation(corpName: string, additionalShares?: number): void;
startCrime( startCrime(
router: IRouter,
crimeType: string, crimeType: string,
hackExp: number, hackExp: number,
strExp: number, strExp: number,
@ -189,13 +216,13 @@ export interface IPlayer {
time: number, time: number,
singParams: any, singParams: any,
): void; ): void;
startFactionFieldWork(faction: Faction): void; startFactionFieldWork(router: IRouter, faction: Faction): void;
startFactionHackWork(faction: Faction): void; startFactionHackWork(router: IRouter, faction: Faction): void;
startFactionSecurityWork(faction: Faction): void; startFactionSecurityWork(router: IRouter, faction: Faction): void;
startFocusing(): void; startFocusing(): void;
startGang(facName: string, isHacking: boolean): void; startGang(facName: string, isHacking: boolean): void;
startWork(companyName: string): void; startWork(router: IRouter, companyName: string): void;
startWorkPartTime(companyName: string): void; startWorkPartTime(router: IRouter, companyName: string): void;
takeDamage(amt: number): boolean; takeDamage(amt: number): boolean;
travel(to: CityName): boolean; travel(to: CityName): boolean;
giveExploit(exploit: Exploit): void; giveExploit(exploit: Exploit): void;
@ -204,9 +231,17 @@ export interface IPlayer {
getCasinoWinnings(): number; getCasinoWinnings(): number;
quitJob(company: string): void; quitJob(company: string): void;
createHacknetServer(): void; createHacknetServer(): void;
startCreateProgramWork(programName: string, time: number, reqLevel: number): void; startCreateProgramWork(router: IRouter, programName: string, time: number, reqLevel: number): void;
queueAugmentation(augmentationName: string): void; queueAugmentation(augmentationName: string): void;
receiveInvite(factionName: string): void; receiveInvite(factionName: string): void;
updateSkillLevels(): void; updateSkillLevels(): void;
gainCodingContractReward(reward: ICodingContractReward, difficulty?: number): string; gainCodingContractReward(reward: ICodingContractReward, difficulty?: number): string;
stopFocusing(): void;
finishFactionWork(cancelled: boolean, sing?: boolean): void;
finishClass(sing?: boolean): void;
finishWork(cancelled: boolean, sing?: boolean): void;
cancelationPenalty(): number;
finishWorkPartTime(sing?: boolean): void;
finishCrime(cancelled: boolean): void;
finishCreateProgramWork(cancelled: boolean): void;
} }

@ -14,10 +14,8 @@ import { CONSTANTS } from "../../Constants";
import { Programs } from "../../Programs/Programs"; import { Programs } from "../../Programs/Programs";
import { determineCrimeSuccess } from "../../Crime/CrimeHelpers"; import { determineCrimeSuccess } from "../../Crime/CrimeHelpers";
import { Crimes } from "../../Crime/Crimes"; import { Crimes } from "../../Crime/Crimes";
import { Engine } from "../../engine";
import { Faction } from "../../Faction/Faction"; import { Faction } from "../../Faction/Faction";
import { Factions } from "../../Faction/Factions"; import { Factions } from "../../Faction/Factions";
import { displayFactionContent } from "../../Faction/FactionHelpers";
import { resetGangs } from "../../Gang/AllGangs"; import { resetGangs } from "../../Gang/AllGangs";
import { hasHacknetServers } from "../../Hacknet/HacknetHelpers"; import { hasHacknetServers } from "../../Hacknet/HacknetHelpers";
import { Cities } from "../../Locations/Cities"; import { Cities } from "../../Locations/Cities";
@ -48,18 +46,12 @@ import Decimal from "decimal.js";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { MoneySourceTracker } from "../../utils/MoneySourceTracker"; import { MoneySourceTracker } from "../../utils/MoneySourceTracker";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { dialogBoxCreate } from "../../../utils/DialogBox";
import { clearEventListeners } from "../../../utils/uiHelpers/clearEventListeners";
import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions"; import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions";
import { Reputation } from "../../ui/React/Reputation"; import { Reputation } from "../../ui/React/Reputation";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { MoneyRate } from "../../ui/React/MoneyRate";
import { ReputationRate } from "../../ui/React/ReputationRate";
import React from "react"; import React from "react";
import ReactDOM from "react-dom";
const CYCLES_PER_SEC = 1000 / CONSTANTS.MilliPerCycle;
export function init() { export function init() {
/* Initialize Player's home computer */ /* Initialize Player's home computer */
@ -518,8 +510,6 @@ export function resetWorkStatus(generalType, group, workType) {
this.currentWorkFactionDescription = ""; this.currentWorkFactionDescription = "";
this.createProgramName = ""; this.createProgramName = "";
this.className = ""; this.className = "";
ReactDOM.unmountComponentAtNode(document.getElementById("work-in-progress-text"));
} }
export function processWorkEarnings(numCycles = 1) { export function processWorkEarnings(numCycles = 1) {
@ -556,7 +546,7 @@ export function processWorkEarnings(numCycles = 1) {
} }
/* Working for Company */ /* Working for Company */
export function startWork(companyName) { export function startWork(router, companyName) {
this.resetWorkStatus(CONSTANTS.WorkTypeCompany, companyName); this.resetWorkStatus(CONSTANTS.WorkTypeCompany, companyName);
this.isWorking = true; this.isWorking = true;
this.focus = true; this.focus = true;
@ -573,25 +563,7 @@ export function startWork(companyName) {
this.workMoneyGainRate = this.getWorkMoneyGain(); this.workMoneyGainRate = this.getWorkMoneyGain();
this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer8Hours; this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer8Hours;
router.toWork();
//Remove all old event listeners from Cancel button
var newCancelButton = clearEventListeners("work-in-progress-cancel-button");
newCancelButton.innerHTML = "Cancel Work";
newCancelButton.addEventListener("click", () => {
this.finishWork(true);
return false;
});
const focusButton = clearEventListeners("work-in-progress-something-else-button");
focusButton.style.visibility = "visible";
focusButton.innerHTML = "Do something else simultaneously";
focusButton.addEventListener("click", () => {
this.stopFocusing();
return false;
});
//Display Work In Progress Screen
Engine.loadWorkInProgressContent();
} }
export function cancelationPenalty() { export function cancelationPenalty() {
@ -607,78 +579,24 @@ export function work(numCycles) {
// Cap the number of cycles being processed to whatever would put you at // Cap the number of cycles being processed to whatever would put you at
// the work time limit (8 hours) // the work time limit (8 hours)
var overMax = false; var overMax = false;
if (this.timeWorked + Engine._idleSpeed * numCycles >= CONSTANTS.MillisecondsPer8Hours) { if (this.timeWorked + CONSTANTS._idleSpeed * numCycles >= CONSTANTS.MillisecondsPer8Hours) {
overMax = true; overMax = true;
numCycles = Math.round((CONSTANTS.MillisecondsPer8Hours - this.timeWorked) / Engine._idleSpeed); numCycles = Math.round((CONSTANTS.MillisecondsPer8Hours - this.timeWorked) / CONSTANTS._idleSpeed);
} }
this.timeWorked += Engine._idleSpeed * numCycles; this.timeWorked += CONSTANTS._idleSpeed * numCycles;
this.workRepGainRate = this.getWorkRepGain(); this.workRepGainRate = this.getWorkRepGain();
this.processWorkEarnings(numCycles); this.processWorkEarnings(numCycles);
const comp = Companies[this.companyName];
influenceStockThroughCompanyWork(comp, this.workRepGainRate, numCycles);
// If timeWorked == 8 hours, then finish. You can only gain 8 hours worth of exp and money // If timeWorked == 8 hours, then finish. You can only gain 8 hours worth of exp and money
if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer8Hours) { if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer8Hours) {
return this.finishWork(false); return this.finishWork(false);
return true;
} }
return false;
const comp = Companies[this.companyName];
let companyRep = "0";
if (comp == null || !(comp instanceof Company)) {
console.error(`Could not find Company: ${this.companyName}`);
} else {
companyRep = comp.playerReputation;
}
influenceStockThroughCompanyWork(comp, this.workRepGainRate, numCycles);
const position = this.jobs[this.companyName];
const penalty = this.cancelationPenalty();
const penaltyString = penalty === 0.5 ? "half" : "three-quarters";
var elem = document.getElementById("work-in-progress-text");
ReactDOM.render(
<>
You are currently working as a {position} at {this.companyName} (Current Company Reputation:{" "}
{Reputation(companyRep)})<br />
<br />
You have been working for {convertTimeMsToTimeElapsedString(this.timeWorked)}
<br />
<br />
You have earned: <br />
<br />
<Money money={this.workMoneyGained} /> ({MoneyRate(this.workMoneyGainRate * CYCLES_PER_SEC)}) <br />
<br />
{Reputation(this.workRepGained)} ({ReputationRate(this.workRepGainRate * CYCLES_PER_SEC)}) reputation for this
company <br />
<br />
{numeralWrapper.formatExp(this.workHackExpGained)} (
{`${numeralWrapper.formatExp(this.workHackExpGainRate * CYCLES_PER_SEC)} / sec`}
) hacking exp <br />
<br />
{numeralWrapper.formatExp(this.workStrExpGained)} (
{`${numeralWrapper.formatExp(this.workStrExpGainRate * CYCLES_PER_SEC)} / sec`}
) strength exp <br />
{numeralWrapper.formatExp(this.workDefExpGained)} (
{`${numeralWrapper.formatExp(this.workDefExpGainRate * CYCLES_PER_SEC)} / sec`}
) defense exp <br />
{numeralWrapper.formatExp(this.workDexExpGained)} (
{`${numeralWrapper.formatExp(this.workDexExpGainRate * CYCLES_PER_SEC)} / sec`}
) dexterity exp <br />
{numeralWrapper.formatExp(this.workAgiExpGained)} (
{`${numeralWrapper.formatExp(this.workAgiExpGainRate * CYCLES_PER_SEC)} / sec`}
) agility exp <br />
<br />
{numeralWrapper.formatExp(this.workChaExpGained)} (
{`${numeralWrapper.formatExp(this.workChaExpGainRate * CYCLES_PER_SEC)} / sec`}
) charisma exp <br />
<br />
You will automatically finish after working for 8 hours. You can cancel earlier if you wish, but you will only
gain {penaltyString} of the reputation you've earned so far.
</>,
elem,
);
} }
export function finishWork(cancelled, sing = false) { export function finishWork(cancelled, sing = false) {
@ -731,10 +649,7 @@ export function finishWork(cancelled, sing = false) {
dialogBoxCreate(content); dialogBoxCreate(content);
} }
var mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "visible";
this.isWorking = false; this.isWorking = false;
Engine.loadLocationContent(false);
if (sing) { if (sing) {
var res = var res =
@ -764,7 +679,7 @@ export function finishWork(cancelled, sing = false) {
this.resetWorkStatus(); this.resetWorkStatus();
} }
export function startWorkPartTime(companyName) { export function startWorkPartTime(router, companyName) {
this.resetWorkStatus(CONSTANTS.WorkTypeCompanyPartTime, companyName); this.resetWorkStatus(CONSTANTS.WorkTypeCompanyPartTime, companyName);
this.isWorking = true; this.isWorking = true;
this.focus = true; this.focus = true;
@ -781,27 +696,18 @@ export function startWorkPartTime(companyName) {
this.workMoneyGainRate = this.getWorkMoneyGain(); this.workMoneyGainRate = this.getWorkMoneyGain();
this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer8Hours; this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer8Hours;
router.toWork();
var newCancelButton = clearEventListeners("work-in-progress-cancel-button");
newCancelButton.innerHTML = "Stop Working";
newCancelButton.addEventListener("click", () => {
this.finishWorkPartTime();
return false;
});
//Display Work In Progress Screen
Engine.loadWorkInProgressContent();
} }
export function workPartTime(numCycles) { export function workPartTime(numCycles) {
//Cap the number of cycles being processed to whatever would put you at the //Cap the number of cycles being processed to whatever would put you at the
//work time limit (8 hours) //work time limit (8 hours)
var overMax = false; var overMax = false;
if (this.timeWorked + Engine._idleSpeed * numCycles >= CONSTANTS.MillisecondsPer8Hours) { if (this.timeWorked + CONSTANTS._idleSpeed * numCycles >= CONSTANTS.MillisecondsPer8Hours) {
overMax = true; overMax = true;
numCycles = Math.round((CONSTANTS.MillisecondsPer8Hours - this.timeWorked) / Engine._idleSpeed); numCycles = Math.round((CONSTANTS.MillisecondsPer8Hours - this.timeWorked) / CONSTANTS._idleSpeed);
} }
this.timeWorked += Engine._idleSpeed * numCycles; this.timeWorked += CONSTANTS._idleSpeed * numCycles;
this.workRepGainRate = this.getWorkRepGain(); this.workRepGainRate = this.getWorkRepGain();
this.processWorkEarnings(numCycles); this.processWorkEarnings(numCycles);
@ -809,61 +715,9 @@ export function workPartTime(numCycles) {
//If timeWorked == 8 hours, then finish. You can only gain 8 hours worth of exp and money //If timeWorked == 8 hours, then finish. You can only gain 8 hours worth of exp and money
if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer8Hours) { if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer8Hours) {
return this.finishWorkPartTime(); return this.finishWorkPartTime();
return true;
} }
return false;
var comp = Companies[this.companyName],
companyRep = "0";
if (comp == null || !(comp instanceof Company)) {
console.error(`Could not find Company: ${this.companyName}`);
} else {
companyRep = comp.playerReputation;
}
const position = this.jobs[this.companyName];
const elem = document.getElementById("work-in-progress-text");
ReactDOM.render(
<>
You are currently working as a {position} at {this.companyName} (Current Company Reputation:{" "}
{Reputation(companyRep)})<br />
<br />
You have been working for {convertTimeMsToTimeElapsedString(this.timeWorked)}
<br />
<br />
You have earned: <br />
<br />
<Money money={this.workMoneyGained} /> ({MoneyRate(this.workMoneyGainRate * CYCLES_PER_SEC)}) <br />
<br />
{Reputation(this.workRepGained)} (
{Reputation(`${numeralWrapper.formatExp(this.workRepGainRate * CYCLES_PER_SEC)} / sec`)}
) reputation for this company <br />
<br />
{numeralWrapper.formatExp(this.workHackExpGained)} (
{`${numeralWrapper.formatExp(this.workHackExpGainRate * CYCLES_PER_SEC)} / sec`}
) hacking exp <br />
<br />
{numeralWrapper.formatExp(this.workStrExpGained)} (
{`${numeralWrapper.formatExp(this.workStrExpGainRate * CYCLES_PER_SEC)} / sec`}
) strength exp <br />
{numeralWrapper.formatExp(this.workDefExpGained)} (
{`${numeralWrapper.formatExp(this.workDefExpGainRate * CYCLES_PER_SEC)} / sec`}
) defense exp <br />
{numeralWrapper.formatExp(this.workDexExpGained)} (
{`${numeralWrapper.formatExp(this.workDexExpGainRate * CYCLES_PER_SEC)} / sec`}
) dexterity exp <br />
{numeralWrapper.formatExp(this.workAgiExpGained)} (
{`${numeralWrapper.formatExp(this.workAgiExpGainRate * CYCLES_PER_SEC)} / sec`}
) agility exp <br />
<br />
{numeralWrapper.formatExp(this.workChaExpGained)} (
{`${numeralWrapper.formatExp(this.workChaExpGainRate * CYCLES_PER_SEC)} / sec`}
) charisma exp <br />
<br />
You will automatically finish after working for 8 hours. You can cancel earlier if you wish, and there will be no
penalty because this is a part-time job.
</>,
elem,
);
} }
export function finishWorkPartTime(sing = false) { export function finishWorkPartTime(sing = false) {
@ -894,10 +748,7 @@ export function finishWorkPartTime(sing = false) {
dialogBoxCreate(content); dialogBoxCreate(content);
} }
var mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "visible";
this.isWorking = false; this.isWorking = false;
Engine.loadLocationContent(false);
if (sing) { if (sing) {
var res = var res =
"You worked for " + "You worked for " +
@ -928,21 +779,15 @@ export function finishWorkPartTime(sing = false) {
} }
export function startFocusing() { export function startFocusing() {
const mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "hidden";
this.focus = true; this.focus = true;
Engine.loadWorkInProgressContent();
} }
export function stopFocusing() { export function stopFocusing() {
const mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "visible";
this.focus = false; this.focus = false;
Engine.loadTerminalContent();
} }
/* Working for Faction */ /* Working for Faction */
export function startFactionWork(faction) { export function startFactionWork(router, faction) {
//Update reputation gain rate to account for faction favor //Update reputation gain rate to account for faction favor
var favorMult = 1 + faction.favor / 100; var favorMult = 1 + faction.favor / 100;
if (isNaN(favorMult)) { if (isNaN(favorMult)) {
@ -957,27 +802,10 @@ export function startFactionWork(faction) {
this.currentWorkFactionName = faction.name; this.currentWorkFactionName = faction.name;
this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer20Hours; this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer20Hours;
router.toWork();
const cancelButton = clearEventListeners("work-in-progress-cancel-button");
cancelButton.innerHTML = "Stop Faction Work";
cancelButton.addEventListener("click", () => {
this.finishFactionWork(true);
return false;
});
const focusButton = clearEventListeners("work-in-progress-something-else-button");
focusButton.style.visibility = "visible";
focusButton.innerHTML = "Do something else simultaneously";
focusButton.addEventListener("click", () => {
this.stopFocusing();
return false;
});
//Display Work In Progress Screen
Engine.loadWorkInProgressContent();
} }
export function startFactionHackWork(faction) { export function startFactionHackWork(router, faction) {
this.resetWorkStatus(CONSTANTS.WorkTypeFaction, faction.name, CONSTANTS.FactionWorkHacking); this.resetWorkStatus(CONSTANTS.WorkTypeFaction, faction.name, CONSTANTS.FactionWorkHacking);
this.workHackExpGainRate = 0.15 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain; this.workHackExpGainRate = 0.15 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
@ -989,10 +817,10 @@ export function startFactionHackWork(faction) {
this.factionWorkType = CONSTANTS.FactionWorkHacking; this.factionWorkType = CONSTANTS.FactionWorkHacking;
this.currentWorkFactionDescription = "carrying out hacking contracts"; this.currentWorkFactionDescription = "carrying out hacking contracts";
this.startFactionWork(faction); this.startFactionWork(router, faction);
} }
export function startFactionFieldWork(faction) { export function startFactionFieldWork(router, faction) {
this.resetWorkStatus(CONSTANTS.WorkTypeFaction, faction.name, CONSTANTS.FactionWorkField); this.resetWorkStatus(CONSTANTS.WorkTypeFaction, faction.name, CONSTANTS.FactionWorkField);
this.workHackExpGainRate = 0.1 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain; this.workHackExpGainRate = 0.1 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
@ -1006,10 +834,10 @@ export function startFactionFieldWork(faction) {
this.factionWorkType = CONSTANTS.FactionWorkField; this.factionWorkType = CONSTANTS.FactionWorkField;
this.currentWorkFactionDescription = "carrying out field missions"; this.currentWorkFactionDescription = "carrying out field missions";
this.startFactionWork(faction); this.startFactionWork(router, faction);
} }
export function startFactionSecurityWork(faction) { export function startFactionSecurityWork(router, faction) {
this.resetWorkStatus(CONSTANTS.WorkTypeFaction, faction.name, CONSTANTS.FactionWorkSecurity); this.resetWorkStatus(CONSTANTS.WorkTypeFaction, faction.name, CONSTANTS.FactionWorkSecurity);
this.workHackExpGainRate = 0.05 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain; this.workHackExpGainRate = 0.05 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
@ -1023,7 +851,7 @@ export function startFactionSecurityWork(faction) {
this.factionWorkType = CONSTANTS.FactionWorkSecurity; this.factionWorkType = CONSTANTS.FactionWorkSecurity;
this.currentWorkFactionDescription = "performing security detail"; this.currentWorkFactionDescription = "performing security detail";
this.startFactionWork(faction); this.startFactionWork(router, faction);
} }
export function workForFaction(numCycles) { export function workForFaction(numCycles) {
@ -1046,56 +874,20 @@ export function workForFaction(numCycles) {
//Cap the number of cycles being processed to whatever would put you at limit (20 hours) //Cap the number of cycles being processed to whatever would put you at limit (20 hours)
var overMax = false; var overMax = false;
if (this.timeWorked + Engine._idleSpeed * numCycles >= CONSTANTS.MillisecondsPer20Hours) { if (this.timeWorked + CONSTANTS._idleSpeed * numCycles >= CONSTANTS.MillisecondsPer20Hours) {
overMax = true; overMax = true;
numCycles = Math.round((CONSTANTS.MillisecondsPer20Hours - this.timeWorked) / Engine._idleSpeed); numCycles = Math.round((CONSTANTS.MillisecondsPer20Hours - this.timeWorked) / CONSTANTS._idleSpeed);
} }
this.timeWorked += Engine._idleSpeed * numCycles; this.timeWorked += CONSTANTS._idleSpeed * numCycles;
this.processWorkEarnings(numCycles); this.processWorkEarnings(numCycles);
//If timeWorked == 20 hours, then finish. You can only work for the faction for 20 hours //If timeWorked == 20 hours, then finish. You can only work for the faction for 20 hours
if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer20Hours) { if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer20Hours) {
return this.finishFactionWork(false); this.finishFactionWork(false);
return true;
} }
return false;
const elem = document.getElementById("work-in-progress-text");
ReactDOM.render(
<>
You are currently {this.currentWorkFactionDescription} for your faction {faction.name}
<br />
(Current Faction Reputation: {Reputation(faction.playerReputation)}). <br />
You have been doing this for {convertTimeMsToTimeElapsedString(this.timeWorked)}
<br />
<br />
You have earned: <br />
<br />
<Money money={this.workMoneyGained} /> ({MoneyRate(this.workMoneyGainRate * CYCLES_PER_SEC)}) <br />
<br />
{Reputation(this.workRepGained)} ({ReputationRate(this.workRepGainRate * CYCLES_PER_SEC)}) reputation for this
faction <br />
<br />
{numeralWrapper.formatExp(this.workHackExpGained)} (
{numeralWrapper.formatExp(this.workHackExpGainRate * CYCLES_PER_SEC)} / sec) hacking exp <br />
<br />
{numeralWrapper.formatExp(this.workStrExpGained)} (
{numeralWrapper.formatExp(this.workStrExpGainRate * CYCLES_PER_SEC)} / sec) strength exp <br />
{numeralWrapper.formatExp(this.workDefExpGained)} (
{numeralWrapper.formatExp(this.workDefExpGainRate * CYCLES_PER_SEC)} / sec) defense exp <br />
{numeralWrapper.formatExp(this.workDexExpGained)} (
{numeralWrapper.formatExp(this.workDexExpGainRate * CYCLES_PER_SEC)} / sec) dexterity exp <br />
{numeralWrapper.formatExp(this.workAgiExpGained)} (
{numeralWrapper.formatExp(this.workAgiExpGainRate * CYCLES_PER_SEC)} / sec) agility exp <br />
<br />
{numeralWrapper.formatExp(this.workChaExpGained)} (
{numeralWrapper.formatExp(this.workChaExpGainRate * CYCLES_PER_SEC)} / sec) charisma exp <br />
<br />
You will automatically finish after working for 20 hours. You can cancel earlier if you wish.
<br />
There is no penalty for cancelling earlier.
</>,
elem,
);
} }
export function finishFactionWork(cancelled, sing = false) { export function finishFactionWork(cancelled, sing = false) {
@ -1125,13 +917,8 @@ export function finishFactionWork(cancelled, sing = false) {
); );
} }
var mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "visible";
this.isWorking = false; this.isWorking = false;
Engine.loadFactionContent();
displayFactionContent(faction.name);
if (sing) { if (sing) {
var res = var res =
"You worked for your faction " + "You worked for your faction " +
@ -1387,7 +1174,7 @@ export function getWorkRepGain() {
// } // }
/* Creating a Program */ /* Creating a Program */
export function startCreateProgramWork(programName, time, reqLevel) { export function startCreateProgramWork(router, programName, time, reqLevel) {
this.resetWorkStatus(); this.resetWorkStatus();
this.isWorking = true; this.isWorking = true;
this.focus = true; this.focus = true;
@ -1419,19 +1206,7 @@ export function startCreateProgramWork(programName, time, reqLevel) {
} }
this.createProgramName = programName; this.createProgramName = programName;
router.toWork();
var cancelButton = clearEventListeners("work-in-progress-cancel-button");
cancelButton.innerHTML = "Cancel work on creating program";
cancelButton.addEventListener("click", () => {
this.finishCreateProgramWork(true);
return false;
});
const focusButton = clearEventListeners("work-in-progress-something-else-button");
focusButton.style.visibility = "hidden";
//Display Work In Progress Screen
Engine.loadWorkInProgressContent();
} }
export function createProgramWork(numCycles) { export function createProgramWork(numCycles) {
@ -1441,28 +1216,14 @@ export function createProgramWork(numCycles) {
skillMult = 1 + (skillMult - 1) / 5; //The divider constant can be adjusted as necessary skillMult = 1 + (skillMult - 1) / 5; //The divider constant can be adjusted as necessary
//Skill multiplier directly applied to "time worked" //Skill multiplier directly applied to "time worked"
this.timeWorked += Engine._idleSpeed * numCycles; this.timeWorked += CONSTANTS._idleSpeed * numCycles;
this.timeWorkedCreateProgram += Engine._idleSpeed * numCycles * skillMult; this.timeWorkedCreateProgram += CONSTANTS._idleSpeed * numCycles * skillMult;
var programName = this.createProgramName;
if (this.timeWorkedCreateProgram >= this.timeNeededToCompleteWork) { if (this.timeWorkedCreateProgram >= this.timeNeededToCompleteWork) {
this.finishCreateProgramWork(false); this.finishCreateProgramWork(false);
return true;
} }
return false;
const elem = document.getElementById("work-in-progress-text");
ReactDOM.render(
<>
You are currently working on coding {programName}.<br />
<br />
You have been working for {convertTimeMsToTimeElapsedString(this.timeWorked)}
<br />
<br />
The program is {((this.timeWorkedCreateProgram / this.timeNeededToCompleteWork) * 100).toFixed(2)}
% complete. <br />
If you cancel, your work will be saved and you can come back to complete the program later.
</>,
elem,
);
} }
export function finishCreateProgramWork(cancelled) { export function finishCreateProgramWork(cancelled) {
@ -1483,17 +1244,13 @@ export function finishCreateProgramWork(cancelled) {
this.gainIntelligenceExp(this.createProgramReqLvl / CONSTANTS.IntelligenceProgramBaseExpGain); this.gainIntelligenceExp(this.createProgramReqLvl / CONSTANTS.IntelligenceProgramBaseExpGain);
} }
var mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "visible";
this.isWorking = false; this.isWorking = false;
Engine.loadTerminalContent();
this.resetWorkStatus(); this.resetWorkStatus();
} }
/* Studying/Taking Classes */ /* Studying/Taking Classes */
export function startClass(costMult, expMult, className) { export function startClass(router, costMult, expMult, className) {
this.resetWorkStatus(); this.resetWorkStatus();
this.isWorking = true; this.isWorking = true;
this.focus = true; this.focus = true;
@ -1501,7 +1258,7 @@ export function startClass(costMult, expMult, className) {
this.className = className; this.className = className;
const gameCPS = 1000 / Engine._idleSpeed; const gameCPS = 1000 / CONSTANTS._idleSpeed;
//Find cost and exp gain per game cycle //Find cost and exp gain per game cycle
var cost = 0; var cost = 0;
@ -1564,62 +1321,13 @@ export function startClass(costMult, expMult, className) {
this.workDexExpGainRate = dexExp * this.dexterity_exp_mult * BitNodeMultipliers.ClassGymExpGain; this.workDexExpGainRate = dexExp * this.dexterity_exp_mult * BitNodeMultipliers.ClassGymExpGain;
this.workAgiExpGainRate = agiExp * this.agility_exp_mult * BitNodeMultipliers.ClassGymExpGain; this.workAgiExpGainRate = agiExp * this.agility_exp_mult * BitNodeMultipliers.ClassGymExpGain;
this.workChaExpGainRate = chaExp * this.charisma_exp_mult * BitNodeMultipliers.ClassGymExpGain; this.workChaExpGainRate = chaExp * this.charisma_exp_mult * BitNodeMultipliers.ClassGymExpGain;
router.toWork();
var cancelButton = clearEventListeners("work-in-progress-cancel-button");
if (
className == CONSTANTS.ClassGymStrength ||
className == CONSTANTS.ClassGymDefense ||
className == CONSTANTS.ClassGymDexterity ||
className == CONSTANTS.ClassGymAgility
) {
cancelButton.innerHTML = "Stop training at gym";
} else {
cancelButton.innerHTML = "Stop taking course";
}
cancelButton.addEventListener("click", () => {
this.finishClass();
return false;
});
const focusButton = clearEventListeners("work-in-progress-something-else-button");
focusButton.style.visibility = "hidden";
//Display Work In Progress Screen
Engine.loadWorkInProgressContent();
} }
export function takeClass(numCycles) { export function takeClass(numCycles) {
this.timeWorked += Engine._idleSpeed * numCycles; this.timeWorked += CONSTANTS._idleSpeed * numCycles;
var className = this.className;
this.processWorkEarnings(numCycles); this.processWorkEarnings(numCycles);
return false;
const elem = document.getElementById("work-in-progress-text");
ReactDOM.render(
<>
You have been {className} for {convertTimeMsToTimeElapsedString(this.timeWorked)}
<br />
<br />
This has cost you: <br />
<Money money={-this.workMoneyGained} /> ({MoneyRate(this.workMoneyLossRate * CYCLES_PER_SEC)}) <br />
<br />
You have gained: <br />
{numeralWrapper.formatExp(this.workHackExpGained)} (
{numeralWrapper.formatExp(this.workHackExpGainRate * CYCLES_PER_SEC)} / sec) hacking exp <br />
{numeralWrapper.formatExp(this.workStrExpGained)} (
{numeralWrapper.formatExp(this.workStrExpGainRate * CYCLES_PER_SEC)} / sec) strength exp <br />
{numeralWrapper.formatExp(this.workDefExpGained)} (
{numeralWrapper.formatExp(this.workDefExpGainRate * CYCLES_PER_SEC)} / sec) defense exp <br />
{numeralWrapper.formatExp(this.workDexExpGained)} (
{numeralWrapper.formatExp(this.workDexExpGainRate * CYCLES_PER_SEC)} / sec) dexterity exp <br />
{numeralWrapper.formatExp(this.workAgiExpGained)} (
{numeralWrapper.formatExp(this.workAgiExpGainRate * CYCLES_PER_SEC)} / sec) agility exp <br />
{numeralWrapper.formatExp(this.workChaExpGained)} (
{numeralWrapper.formatExp(this.workChaExpGainRate * CYCLES_PER_SEC)} / sec) charisma exp <br />
You may cancel at any time
</>,
elem,
);
} }
//The 'sing' argument defines whether or not this function was called //The 'sing' argument defines whether or not this function was called
@ -1650,12 +1358,8 @@ export function finishClass(sing = false) {
); );
} }
var mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "visible";
this.isWorking = false; this.isWorking = false;
Engine.loadLocationContent(false);
if (sing) { if (sing) {
var res = var res =
"After " + "After " +
@ -1686,7 +1390,19 @@ export function finishClass(sing = false) {
} }
//The EXP and $ gains are hardcoded. Time is in ms //The EXP and $ gains are hardcoded. Time is in ms
export function startCrime(crimeType, hackExp, strExp, defExp, dexExp, agiExp, chaExp, money, time, singParams = null) { export function startCrime(
router,
crimeType,
hackExp,
strExp,
defExp,
dexExp,
agiExp,
chaExp,
money,
time,
singParams = null,
) {
this.crimeType = crimeType; this.crimeType = crimeType;
this.resetWorkStatus(); this.resetWorkStatus();
@ -1708,49 +1424,17 @@ export function startCrime(crimeType, hackExp, strExp, defExp, dexExp, agiExp, c
this.workMoneyGained = money * this.crime_money_mult * BitNodeMultipliers.CrimeMoney; this.workMoneyGained = money * this.crime_money_mult * BitNodeMultipliers.CrimeMoney;
this.timeNeededToCompleteWork = time; this.timeNeededToCompleteWork = time;
router.toWork();
//Remove all old event listeners from Cancel button
const newCancelButton = clearEventListeners("work-in-progress-cancel-button");
newCancelButton.innerHTML = "Cancel crime";
newCancelButton.addEventListener("click", () => {
this.finishCrime(true);
return false;
});
const focusButton = clearEventListeners("work-in-progress-something-else-button");
focusButton.style.visibility = "hidden";
//Display Work In Progress Screen
Engine.loadWorkInProgressContent();
} }
export function commitCrime(numCycles) { export function commitCrime(numCycles) {
this.timeWorked += Engine._idleSpeed * numCycles; this.timeWorked += CONSTANTS._idleSpeed * numCycles;
if (this.timeWorked >= this.timeNeededToCompleteWork) { if (this.timeWorked >= this.timeNeededToCompleteWork) {
this.finishCrime(false); this.finishCrime(false);
return; return true;
} }
return false;
var percent = Math.round((this.timeWorked / this.timeNeededToCompleteWork) * 100);
var numBars = Math.round(percent / 5);
if (numBars < 0) {
numBars = 0;
}
if (numBars > 20) {
numBars = 20;
}
var progressBar = "[" + Array(numBars + 1).join("|") + Array(20 - numBars + 1).join(" ") + "]";
var txt = document.getElementById("work-in-progress-text");
txt.innerHTML =
"You are attempting to " +
this.crimeType +
".<br>" +
"Time remaining: " +
convertTimeMsToTimeElapsedString(this.timeNeededToCompleteWork - this.timeWorked) +
"<br>" +
progressBar.replace(/ /g, "&nbsp;");
} }
export function finishCrime(cancelled) { export function finishCrime(cancelled) {
@ -1892,11 +1576,9 @@ export function finishCrime(cancelled) {
} }
this.committingCrimeThruSingFn = false; this.committingCrimeThruSingFn = false;
this.singFnCrimeWorkerScript = null; this.singFnCrimeWorkerScript = null;
var mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "visible";
this.isWorking = false; this.isWorking = false;
this.crimeType = "";
this.resetWorkStatus(); this.resetWorkStatus();
Engine.loadLocationContent(false);
} }
//Cancels the player's current "work" assignment and gives the proper rewards //Cancels the player's current "work" assignment and gives the proper rewards

@ -11,8 +11,8 @@ interface IProps {
export function MoreEarningsContent(props: IProps): React.ReactElement { export function MoreEarningsContent(props: IProps): React.ReactElement {
return ( return (
<> <>
{StatsTable( <StatsTable
[ rows={[
["Money ", <Money money={props.sleeve.earningsForTask.money} />], ["Money ", <Money money={props.sleeve.earningsForTask.money} />],
["Hacking Exp ", numeralWrapper.formatExp(props.sleeve.earningsForTask.hack)], ["Hacking Exp ", numeralWrapper.formatExp(props.sleeve.earningsForTask.hack)],
["Strength Exp ", numeralWrapper.formatExp(props.sleeve.earningsForTask.str)], ["Strength Exp ", numeralWrapper.formatExp(props.sleeve.earningsForTask.str)],
@ -20,12 +20,12 @@ export function MoreEarningsContent(props: IProps): React.ReactElement {
["Dexterity Exp ", numeralWrapper.formatExp(props.sleeve.earningsForTask.dex)], ["Dexterity Exp ", numeralWrapper.formatExp(props.sleeve.earningsForTask.dex)],
["Agility Exp ", numeralWrapper.formatExp(props.sleeve.earningsForTask.agi)], ["Agility Exp ", numeralWrapper.formatExp(props.sleeve.earningsForTask.agi)],
["Charisma Exp ", numeralWrapper.formatExp(props.sleeve.earningsForTask.cha)], ["Charisma Exp ", numeralWrapper.formatExp(props.sleeve.earningsForTask.cha)],
], ]}
"Earnings for Current Task:", title="Earnings for Current Task:"
)} />
<br /> <br />
{StatsTable( <StatsTable
[ rows={[
["Money: ", <Money money={props.sleeve.earningsForPlayer.money} />], ["Money: ", <Money money={props.sleeve.earningsForPlayer.money} />],
["Hacking Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForPlayer.hack)], ["Hacking Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForPlayer.hack)],
["Strength Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForPlayer.str)], ["Strength Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForPlayer.str)],
@ -33,12 +33,12 @@ export function MoreEarningsContent(props: IProps): React.ReactElement {
["Dexterity Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForPlayer.dex)], ["Dexterity Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForPlayer.dex)],
["Agility Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForPlayer.agi)], ["Agility Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForPlayer.agi)],
["Charisma Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForPlayer.cha)], ["Charisma Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForPlayer.cha)],
], ]}
"Total Earnings for Host Consciousness:", title="Total Earnings for Host Consciousness:"
)} />
<br /> <br />
{StatsTable( <StatsTable
[ rows={[
["Money: ", <Money money={props.sleeve.earningsForSleeves.money} />], ["Money: ", <Money money={props.sleeve.earningsForSleeves.money} />],
["Hacking Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForSleeves.hack)], ["Hacking Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForSleeves.hack)],
["Strength Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForSleeves.str)], ["Strength Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForSleeves.str)],
@ -46,9 +46,9 @@ export function MoreEarningsContent(props: IProps): React.ReactElement {
["Dexterity Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForSleeves.dex)], ["Dexterity Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForSleeves.dex)],
["Agility Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForSleeves.agi)], ["Agility Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForSleeves.agi)],
["Charisma Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForSleeves.cha)], ["Charisma Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForSleeves.cha)],
], ]}
"Total Earnings for Other Sleeves:", title="Total Earnings for Other Sleeves:"
)} />
<br /> <br />
</> </>
); );

@ -10,20 +10,20 @@ interface IProps {
export function MoreStatsContent(props: IProps): React.ReactElement { export function MoreStatsContent(props: IProps): React.ReactElement {
return ( return (
<> <>
{StatsTable( <StatsTable
[ rows={[
["Hacking: ", props.sleeve.hacking_skill, `(${numeralWrapper.formatExp(props.sleeve.hacking_exp)} exp)`], ["Hacking: ", props.sleeve.hacking_skill, `(${numeralWrapper.formatExp(props.sleeve.hacking_exp)} exp)`],
["Strength: ", props.sleeve.strength, `(${numeralWrapper.formatExp(props.sleeve.strength_exp)} exp)`], ["Strength: ", props.sleeve.strength, `(${numeralWrapper.formatExp(props.sleeve.strength_exp)} exp)`],
["Defense: ", props.sleeve.defense, `(${numeralWrapper.formatExp(props.sleeve.defense_exp)} exp)`], ["Defense: ", props.sleeve.defense, `(${numeralWrapper.formatExp(props.sleeve.defense_exp)} exp)`],
["Dexterity: ", props.sleeve.dexterity, `(${numeralWrapper.formatExp(props.sleeve.dexterity_exp)} exp)`], ["Dexterity: ", props.sleeve.dexterity, `(${numeralWrapper.formatExp(props.sleeve.dexterity_exp)} exp)`],
["Agility: ", props.sleeve.agility, `(${numeralWrapper.formatExp(props.sleeve.agility_exp)} exp)`], ["Agility: ", props.sleeve.agility, `(${numeralWrapper.formatExp(props.sleeve.agility_exp)} exp)`],
["Charisma: ", props.sleeve.charisma, `(${numeralWrapper.formatExp(props.sleeve.charisma_exp)} exp)`], ["Charisma: ", props.sleeve.charisma, `(${numeralWrapper.formatExp(props.sleeve.charisma_exp)} exp)`],
], ]}
"Stats:", title="Stats:"
)} />
<br /> <br />
{StatsTable( <StatsTable
[ rows={[
["Hacking Level multiplier: ", numeralWrapper.formatPercentage(props.sleeve.hacking_mult)], ["Hacking Level multiplier: ", numeralWrapper.formatPercentage(props.sleeve.hacking_mult)],
["Hacking Experience multiplier: ", numeralWrapper.formatPercentage(props.sleeve.hacking_exp_mult)], ["Hacking Experience multiplier: ", numeralWrapper.formatPercentage(props.sleeve.hacking_exp_mult)],
["Strength Level multiplier: ", numeralWrapper.formatPercentage(props.sleeve.strength_mult)], ["Strength Level multiplier: ", numeralWrapper.formatPercentage(props.sleeve.strength_mult)],
@ -41,9 +41,9 @@ export function MoreStatsContent(props: IProps): React.ReactElement {
["Salary multiplier: ", numeralWrapper.formatPercentage(props.sleeve.work_money_mult)], ["Salary multiplier: ", numeralWrapper.formatPercentage(props.sleeve.work_money_mult)],
["Crime Money multiplier: ", numeralWrapper.formatPercentage(props.sleeve.crime_money_mult)], ["Crime Money multiplier: ", numeralWrapper.formatPercentage(props.sleeve.crime_money_mult)],
["Crime Success multiplier: ", numeralWrapper.formatPercentage(props.sleeve.crime_success_mult)], ["Crime Success multiplier: ", numeralWrapper.formatPercentage(props.sleeve.crime_success_mult)],
], ]}
"Multipliers:", title="Multipliers:"
)} />
</> </>
); );
} }

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