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 {
> p {
font-size: $defaultFontSize * 0.875;
width: 70%;
}
}

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

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

@ -4,7 +4,7 @@
@import "reset";
:root {
--my-font-color: #6f3;
--my-font-color: #0c0;
--my-background-color: #000;
--my-highlight-color: #fff;
--my-prompt-color: #f92672;
@ -331,7 +331,7 @@ a:visited {
#status-text-container {
background-color: transparent;
position: absolute;
position: fixed;
top: 0;
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,
"screenshotsFolder": ".cypress/screenshots",
"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", () => {
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.findByRole("textbox").type("connect n00dles{enter}");
@ -32,7 +32,7 @@ describe("netscript", () => {
});
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.findByRole("textbox").type("nano script.js{enter}");

@ -3,19 +3,19 @@ export {};
describe("tutorial", () => {
it("completes the tutorial", () => {
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.findByRole("button", { name: "Stats" }).click();
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.findByRole("button", { name: "Terminal" }).click();
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.findByRole("textbox").type("help{enter}");
@ -29,7 +29,7 @@ describe("tutorial", () => {
cy.findByText(/that's great and all/i);
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.findByText(/now you can see information/i);
@ -46,10 +46,11 @@ describe("tutorial", () => {
cy.findByRole("textbox").type("hack{enter}");
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("nano n00dles.script{enter}");
cy.findByRole("textbox", { timeout: 15_000 }).should("not.be.disabled").type("home{enter}");
cy.findByRole("textbox").type("nano n00dles.script{enter}");
// monaco can take a bit
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.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.findByText(/Your script is now running/i);
@ -72,7 +73,7 @@ describe("tutorial", () => {
cy.findByRole("textbox").type("tail n00dles.script{enter}");
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.findByRole("button", { name: "Hacknet" }).click();
@ -87,7 +88,7 @@ describe("tutorial", () => {
cy.findByRole("button", { name: "Tutorial" }).click();
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(/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

119
dist/engineStyle.css vendored

@ -15,7 +15,7 @@
vertical-align: middle; }
:root {
--my-font-color: #6f3;
--my-font-color: #0c0;
--my-background-color: #000;
--my-highlight-color: #fff;
--my-prompt-color: #f92672; }
@ -573,7 +573,7 @@ input[type="checkbox"] {
vertical-align: middle; }
:root {
--my-font-color: #6f3;
--my-font-color: #0c0;
--my-background-color: #000;
--my-highlight-color: #fff;
--my-prompt-color: #f92672; }
@ -1374,115 +1374,13 @@ button {
align-items: center;
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 */
/* Attributes */
/**
* Styling for the Hacknet Nodes UI Page
*/
.hacknet-general-info {
margin: 10px;
width: 70vw; }
margin: 10px; }
#hacknet-nodes-container li {
float: left;
@ -1650,8 +1548,7 @@ button {
/* COLORS */
/* Attributes */
.augmentations-content > p {
font-size: 14px;
width: 70%; }
font-size: 14px; }
.augmentations-list button,
.augmentations-list div {
@ -1824,7 +1721,7 @@ button {
vertical-align: middle; }
:root {
--my-font-color: #6f3;
--my-font-color: #0c0;
--my-background-color: #000;
--my-highlight-color: #fff;
--my-prompt-color: #f92672; }
@ -2691,11 +2588,11 @@ input[type="checkbox"] {
width: 100%;
height: 100%;
background: #000;
color: #6f3; }
color: #0c0; }
.loaderoverlay .loaderspinner, .loaderoverlay .loaderspinner:before, .loaderoverlay .loaderspinner:after {
border: 20px solid rgba(0, 0, 0, 0);
border-top-color: #6f3;
border-bottom-color: #6f3;
border-top-color: #0c0;
border-bottom-color: #0c0;
border-radius: 1000px;
position: absolute;
top: 50%;

81
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

@ -3,6 +3,50 @@
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)
-------------------------------------------

@ -64,9 +64,9 @@ documentation_title = '{0} Documentation'.format(project)
# built documents.
#
# The short X.Y version.
version = '0.53'
version = '0.54'
# 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
# for a list of supported languages.

@ -117,30 +117,6 @@ that are loaded.
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)**
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
(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,
so running ``commitCrime('rob store')`` will return 60).
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 60000).
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.

@ -36,46 +36,10 @@
ga("send", "pageview");
</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>
<div id="entire-game-container" style="visibility: hidden">
<div id="mainmenu-container" style="display: flex; flex-direction: row">
<!-- 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>
<div id="entire-game-container">
<div id="mainmenu-container" style="display: flex; flex-direction: row"></div>
<!-- Status text -->
<div id="status-text-container">
@ -83,33 +47,8 @@
</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>
<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>
</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-react": "^7.0.0",
"@babel/preset-typescript": "^7.15.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.1",
"@testing-library/cypress": "^8.0.1",
"@types/file-saver": "^2.0.3",
"@types/jest": "^27.0.1",
@ -102,6 +103,7 @@
"null-loader": "^1.0.0",
"prettier": "^2.3.2",
"raw-loader": "~0.5.0",
"react-refresh": "^0.10.0",
"regenerator-runtime": "^0.13.9",
"sass-loader": "^7.0.3",
"script-loader": "~0.7.0",
@ -2465,6 +2467,15 @@
"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": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.0.tgz",
@ -3914,6 +3925,240 @@
"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": {
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.10.1.tgz",
@ -4204,9 +4449,9 @@
"integrity": "sha512-crV/441NhrynLIclg94i1wV6nX/6rU9ByUyn4muCrsL0HPd3nBzrt6kpQ9MQOB+HeYgLcRARteNJcbnYkp5OwA=="
},
"node_modules/@types/json-schema": {
"version": "7.0.7",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz",
"integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==",
"version": "7.0.9",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
"integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
"dev": true
},
"node_modules/@types/lodash": {
@ -5266,6 +5511,18 @@
"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": {
"version": "2.1.1",
"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==",
"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": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz",
@ -9544,6 +9807,15 @@
"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": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz",
@ -21846,6 +22118,15 @@
"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": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz",
@ -21951,6 +22232,14 @@
"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": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
@ -23784,6 +24073,12 @@
"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": {
"version": "1.14.0",
"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": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
"integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.3.2.tgz",
"integrity": "sha512-cfvZ1nOC/VqAt8bVOIlFz8x+HdDASpiFYrSi0U0nzcAFlOnzzQ/gsPg2PP1uqjreO7sQCtraYJHMduXSewQsSA==",
"dev": true,
"optional": true,
"peer": true,
"engines": {
"node": ">=8"
"node": ">=12.20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/type-is": {
@ -27969,6 +28270,18 @@
"buffer-crc32": "~0.2.3",
"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": {
@ -29655,6 +29968,12 @@
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"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"
}
},
"@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": {
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.10.1.tgz",
@ -30899,9 +31366,9 @@
"integrity": "sha512-crV/441NhrynLIclg94i1wV6nX/6rU9ByUyn4muCrsL0HPd3nBzrt6kpQ9MQOB+HeYgLcRARteNJcbnYkp5OwA=="
},
"@types/json-schema": {
"version": "7.0.7",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz",
"integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==",
"version": "7.0.9",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
"integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
"dev": true
},
"@types/lodash": {
@ -31806,6 +32273,12 @@
"integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=",
"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": {
"version": "2.1.1",
"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==",
"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": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz",
@ -35321,6 +35800,15 @@
"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": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz",
@ -45063,6 +45551,12 @@
"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": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz",
@ -45144,6 +45638,11 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"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": {
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-1.14.0.tgz",
@ -48442,9 +48947,12 @@
"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=="
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.3.2.tgz",
"integrity": "sha512-cfvZ1nOC/VqAt8bVOIlFz8x+HdDASpiFYrSi0U0nzcAFlOnzzQ/gsPg2PP1uqjreO7sQCtraYJHMduXSewQsSA==",
"dev": true,
"optional": true,
"peer": true
},
"type-is": {
"version": "1.6.18",
@ -50137,6 +50645,12 @@
"buffer-crc32": "~0.2.3",
"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-react": "^7.0.0",
"@babel/preset-typescript": "^7.15.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.1",
"@testing-library/cypress": "^8.0.1",
"@types/file-saver": "^2.0.3",
"@types/jest": "^27.0.1",
@ -104,6 +105,7 @@
"null-loader": "^1.0.0",
"prettier": "^2.3.2",
"raw-loader": "~0.5.0",
"react-refresh": "^0.10.0",
"regenerator-runtime": "^0.13.9",
"sass-loader": "^7.0.3",
"script-loader": "~0.7.0",

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

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

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

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

@ -15,6 +15,7 @@ import { Skill } from "./Skill";
import { City } from "./City";
import { IAction } from "./IAction";
import { IPlayer } from "../PersonObjects/IPlayer";
import { IRouter } from "../ui/Router";
import { ConsoleHelpText } from "./data/Help";
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
import { getRandomInt } from "../../utils/helpers/getRandomInt";
@ -25,7 +26,7 @@ import { addOffset } from "../../utils/helpers/addOffset";
import { Faction } from "../Faction/Faction";
import { Factions, factionExists } from "../Faction/Factions";
import { calculateHospitalizationCost } from "../Hospital/Hospital";
import { hackWorldDaemon, redPillFlag } from "../RedPill";
import { redPillFlag } from "../RedPill";
import { dialogBoxCreate } from "../../utils/DialogBox";
import { Settings } from "../Settings/Settings";
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) {
case ActionTypes["Contract"]:
case ActionTypes["Operation"]: {
@ -1338,7 +1339,7 @@ export class Bladeburner implements IBladeburner {
// Operation Daedalus
if (action.name === "Operation Daedalus") {
this.resetAction();
return hackWorldDaemon(player.bitNodeN);
return router.toBitVerse(false, false);
}
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.actionTimeToComplete <= 0) {
throw new Error(`Invalid actionTimeToComplete value: ${this.actionTimeToComplete}, type; ${this.action.type}`);
@ -1555,7 +1556,7 @@ export class Bladeburner implements IBladeburner {
this.actionTimeOverflow = 0;
if (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
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
@ -1958,7 +1959,7 @@ export class Bladeburner implements IBladeburner {
this.randomEventCounter += getRandomInt(240, 600);
}
this.processAction(player, seconds);
this.processAction(router, player, seconds);
// Automation
if (this.automateEnabled) {

@ -3,6 +3,7 @@ import { City } from "./City";
import { Skill } from "./Skill";
import { IAction } from "./IAction";
import { IPlayer } from "../PersonObjects/IPlayer";
import { IRouter } from "../ui/Router";
import { WorkerScript } from "../Netscript/WorkerScript";
export interface IBladeburner {
@ -103,11 +104,11 @@ export interface IBladeburner {
completeOperation(success: boolean): void;
getActionObject(actionId: IActionIdentifier): IAction | null;
completeContract(success: boolean): void;
completeAction(player: IPlayer): void;
completeAction(router: IRouter, player: IPlayer): 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;
calculateMaxStamina(player: IPlayer): 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 { AllPages } from "./AllPages";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import { use } from "../../ui/Context";
import { IBladeburner } from "../IBladeburner";
interface IProps {
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 (
<div className="bladeburner-container">
<div style={{ height: "60%", display: "block", position: "relative" }}>
@ -25,9 +24,9 @@ export function Root(props: IProps): React.ReactElement {
border: "1px solid white",
}}
>
<Stats bladeburner={props.bladeburner} player={props.player} engine={props.engine} />
<Stats bladeburner={props.bladeburner} player={player} router={router} />
</div>
<Console bladeburner={props.bladeburner} player={props.player} />
<Console bladeburner={props.bladeburner} player={player} />
</div>
<div
style={{
@ -39,7 +38,7 @@ export function Root(props: IProps): React.ReactElement {
position: "relative",
}}
>
<AllPages bladeburner={props.bladeburner} player={props.player} />
<AllPages bladeburner={props.bladeburner} player={player} />
</div>
</div>
);

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

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

@ -121,7 +121,7 @@ export const CONSTANTS: {
TotalNumBitNodes: number;
LatestUpdate: string;
} = {
Version: "0.53.0",
Version: "0.54.0",
// Speed (in ms) at which the main loop is updated
_idleSpeed: 200,
@ -344,83 +344,48 @@ export const CONSTANTS: {
TotalNumBitNodes: 24,
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)
* Lots of test. (@threehams)
* Massive improvements to build speed. (@threehams)
* Dev notes: This won't affect any players but is immensely useful for me.
* 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)
** 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.
* Fixed a bug that allowed players to recover shock much faster than intended.
** 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.
* All research tooltips are always visible.
* Smart supply is enabled by default if purchased (@Nolshine)
** Misc. **
* The world map is now used in sleeve travel and bladeburner travel.
* noselect a bunch of stuff.
* Ascii maps letters are more contrasting
* Updated documentation for infiltration.
* Most money costs in the game will turn grey/cyan when you don't have enough money.
* Donation textbox has better look & feel.
* Tech vendors ram & cores buttons have better look and feels.
* cores cost modified to be a formula instead of a semi-random array of numbers.
* Tech vendors now give a hint about where to get bigger servers.
* logboxes now displays whitespaces exactly. (@Cass)
* 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
`,

@ -1,6 +1,7 @@
import { CONSTANTS } from "../Constants";
import { IPlayer } from "../PersonObjects/IPlayer";
import { IPlayerOrSleeve } from "../PersonObjects/IPlayerOrSleeve";
import { IRouter } from "../ui/Router";
export interface IConstructorParams {
hacking_success_weight?: number;
@ -85,11 +86,12 @@ export class Crime {
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) {
div = 1;
}
p.startCrime(
router,
this.type,
this.hacking_exp / div,
this.strength_exp / div,

@ -1,9 +1,9 @@
import { IPlayer } from "./PersonObjects/IPlayer";
import { Bladeburner } from "./Bladeburner/Bladeburner";
import { IEngine } from "./IEngine";
import { IRouter } from "./ui/Router";
import React from "react";
import { TTheme as Theme } from "./ui/React/Theme";
import { General } from "./DevMenu/ui/General";
import { Stats } from "./DevMenu/ui/Stats";
@ -24,36 +24,35 @@ import { TimeSkip } from "./DevMenu/ui/TimeSkip";
interface IProps {
player: IPlayer;
engine: IEngine;
router: IRouter;
}
export function DevMenuRoot(props: IProps): React.ReactElement {
return (
<Theme>
<>
<h1>Development Menu - Only meant to be used for testing/debugging</h1>
<General player={props.player} />
<Stats player={props.player} />
<Factions player={props.player} />
<Augmentations player={props.player} />
<SourceFiles player={props.player} />
<Programs player={props.player} />
<Servers />
<Companies />
<>
<h1>Development Menu - Only meant to be used for testing/debugging</h1>
<General player={props.player} router={props.router} />
<Stats player={props.player} />
<Factions player={props.player} />
<Augmentations player={props.player} />
<SourceFiles player={props.player} />
<Programs player={props.player} />
<Servers />
<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} />
</>
</Theme>
<TimeSkip player={props.player} engine={props.engine} />
</>
);
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -28,9 +28,9 @@ export function TimeSkip(props: IProps): React.ReactElement {
}
return (
<Accordion>
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<h2>Sleeves</h2>
<h2>Time skip</h2>
</AccordionSummary>
<AccordionDetails>
<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 hasAugmentationPrereqs(aug: Augmentation): boolean;
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 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 { PlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../Constants";
import { Engine } from "../engine";
import { Faction } from "./Faction";
import { Factions } from "./Factions";
import { HackingMission, setInMission } from "../Missions";
@ -65,29 +60,6 @@ export function startHackingMission(faction) {
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
//specified Augmentation
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 {
dialogBoxCreate(
"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
*/
import * as React from "react";
import React, { useState } from "react";
import { PurchaseableAugmentation } from "./PurchaseableAugmentation";
import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Faction } from "../../Faction/Faction";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { PurchaseAugmentationsOrderSetting } from "../../Settings/SettingEnums";
import { Settings } from "../../Settings/Settings";
import { StdButton } from "../../ui/React/StdButton";
import { use } from "../../ui/Context";
type IProps = {
faction: Faction;
p: IPlayer;
routeToMainPage: () => void;
};
type IState = {
rerenderFlag: boolean;
sortOrder: PurchaseAugmentationsOrderSetting;
};
const infoStyleMarkup = {
width: "70%",
};
export class AugmentationsPage extends React.Component<IProps, IState> {
export function AugmentationsPage(props: IProps): React.ReactElement {
const player = use.Player();
// Flag for whether the player has a gang with this faction
isPlayersGang: boolean;
constructor(props: IProps) {
super(props);
const isPlayersGang = player.inGang() && player.getGangName() === props.faction.name;
this.isPlayersGang = props.p.inGang() && props.p.getGangName() === props.faction.name;
const setRerender = useState(false)[1];
this.state = {
rerenderFlag: false,
sortOrder: PurchaseAugmentationsOrderSetting.Default,
};
this.rerender = this.rerender.bind(this);
function rerender(): void {
setRerender((old) => !old);
}
getAugs(): string[] {
if (this.isPlayersGang) {
function getAugs(): string[] {
if (isPlayersGang) {
const augs: string[] = [];
for (const augName in Augmentations) {
const aug = Augmentations[augName];
@ -57,25 +42,25 @@ export class AugmentationsPage extends React.Component<IProps, IState> {
return augs;
} else {
return this.props.faction.augmentations.slice();
return props.faction.augmentations.slice();
}
}
getAugsSorted(): string[] {
function getAugsSorted(): string[] {
switch (Settings.PurchaseAugmentationsOrder) {
case PurchaseAugmentationsOrderSetting.Cost: {
return this.getAugsSortedByCost();
return getAugsSortedByCost();
}
case PurchaseAugmentationsOrderSetting.Reputation: {
return this.getAugsSortedByReputation();
return getAugsSortedByReputation();
}
default:
return this.getAugsSortedByDefault();
return getAugsSortedByDefault();
}
}
getAugsSortedByCost(): string[] {
const augs = this.getAugs();
function getAugsSortedByCost(): string[] {
const augs = getAugs();
augs.sort((augName1, augName2) => {
const aug1 = Augmentations[augName1],
aug2 = Augmentations[augName2];
@ -89,8 +74,8 @@ export class AugmentationsPage extends React.Component<IProps, IState> {
return augs;
}
getAugsSortedByReputation(): string[] {
const augs = this.getAugs();
function getAugsSortedByReputation(): string[] {
const augs = getAugs();
augs.sort((augName1, augName2) => {
const aug1 = Augmentations[augName1],
aug2 = Augmentations[augName2];
@ -103,88 +88,70 @@ export class AugmentationsPage extends React.Component<IProps, IState> {
return augs;
}
getAugsSortedByDefault(): string[] {
return this.getAugs();
function getAugsSortedByDefault(): string[] {
return getAugs();
}
switchSortOrder(newOrder: PurchaseAugmentationsOrderSetting): void {
function switchSortOrder(newOrder: PurchaseAugmentationsOrderSetting): void {
Settings.PurchaseAugmentationsOrder = newOrder;
this.rerender();
rerender();
}
rerender(): void {
this.setState((prevState) => {
return {
rerenderFlag: !prevState.rerenderFlag,
};
});
}
const augs = getAugsSorted();
const purchasable = augs.filter(
(aug: string) =>
aug === AugmentationNames.NeuroFluxGovernor ||
(!player.augmentations.some((a) => a.name === aug) && !player.queuedAugmentations.some((a) => a.name === aug)),
);
render(): React.ReactNode {
const augs = this.getAugsSorted();
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 => {
return <PurchaseableAugmentation augName={aug} faction={props.faction} key={aug} p={player} rerender={rerender} />;
};
const purchaseableAugmentation = (aug: string): React.ReactNode => {
return (
<PurchaseableAugmentation
augName={aug}
faction={this.props.faction}
key={aug}
p={this.props.p}
rerender={this.rerender}
/>
);
};
const augListElems = purchasable.map((aug) => purchaseableAugmentation(aug));
const augListElems = purchasable.map((aug) => purchaseableAugmentation(aug));
let ownedElem = <></>;
const owned = augs.filter((aug: string) => !purchasable.includes(aug));
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"}
/>
let ownedElem = <></>;
const owned = augs.filter((aug: string) => !purchasable.includes(aug));
if (owned.length !== 0) {
ownedElem = (
<>
<br />
{augListElems}
{ownedElem}
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
</div>
<h2>Purchased Augmentations</h2>
<p>This factions also offers these augmentations but you already own them.</p>
{owned.map((aug) => purchaseableAugmentation(aug))}
</>
);
}
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.
*/
import React from "react";
import { removePopup } from "../../ui/React/createPopup";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { StdButton } from "../../ui/React/StdButton";
import { IEngine } from "../../IEngine";
import React from "react";
import { removePopup } from "../../ui/React/createPopup";
import { StdButton } from "../../ui/React/StdButton";
import { IRouter } from "../../ui/Router";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface ICreateGangPopupProps {
popupId: string;
facName: string;
p: IPlayer;
engine: IEngine;
}
interface ICreateGangPopupProps {
popupId: string;
facName: string;
player: IPlayer;
router: IRouter;
}
export function CreateGangPopup(props: ICreateGangPopupProps): React.ReactElement {
const combatGangText = "This is a COMBAT gang. Members in this gang will have different tasks than HACKING gangs. " +
export function CreateGangPopup(props: ICreateGangPopupProps): React.ReactElement {
const player = props.player;
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 " +
"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 " +
"is not as important.";
function isHacking(): boolean {
return ["NiteSec", "The Black Hand"].includes(props.facName);
}
function isHacking(): boolean {
return ["NiteSec", "The Black Hand"].includes(props.facName);
}
function createGang(): void {
props.p.startGang(props.facName, isHacking());
removePopup(props.popupId);
props.engine.loadGangContent();
}
function createGang(): void {
player.startGang(props.facName, isHacking());
removePopup(props.popupId);
router.toGang();
}
function onKeyUp(event: React.KeyboardEvent): void {
if (event.keyCode === 13) createGang();
}
function onKeyUp(event: React.KeyboardEvent): void {
if (event.keyCode === 13) createGang();
}
return (
<>
Would you like to create a new Gang with {props.facName}?
<br/>
<br/>
Note that this will prevent you from creating a Gang with any other Faction until this BitNode is destroyed. It also resets your reputation with this faction.
<br/>
<br/>
{ (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.
<div className="popup-box-input-div" >
<StdButton onClick={createGang} onKeyUp={onKeyUp} text="Create Gang" style={{float: "right"}} autoFocus={true}/>
</div>
</>
);
}
return (
<>
Would you like to create a new Gang with {props.facName}?
<br />
<br />
Note that this will prevent you from creating a Gang with any other Faction until this BitNode is destroyed. It
also resets your reputation with this faction.
<br />
<br />
{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.
<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
* accessible factions
*/
import * as React from "react";
import React, { useState } from "react";
import { AugmentationsPage } from "./AugmentationsPage";
import { DonateOption } from "./DonateOption";
@ -11,28 +11,18 @@ import { Info } from "./Info";
import { Option } from "./Option";
import { CONSTANTS } from "../../Constants";
import { IEngine } from "../../IEngine";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { Faction } from "../../Faction/Faction";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { createSleevePurchasesFromCovenantPopup } from "../../PersonObjects/Sleeve/SleeveCovenantPurchases";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { createPopup } from "../../ui/React/createPopup";
import { use } from "../../ui/Context";
import { CreateGangPopup } from "./CreateGangPopup";
type IProps = {
engine: IEngine;
initiallyOnAugmentationsPage?: boolean;
faction: Faction;
p: IPlayer;
startHackingMissionFn: (faction: Faction) => void;
};
type IState = {
rerenderFlag: boolean;
purchasingAugs: boolean;
};
// Info text for all options on the UI
@ -72,89 +62,66 @@ const GangNames = [
"The Black Hand",
];
export class FactionRoot extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
export function FactionRoot(props: IProps): React.ReactElement {
const faction = props.faction;
this.state = {
rerenderFlag: false,
purchasingAugs: props.initiallyOnAugmentationsPage ? props.initiallyOnAugmentationsPage : false,
};
const player = use.Player();
const router = use.Router();
const [, setRerenderFlag] = useState(false);
const [purchasingAugs, setPurchasingAugs] = useState(false);
this.manageGang = this.manageGang.bind(this);
this.rerender = this.rerender.bind(this);
this.routeToMain = this.routeToMain.bind(this);
this.routeToPurchaseAugs = this.routeToPurchaseAugs.bind(this);
this.sleevePurchases = this.sleevePurchases.bind(this);
this.startFieldWork = this.startFieldWork.bind(this);
this.startHackingContracts = this.startHackingContracts.bind(this);
this.startHackingMission = this.startHackingMission.bind(this);
this.startSecurityWork = this.startSecurityWork.bind(this);
}
manageGang(): void {
function manageGang(faction: Faction): void {
// If player already has a gang, just go to the gang UI
if (this.props.p.inGang()) {
return this.props.engine.loadGangContent();
if (player.inGang()) {
return router.toGang();
}
const popupId = "create-gang-popup";
createPopup(popupId, CreateGangPopup, {
popupId: popupId,
facName: this.props.faction.name,
p: this.props.p,
engine: this.props.engine,
facName: faction.name,
player: player,
router: router,
});
}
rerender(): void {
this.setState((prevState) => {
return {
rerenderFlag: !prevState.rerenderFlag,
};
});
function rerender(): void {
setRerenderFlag((old) => !old);
}
// Route to the main faction page
routeToMain(): void {
this.setState({ purchasingAugs: false });
function routeToMain(): void {
setPurchasingAugs(false);
}
// Route to the purchase augmentation UI for this faction
routeToPurchaseAugs(): void {
this.setState({ purchasingAugs: true });
function routeToPurchaseAugs(): void {
setPurchasingAugs(true);
}
sleevePurchases(): void {
createSleevePurchasesFromCovenantPopup(this.props.p);
function sleevePurchases(): void {
createSleevePurchasesFromCovenantPopup(player);
}
startFieldWork(): void {
this.props.p.startFactionFieldWork(this.props.faction);
function startFieldWork(faction: Faction): void {
player.startFactionFieldWork(router, faction);
}
startHackingContracts(): void {
this.props.p.startFactionHackWork(this.props.faction);
function startHackingContracts(faction: Faction): void {
player.startFactionHackWork(router, faction);
}
startHackingMission(): void {
const fac = this.props.faction;
this.props.p.singularityStopWork();
this.props.engine.loadMissionContent();
this.props.startHackingMissionFn(fac);
function startHackingMission(faction: Faction): void {
player.singularityStopWork();
router.toHackingMission(faction);
}
startSecurityWork(): void {
this.props.p.startFactionSecurityWork(this.props.faction);
function startSecurityWork(faction: Faction): void {
player.startFactionSecurityWork(router, faction);
}
render(): React.ReactNode {
return this.state.purchasingAugs ? this.renderAugmentationsPage() : this.renderMainPage();
}
renderMainPage(): React.ReactNode {
const p = this.props.p;
const faction = this.props.faction;
function MainPage({ faction }: { faction: Faction }): React.ReactElement {
const p = player;
const factionInfo = faction.getInfo();
// 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">
<h1>{faction.name}</h1>
<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 && (
<Option buttonText={"Hacking Mission"} infoText={hackingMissionInfo} onClick={this.startHackingMission} />
<Option
buttonText={"Hacking Mission"}
infoText={hackingMissionInfo}
onClick={() => startHackingMission(faction)}
/>
)}
{!isPlayersGang && factionInfo.offerHackingWork && (
<Option
buttonText={"Hacking Contracts"}
infoText={hackingContractsInfo}
onClick={this.startHackingContracts}
onClick={() => startHackingContracts(faction)}
/>
)}
{!isPlayersGang && factionInfo.offerFieldWork && (
<Option buttonText={"Field Work"} infoText={fieldWorkInfo} onClick={this.startFieldWork} />
<Option buttonText={"Field Work"} infoText={fieldWorkInfo} onClick={() => startFieldWork(faction)} />
)}
{!isPlayersGang && factionInfo.offerSecurityWork && (
<Option buttonText={"Security Work"} infoText={securityWorkInfo} onClick={this.startSecurityWork} />
<Option buttonText={"Security Work"} infoText={securityWorkInfo} onClick={() => startSecurityWork(faction)} />
)}
{!isPlayersGang && factionInfo.offersWork() && (
<DonateOption
faction={this.props.faction}
p={this.props.p}
rerender={this.rerender}
faction={faction}
p={player}
rerender={rerender}
favorToDonate={favorToDonate}
disabled={!canDonate}
/>
)}
<Option buttonText={"Purchase Augmentations"} infoText={augmentationsInfo} onClick={this.routeToPurchaseAugs} />
<Option buttonText={"Purchase Augmentations"} infoText={augmentationsInfo} onClick={routeToPurchaseAugs} />
{canPurchaseSleeves && (
<Option
buttonText={"Purchase & Upgrade Duplicate Sleeves"}
infoText={sleevePurchasesInfo}
onClick={this.sleevePurchases}
onClick={sleevePurchases}
/>
)}
</div>
);
}
renderAugmentationsPage(): React.ReactNode {
return (
<>
<AugmentationsPage faction={this.props.faction} p={this.props.p} routeToMainPage={this.routeToMain} />
</>
);
}
return purchasingAugs ? (
<AugmentationsPage faction={faction} routeToMainPage={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;
faction: Faction;
aug: Augmentation;
rerender: () => void;
popupId: string;
}
@ -24,6 +25,7 @@ export function PurchaseAugmentationPopup(props: IProps): React.ReactElement {
}
purchaseAugmentation(props.aug, props.faction);
props.rerender();
removePopup(props.popupId);
}

@ -7,7 +7,6 @@ import * as React from "react";
import { getNextNeurofluxLevel, hasAugmentationPrereqs, purchaseAugmentation } from "../FactionHelpers";
import { PurchaseAugmentationPopup } from "./PurchaseAugmentationPopup";
import { Augmentation } from "../../Augmentation/Augmentation";
import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Faction } from "../../Faction/Faction";
@ -28,63 +27,56 @@ type IProps = {
rerender: () => void;
};
export class PurchaseableAugmentation extends React.Component<IProps, any> {
aug: Augmentation;
export function PurchaseableAugmentation(props: IProps): React.ReactElement {
const aug = Augmentations[props.augName];
if (aug == null) throw new Error(`aug ${props.augName} does not exists`);
constructor(props: IProps) {
super(props);
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);
function getMoneyCost(): number {
return aug.baseCost * props.faction.getInfo().augmentationPriceMult;
}
getMoneyCost(): number {
return this.aug.baseCost * this.props.faction.getInfo().augmentationPriceMult;
function getRepCost(): number {
return aug.baseRepRequirement * props.faction.getInfo().augmentationRepRequirementMult;
}
getRepCost(): number {
return this.aug.baseRepRequirement * this.props.faction.getInfo().augmentationRepRequirementMult;
}
handleClick(): void {
function handleClick(): void {
if (!Settings.SuppressBuyAugmentationConfirmation) {
const popupId = "purchase-augmentation-popup";
createPopup(popupId, PurchaseAugmentationPopup, {
aug: this.aug,
faction: this.props.faction,
player: this.props.p,
aug: aug,
faction: props.faction,
player: props.p,
rerender: props.rerender,
popupId: popupId,
});
} else {
purchaseAugmentation(this.aug, this.props.faction);
purchaseAugmentation(aug, props.faction);
props.rerender();
}
}
// Whether the player has the prerequisite Augmentations
hasPrereqs(): boolean {
return hasAugmentationPrereqs(this.aug);
function hasPrereqs(): boolean {
return hasAugmentationPrereqs(aug);
}
// Whether the player has enough rep for this Augmentation
hasReputation(): boolean {
return this.props.faction.playerReputation >= this.getRepCost();
function hasReputation(): boolean {
return props.faction.playerReputation >= getRepCost();
}
// Whether the player has this augmentations (purchased OR installed)
owned(): boolean {
function owned(): boolean {
let owned = false;
for (const queuedAug of this.props.p.queuedAugmentations) {
if (queuedAug.name === this.props.augName) {
for (const queuedAug of props.p.queuedAugmentations) {
if (queuedAug.name === props.augName) {
owned = true;
break;
}
}
for (const installedAug of this.props.p.augmentations) {
if (installedAug.name === this.props.augName) {
for (const installedAug of props.p.augmentations) {
if (installedAug.name === props.augName) {
owned = true;
break;
}
@ -93,96 +85,94 @@ export class PurchaseableAugmentation extends React.Component<IProps, any> {
return owned;
}
render(): React.ReactNode {
if (this.aug == null) {
console.error(
`Invalid Augmentation when trying to create PurchaseableAugmentation display element: ${this.props.augName}`,
);
return null;
}
const moneyCost = this.getMoneyCost();
const repCost = this.getRepCost();
// Determine UI properties
let disabled = 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>
if (aug == null) {
console.error(
`Invalid Augmentation when trying to create PurchaseableAugmentation display element: ${props.augName}`,
);
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_PROMPT_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-background-color", FconfSettings.THEME_BACKGROUND_COLOR);
document.body.style.setProperty("--my-prompt-color", FconfSettings.THEME_PROMPT_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-background-color", FconfSettings.THEME_BACKGROUND_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 { WorkerScript } from "../Netscript/WorkerScript";
import { IPlayer } from "../PersonObjects/IPlayer";
import { IAscensionResult } from "./IAscensionResult";
export interface IGang {
facName: string;
@ -37,8 +38,9 @@ export interface IGang {
getWantedPenalty(): number;
calculatePower(): number;
killMember(member: GangMember): void;
ascendMember(member: GangMember, workerScript: WorkerScript): void;
ascendMember(member: GangMember, workerScript: WorkerScript): IAscensionResult;
getDiscount(): number;
getAllTaskNames(): string[];
getUpgradeCost(upg: GangMemberUpgrade): number;
toJSON(): any;
}

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

@ -55,7 +55,9 @@ export function GangStats(props: IProps): React.ReactElement {
</p>
<br />
<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>
<br />
<p className="tooltip" style={{ display: "inline-block" }}>

@ -28,7 +28,7 @@ export function TaskSelector(props: IProps): React.ReactElement {
const tasks = props.gang.getAllTaskNames();
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`],
[`Wanted Level:`, `${numeralWrapper.formatWanted(5 * props.member.calculateWantedLevelGain(props.gang))} / sec`],
[`Total Respect:`, `${numeralWrapper.formatRespect(props.member.earnedRespect)}`],
@ -46,7 +46,9 @@ export function TaskSelector(props: IProps): React.ReactElement {
</option>
))}
</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"}>
<p>Production:</p>
<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>
</div>
<div className={"row"}>

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

@ -30,4 +30,5 @@ export interface IEngine {
loadMissionContent: () => void;
loadResleevingContent: () => 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 { IEngine } from "../../IEngine";
import { use } from "../../ui/Context";
import React, { useState } from "react";
import Grid from "@mui/material/Grid";
import { Countdown } from "./Countdown";
@ -14,8 +13,6 @@ import { WireCuttingGame } from "./WireCuttingGame";
import { Victory } from "./Victory";
interface IProps {
Player: IPlayer;
Engine: IEngine;
StartingDifficulty: number;
Difficulty: number;
MaxLevel: number;
@ -40,6 +37,8 @@ const minigames = [
];
export function Game(props: IProps): React.ReactElement {
const player = use.Player();
const router = use.Router();
const [level, setLevel] = useState(1);
const [stage, setStage] = useState(Stage.Countdown);
const [results, setResults] = useState("");
@ -89,12 +88,10 @@ export function Game(props: IProps): React.ReactElement {
pushResult(false);
// Kill the player immediately if they use automation, so
// it's clear they're not meant to
const damage = options?.automated ? props.Player.hp : props.StartingDifficulty * 3;
if (props.Player.takeDamage(damage)) {
const menu = document.getElementById("mainmenu-container");
if (menu === null) throw new Error("mainmenu-container not found");
menu.style.visibility = "visible";
props.Engine.loadLocationContent();
const damage = options?.automated ? player.hp : props.StartingDifficulty * 3;
if (player.takeDamage(damage)) {
router.toCity();
return;
}
setupNextGame();
}
@ -112,8 +109,6 @@ export function Game(props: IProps): React.ReactElement {
case Stage.Sell:
stageComponent = (
<Victory
Player={props.Player}
Engine={props.Engine}
StartingDifficulty={props.StartingDifficulty}
Difficulty={props.Difficulty}
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 { StdButton } from "../../ui/React/StdButton";
import { Location } from "../../Locations/Location";
import Grid from "@mui/material/Grid";
interface IProps {
Player: IPlayer;
Engine: IEngine;
Location: string;
Location: Location;
Difficulty: number;
MaxLevel: number;
start: () => void;
@ -51,11 +48,12 @@ function coloredArrow(difficulty: number): JSX.Element {
}
export function Intro(props: IProps): React.ReactElement {
console.log(props);
return (
<>
<Grid container spacing={3}>
<Grid item xs={10}>
<h1>Infiltrating {props.Location}</h1>
<h1>Infiltrating {props.Location.name}</h1>
</Grid>
<Grid item xs={10}>
<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 React, { useState } from "react";
import { StdButton } from "../../ui/React/StdButton";
@ -7,23 +5,21 @@ import Grid from "@mui/material/Grid";
import { Money } from "../../ui/React/Money";
import { Reputation } from "../../ui/React/Reputation";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { use } from "../../ui/Context";
interface IProps {
Player: IPlayer;
Engine: IEngine;
StartingDifficulty: number;
Difficulty: number;
MaxLevel: number;
}
export function Victory(props: IProps): React.ReactElement {
const player = use.Player();
const router = use.Router();
const [faction, setFaction] = useState("none");
function quitInfiltration(): void {
const menu = document.getElementById("mainmenu-container");
if (!menu) throw new Error("mainmenu-container somehow null");
menu.style.visibility = "visible";
props.Engine.loadLocationContent();
router.toCity();
}
const levelBonus = props.MaxLevel * Math.pow(1.01, props.MaxLevel);
@ -43,8 +39,8 @@ export function Victory(props: IProps): React.ReactElement {
BitNodeMultipliers.InfiltrationMoney;
function sell(): void {
props.Player.gainMoney(moneyGain);
props.Player.recordMoneySource(moneyGain, "infiltration");
player.gainMoney(moneyGain);
player.recordMoneySource(moneyGain, "infiltration");
quitInfiltration();
}
@ -70,7 +66,7 @@ export function Victory(props: IProps): React.ReactElement {
<option key={"none"} value={"none"}>
{"none"}
</option>
{props.Player.factions
{player.factions
.filter((f) => Factions[f].getInfo().offersWork())
.map((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 { City } from "../City";
import { Cities } from "../Cities";
import { LocationName } from "../data/LocationNames";
import { Locations } from "../Locations";
import { Location } from "../Location";
import { Settings } from "../../Settings/Settings";
import { StdButton } from "../../ui/React/StdButton";
import { use } from "../../ui/Context";
import { IRouter } from "../../ui/Router";
type IProps = {
city: City;
enterLocation: (to: LocationName) => void;
};
export class LocationCity extends React.Component<IProps, any> {
asciiCity(): React.ReactNode {
const LocationLetter = (location: LocationName): JSX.Element => {
if (location)
return (
<span
key={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 toLocation(router: IRouter, location: Location): void {
if (location.name === LocationName.TravelAgency) {
router.toTravel();
} else if (location.name === LocationName.WorldStockExchange) {
router.toStockMarket();
} else {
router.toLocation(location);
}
}
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
*/
import * as React from "react";
import React, { useState } from "react";
import { ApplyToJobButton } from "./ApplyToJobButton";
import { Location } from "../Location";
import { Locations } from "../Locations";
import { LocationName } from "../data/LocationNames";
import { IEngine } from "../../IEngine";
import { Companies } from "../../Company/Companies";
import { Company } from "../../Company/Company";
import { CompanyPosition } from "../../Company/CompanyPosition";
import { CompanyPositions } from "../../Company/CompanyPositions";
import * as posNames from "../../Company/data/companypositionnames";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { StdButton } from "../../ui/React/StdButton";
import { Reputation } from "../../ui/React/Reputation";
import { Favor } from "../../ui/React/Favor";
import { createPopup } from "../../ui/React/createPopup";
import { use } from "../../ui/Context";
import { QuitJobPopup } from "../../Company/ui/QuitJobPopup";
type IProps = {
engine: IEngine;
locName: LocationName;
p: IPlayer;
};
type IState = {
employedHere: boolean;
};
const blockStyleMarkup = {
display: "block",
};
export class CompanyLocation extends React.Component<IProps, IState> {
export function CompanyLocation(props: IProps): React.ReactElement {
const p = use.Player();
const router = use.Router();
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
/**
* 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
*/
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
* (if he has one)
*/
companyPosition: CompanyPosition | null = null;
const companyPosition = jobTitle ? CompanyPositions[jobTitle] : null;
/**
* Stores button styling that sets them all to block display
*/
btnStyle: any;
p.location = props.locName;
/**
* 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 {
function applyForAgentJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
this.props.p.applyForAgentJob();
this.checkIfEmployedHere(true);
p.applyForAgentJob();
rerender();
}
applyForBusinessConsultantJob(e: React.MouseEvent<HTMLElement>): void {
function applyForBusinessConsultantJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
this.props.p.applyForBusinessConsultantJob();
this.checkIfEmployedHere(true);
p.applyForBusinessConsultantJob();
rerender();
}
applyForBusinessJob(e: React.MouseEvent<HTMLElement>): void {
function applyForBusinessJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
this.props.p.applyForBusinessJob();
this.checkIfEmployedHere(true);
p.applyForBusinessJob();
rerender();
}
applyForEmployeeJob(e: React.MouseEvent<HTMLElement>): void {
function applyForEmployeeJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
this.props.p.applyForEmployeeJob();
this.checkIfEmployedHere(true);
p.applyForEmployeeJob();
rerender();
}
applyForItJob(e: React.MouseEvent<HTMLElement>): void {
function applyForItJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
this.props.p.applyForItJob();
this.checkIfEmployedHere(true);
p.applyForItJob();
rerender();
}
applyForPartTimeEmployeeJob(e: React.MouseEvent<HTMLElement>): void {
function applyForPartTimeEmployeeJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
this.props.p.applyForPartTimeEmployeeJob();
this.checkIfEmployedHere(true);
p.applyForPartTimeEmployeeJob();
rerender();
}
applyForPartTimeWaiterJob(e: React.MouseEvent<HTMLElement>): void {
function applyForPartTimeWaiterJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
this.props.p.applyForPartTimeWaiterJob();
this.checkIfEmployedHere(true);
p.applyForPartTimeWaiterJob();
rerender();
}
applyForSecurityJob(e: React.MouseEvent<HTMLElement>): void {
function applyForSecurityJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
this.props.p.applyForSecurityJob();
this.checkIfEmployedHere(true);
p.applyForSecurityJob();
rerender();
}
applyForSoftwareConsultantJob(e: React.MouseEvent<HTMLElement>): void {
function applyForSoftwareConsultantJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
this.props.p.applyForSoftwareConsultantJob();
this.checkIfEmployedHere(true);
p.applyForSoftwareConsultantJob();
rerender();
}
applyForSoftwareJob(e: React.MouseEvent<HTMLElement>): void {
function applyForSoftwareJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
this.props.p.applyForSoftwareJob();
this.checkIfEmployedHere(true);
p.applyForSoftwareJob();
rerender();
}
applyForWaiterJob(e: React.MouseEvent<HTMLElement>): void {
function applyForWaiterJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
this.props.p.applyForWaiterJob();
this.checkIfEmployedHere(true);
p.applyForWaiterJob();
rerender();
}
checkIfEmployedHere(updateState = false): 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 {
function startInfiltration(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
const loc = this.location;
if (!loc.infiltrationData) {
console.error(`trying to start infiltration at ${this.props.locName} but the infiltrationData is null`);
return;
}
const loc = location;
if (!loc.infiltrationData)
throw new Error(`trying to start infiltration at ${props.locName} but the infiltrationData is null`);
this.props.engine.loadInfiltrationContent(
this.props.locName,
loc.infiltrationData.startingSecurityLevel,
loc.infiltrationData.maxClearanceLevel,
);
const data = loc.infiltrationData;
if (data == null) {
return;
}
router.toInfiltration(loc);
}
work(e: React.MouseEvent<HTMLElement>): void {
function work(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
const pos = this.companyPosition;
const pos = companyPosition;
if (pos instanceof CompanyPosition) {
if (pos.isPartTimeJob() || pos.isSoftwareConsultantJob() || pos.isBusinessConsultantJob()) {
this.props.p.startWorkPartTime(this.props.locName);
p.startWorkPartTime(router, props.locName);
} 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;
const popupId = `quit-job-popup`;
createPopup(popupId, QuitJobPopup, {
locName: this.props.locName,
company: this.company,
player: this.props.p,
onQuit: () => this.checkIfEmployedHere(true),
locName: props.locName,
company: company,
player: p,
onQuit: rerender,
popupId: popupId,
});
}
render(): React.ReactNode {
const isEmployedHere = this.jobTitle != null;
const favorGain = this.company.getFavorGain();
const isEmployedHere = jobTitle != null;
const favorGain = company.getFavorGain();
return (
<div>
{isEmployedHere && (
<div>
<p>Job Title: {this.jobTitle}</p>
<br />
<p style={blockStyleMarkup}>-------------------------</p>
<br />
<p className={"tooltip"}>
Company reputation: {Reputation(this.company.playerReputation)}
<span className={"tooltiptext"}>
You will earn {Favor(favorGain[0])} company favor upon resetting after installing Augmentations
</span>
</p>
<br />
<br />
<p style={blockStyleMarkup}>-------------------------</p>
<br />
<p className={"tooltip"}>
Company Favor: {Favor(this.company.favor)}
<span className={"tooltiptext"}>
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
on how much reputation you have with the comapny.
</span>
</p>
<br />
<br />
<p style={blockStyleMarkup}>-------------------------</p>
<br />
<StdButton onClick={this.work} text={"Work"} />
&nbsp;&nbsp;&nbsp;&nbsp;
<StdButton onClick={this.quit} text={"Quit"} />
</div>
)}
{this.company.hasAgentPositions() && (
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.AgentCompanyPositions[0]]}
onClick={this.applyForAgentJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply for Agent Job"}
/>
)}
{this.company.hasBusinessConsultantPositions() && (
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]]}
onClick={this.applyForBusinessConsultantJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply for Business Consultant Job"}
/>
)}
{this.company.hasBusinessPositions() && (
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.BusinessCompanyPositions[0]]}
onClick={this.applyForBusinessJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply for Business Job"}
/>
)}
{this.company.hasEmployeePositions() && (
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.MiscCompanyPositions[1]]}
onClick={this.applyForEmployeeJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply to be an Employee"}
/>
)}
{this.company.hasEmployeePositions() && (
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.PartTimeCompanyPositions[1]]}
onClick={this.applyForPartTimeEmployeeJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply to be a part-time Employee"}
/>
)}
{this.company.hasITPositions() && (
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.ITCompanyPositions[0]]}
onClick={this.applyForItJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply for IT Job"}
/>
)}
{this.company.hasSecurityPositions() && (
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.SecurityCompanyPositions[2]]}
onClick={this.applyForSecurityJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply for Security Job"}
/>
)}
{this.company.hasSoftwareConsultantPositions() && (
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.SoftwareConsultantCompanyPositions[0]]}
onClick={this.applyForSoftwareConsultantJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply for Software Consultant Job"}
/>
)}
{this.company.hasSoftwarePositions() && (
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.SoftwareCompanyPositions[0]]}
onClick={this.applyForSoftwareJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply for Software Job"}
/>
)}
{this.company.hasWaiterPositions() && (
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.MiscCompanyPositions[0]]}
onClick={this.applyForWaiterJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply to be a Waiter"}
/>
)}
{this.company.hasWaiterPositions() && (
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.PartTimeCompanyPositions[0]]}
onClick={this.applyForPartTimeWaiterJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply to be a part-time Waiter"}
/>
)}
{this.location.infiltrationData != null && (
<StdButton onClick={this.startInfiltration} style={this.btnStyle} text={"Infiltrate Company"} />
)}
<br />
<br />
<br />
<br />
<br />
</div>
);
}
return (
<div>
{isEmployedHere && (
<div>
<p>Job Title: {jobTitle}</p>
<br />
<p style={{ display: "block" }}>-------------------------</p>
<br />
<p className={"tooltip"}>
Company reputation: {Reputation(company.playerReputation)}
<span className={"tooltiptext"}>
You will earn {Favor(favorGain[0])} company favor upon resetting after installing Augmentations
</span>
</p>
<br />
<br />
<p style={{ display: "block" }}>-------------------------</p>
<br />
<p className={"tooltip"}>
Company Favor: {Favor(company.favor)}
<span className={"tooltiptext"}>
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 on
how much reputation you have with the comapny.
</span>
</p>
<br />
<br />
<p style={{ display: "block" }}>-------------------------</p>
<br />
<StdButton onClick={work} text={"Work"} />
&nbsp;&nbsp;&nbsp;&nbsp;
<StdButton onClick={quit} text={"Quit"} />
</div>
)}
{company.hasAgentPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.AgentCompanyPositions[0]]}
onClick={applyForAgentJob}
p={p}
style={{ display: "block" }}
text={"Apply for Agent Job"}
/>
)}
{company.hasBusinessConsultantPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]]}
onClick={applyForBusinessConsultantJob}
p={p}
style={{ display: "block" }}
text={"Apply for Business Consultant Job"}
/>
)}
{company.hasBusinessPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.BusinessCompanyPositions[0]]}
onClick={applyForBusinessJob}
p={p}
style={{ display: "block" }}
text={"Apply for Business Job"}
/>
)}
{company.hasEmployeePositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.MiscCompanyPositions[1]]}
onClick={applyForEmployeeJob}
p={p}
style={{ display: "block" }}
text={"Apply to be an Employee"}
/>
)}
{company.hasEmployeePositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.PartTimeCompanyPositions[1]]}
onClick={applyForPartTimeEmployeeJob}
p={p}
style={{ display: "block" }}
text={"Apply to be a part-time Employee"}
/>
)}
{company.hasITPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.ITCompanyPositions[0]]}
onClick={applyForItJob}
p={p}
style={{ display: "block" }}
text={"Apply for IT Job"}
/>
)}
{company.hasSecurityPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.SecurityCompanyPositions[2]]}
onClick={applyForSecurityJob}
p={p}
style={{ display: "block" }}
text={"Apply for Security Job"}
/>
)}
{company.hasSoftwareConsultantPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.SoftwareConsultantCompanyPositions[0]]}
onClick={applyForSoftwareConsultantJob}
p={p}
style={{ display: "block" }}
text={"Apply for Software Consultant Job"}
/>
)}
{company.hasSoftwarePositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.SoftwareCompanyPositions[0]]}
onClick={applyForSoftwareJob}
p={p}
style={{ display: "block" }}
text={"Apply for Software Job"}
/>
)}
{company.hasWaiterPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.MiscCompanyPositions[0]]}
onClick={applyForWaiterJob}
p={p}
style={{ display: "block" }}
text={"Apply to be a Waiter"}
/>
)}
{company.hasWaiterPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.PartTimeCompanyPositions[0]]}
onClick={applyForPartTimeWaiterJob}
p={p}
style={{ display: "block" }}
text={"Apply to be a part-time Waiter"}
/>
)}
{location.infiltrationData != null && (
<StdButton onClick={startInfiltration} style={{ display: "block" }} text={"Infiltrate Company"} />
)}
<br />
<br />
<br />
<br />
<br />
</div>
);
}

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

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

@ -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 { Crimes } from "../../Crime/Crimes";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { numeralWrapper } from "../../ui/numeralFormat";
import { AutoupdatingStdButton } from "../../ui/React/AutoupdatingStdButton";
import { use } from "../../ui/Context";
type IProps = {
p: IPlayer;
};
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 {
export function SlumsLocation(): React.ReactElement {
const player = use.Player();
const router = use.Router();
function shoplift(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
return;
}
Crimes.Heist.commit(this.props.p);
Crimes.Heist.commit(router, player);
}
render(): React.ReactNode {
const shopliftChance = Crimes.Shoplift.successRate(this.props.p);
const robStoreChance = Crimes.RobStore.successRate(this.props.p);
const mugChance = Crimes.Mug.successRate(this.props.p);
const larcenyChance = Crimes.Larceny.successRate(this.props.p);
const drugsChance = Crimes.DealDrugs.successRate(this.props.p);
const bondChance = Crimes.BondForgery.successRate(this.props.p);
const armsChance = Crimes.TraffickArms.successRate(this.props.p);
const homicideChance = Crimes.Homicide.successRate(this.props.p);
const gtaChance = Crimes.GrandTheftAuto.successRate(this.props.p);
const kidnapChance = Crimes.Kidnap.successRate(this.props.p);
const assassinateChance = Crimes.Assassination.successRate(this.props.p);
const heistChance = Crimes.Heist.successRate(this.props.p);
const shopliftChance = Crimes.Shoplift.successRate(player);
const robStoreChance = Crimes.RobStore.successRate(player);
const mugChance = Crimes.Mug.successRate(player);
const larcenyChance = Crimes.Larceny.successRate(player);
const drugsChance = Crimes.DealDrugs.successRate(player);
const bondChance = Crimes.BondForgery.successRate(player);
const armsChance = Crimes.TraffickArms.successRate(player);
const homicideChance = Crimes.Homicide.successRate(player);
const gtaChance = Crimes.GrandTheftAuto.successRate(player);
const kidnapChance = Crimes.Kidnap.successRate(player);
const assassinateChance = Crimes.Assassination.successRate(player);
const heistChance = Crimes.Heist.successRate(player);
return (
<div>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.shoplift}
style={this.btnStyle}
text={`Shoplift (${numeralWrapper.formatPercentage(shopliftChance)} chance of success)`}
tooltip={"Attempt to shoplift from a low-end retailer"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.robStore}
style={this.btnStyle}
text={`Rob store (${numeralWrapper.formatPercentage(robStoreChance)} chance of success)`}
tooltip={"Attempt to commit armed robbery on a high-end store"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.mug}
style={this.btnStyle}
text={`Mug someone (${numeralWrapper.formatPercentage(mugChance)} chance of success)`}
tooltip={"Attempt to mug a random person on the street"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.larceny}
style={this.btnStyle}
text={`Larceny (${numeralWrapper.formatPercentage(larcenyChance)} chance of success)`}
tooltip={"Attempt to rob property from someone's house"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.dealDrugs}
style={this.btnStyle}
text={`Deal Drugs (${numeralWrapper.formatPercentage(drugsChance)} chance of success)`}
tooltip={"Attempt to deal drugs"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.bondForgery}
style={this.btnStyle}
text={`Bond Forgery (${numeralWrapper.formatPercentage(bondChance)} chance of success)`}
tooltip={"Attempt to forge corporate bonds"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.traffickArms}
style={this.btnStyle}
text={`Traffick illegal Arms (${numeralWrapper.formatPercentage(armsChance)} chance of success)`}
tooltip={"Attempt to smuggle illegal arms into the city"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.homicide}
style={this.btnStyle}
text={`Homicide (${numeralWrapper.formatPercentage(homicideChance)} chance of success)`}
tooltip={"Attempt to murder a random person on the street"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.grandTheftAuto}
style={this.btnStyle}
text={`Grand theft Auto (${numeralWrapper.formatPercentage(gtaChance)} chance of success)`}
tooltip={"Attempt to commit grand theft auto"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.kidnap}
style={this.btnStyle}
text={`Kidnap and Ransom (${numeralWrapper.formatPercentage(kidnapChance)} chance of success)`}
tooltip={"Attempt to kidnap and ransom a high-profile-target"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.assassinate}
style={this.btnStyle}
text={`Assassinate (${numeralWrapper.formatPercentage(assassinateChance)} chance of success)`}
tooltip={"Attempt to assassinate a high-profile target"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.heist}
style={this.btnStyle}
text={`Heist (${numeralWrapper.formatPercentage(heistChance)} chance of success)`}
tooltip={"Attempt to pull off the ultimate heist"}
/>
</div>
);
}
return (
<div>
<AutoupdatingStdButton
label={`Shoplift (${numeralWrapper.formatPercentage(shopliftChance)} chance of success)`}
intervalTime={5e3}
onClick={shoplift}
style={{ display: "block" }}
text={`Shoplift (${numeralWrapper.formatPercentage(shopliftChance)} chance of success)`}
tooltip={"Attempt to shoplift from a low-end retailer"}
/>
<AutoupdatingStdButton
label={`Rob store (${numeralWrapper.formatPercentage(robStoreChance)} chance of success)`}
intervalTime={5e3}
onClick={robStore}
style={{ display: "block" }}
text={`Rob store (${numeralWrapper.formatPercentage(robStoreChance)} chance of success)`}
tooltip={"Attempt to commit armed robbery on a high-end store"}
/>
<AutoupdatingStdButton
label={`Mug someone (${numeralWrapper.formatPercentage(mugChance)} chance of success)`}
intervalTime={5e3}
onClick={mug}
style={{ display: "block" }}
text={`Mug someone (${numeralWrapper.formatPercentage(mugChance)} chance of success)`}
tooltip={"Attempt to mug a random person on the street"}
/>
<AutoupdatingStdButton
label={`Larceny (${numeralWrapper.formatPercentage(larcenyChance)} chance of success)`}
intervalTime={5e3}
onClick={larceny}
style={{ display: "block" }}
text={`Larceny (${numeralWrapper.formatPercentage(larcenyChance)} chance of success)`}
tooltip={"Attempt to rob property from someone's house"}
/>
<AutoupdatingStdButton
label={`Deal Drugs (${numeralWrapper.formatPercentage(drugsChance)} chance of success)`}
intervalTime={5e3}
onClick={dealDrugs}
style={{ display: "block" }}
text={`Deal Drugs (${numeralWrapper.formatPercentage(drugsChance)} chance of success)`}
tooltip={"Attempt to deal drugs"}
/>
<AutoupdatingStdButton
label={`Bond Forgery (${numeralWrapper.formatPercentage(bondChance)} chance of success)`}
intervalTime={5e3}
onClick={bondForgery}
style={{ display: "block" }}
text={`Bond Forgery (${numeralWrapper.formatPercentage(bondChance)} chance of success)`}
tooltip={"Attempt to forge corporate bonds"}
/>
<AutoupdatingStdButton
label={`Traffick illegal Arms (${numeralWrapper.formatPercentage(armsChance)} chance of success)`}
intervalTime={5e3}
onClick={traffickArms}
style={{ display: "block" }}
text={`Traffick illegal Arms (${numeralWrapper.formatPercentage(armsChance)} chance of success)`}
tooltip={"Attempt to smuggle illegal arms into the city"}
/>
<AutoupdatingStdButton
label={`Homicide (${numeralWrapper.formatPercentage(homicideChance)} chance of success)`}
intervalTime={5e3}
onClick={homicide}
style={{ display: "block" }}
text={`Homicide (${numeralWrapper.formatPercentage(homicideChance)} chance of success)`}
tooltip={"Attempt to murder a random person on the street"}
/>
<AutoupdatingStdButton
label={`Grand theft Auto (${numeralWrapper.formatPercentage(gtaChance)} chance of success)`}
intervalTime={5e3}
onClick={grandTheftAuto}
style={{ display: "block" }}
text={`Grand theft Auto (${numeralWrapper.formatPercentage(gtaChance)} chance of success)`}
tooltip={"Attempt to commit grand theft auto"}
/>
<AutoupdatingStdButton
label={`Kidnap and Ransom (${numeralWrapper.formatPercentage(kidnapChance)} chance of success)`}
intervalTime={5e3}
onClick={kidnap}
style={{ display: "block" }}
text={`Kidnap and Ransom (${numeralWrapper.formatPercentage(kidnapChance)} chance of success)`}
tooltip={"Attempt to kidnap and ransom a high-profile-target"}
/>
<AutoupdatingStdButton
label={`Assassinate (${numeralWrapper.formatPercentage(assassinateChance)} chance of success)`}
intervalTime={5e3}
onClick={assassinate}
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
* properties
*/
import * as React from "react";
import React, { useState } from "react";
import { Location } from "../Location";
import { CreateCorporationPopup } from "../../Corporation/ui/CreateCorporationPopup";
import { createPopup } from "../../ui/React/createPopup";
import { LocationName } from "../data/LocationNames";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { use } from "../../ui/Context";
import { AutoupdatingStdButton } from "../../ui/React/AutoupdatingStdButton";
import { StdButton } from "../../ui/React/StdButton";
@ -26,43 +25,21 @@ import { StdButton } from "../../ui/React/StdButton";
import { dialogBoxCreate } from "../../../utils/DialogBox";
type IProps = {
engine: IEngine;
loc: Location;
p: IPlayer;
};
type IState = {
inBladeburner: boolean;
};
export class SpecialLocation extends React.Component<IProps, IState> {
/**
* 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(),
};
}
export function SpecialLocation(props: IProps): React.ReactElement {
const player = use.Player();
const router = use.Router();
const setRerender = useState(false)[1];
const inBladeburner = player.inBladeburner();
/**
* Click handler for "Create Corporation" button at Sector-12 City Hall
*/
createCorporationPopup(): void {
function createCorporationPopup(): void {
const popupId = `create-start-corporation-popup`;
createPopup(popupId, CreateCorporationPopup, {
player: this.props.p,
player: player,
popupId: popupId,
});
}
@ -70,19 +47,17 @@ export class SpecialLocation extends React.Component<IProps, IState> {
/**
* Click handler for Bladeburner button at Sector-12 NSA
*/
handleBladeburner(): void {
const p = this.props.p;
function handleBladeburner(): void {
const p = player;
if (p.inBladeburner()) {
// Enter Bladeburner division
this.props.engine.loadBladeburnerContent();
router.toBladeburner();
} else {
// Apply for Bladeburner division
if (p.strength >= 100 && p.defense >= 100 && p.dexterity >= 100 && p.agility >= 100) {
p.startBladeburner({ new: true });
dialogBoxCreate("You have been accepted into the Bladeburner division!");
this.setState({
inBladeburner: true,
});
setRerender((old) => !old);
const worldHeader = document.getElementById("world-menu-header");
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
*/
handleResleeving(): void {
this.props.engine.loadResleevingContent();
function handleResleeving(): void {
router.toResleeves();
}
renderBladeburner(): React.ReactNode {
if (!this.props.p.canAccessBladeburner()) {
return null;
function renderBladeburner(): React.ReactElement {
if (!player.canAccessBladeburner()) {
return <></>;
}
const text = this.state.inBladeburner ? "Enter Bladeburner Headquarters" : "Apply to Bladeburner Division";
return <StdButton onClick={this.handleBladeburner} style={this.btnStyle} text={text} />;
const text = inBladeburner ? "Enter Bladeburner Headquarters" : "Apply to Bladeburner Division";
return <StdButton onClick={handleBladeburner} style={{ display: "block" }} text={text} />;
}
renderNoodleBar(): React.ReactNode {
function renderNoodleBar(): React.ReactElement {
function EatNoodles(): void {
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 {
if (!this.props.p.canAccessCorporation()) {
function renderCreateCorporation(): React.ReactElement {
if (!player.canAccessCorporation()) {
return (
<>
<p>
@ -130,38 +105,36 @@ export class SpecialLocation extends React.Component<IProps, IState> {
}
return (
<AutoupdatingStdButton
disabled={!this.props.p.canAccessCorporation() || this.props.p.hasCorporation()}
onClick={this.createCorporationPopup}
style={this.btnStyle}
disabled={!player.canAccessCorporation() || player.hasCorporation()}
onClick={createCorporationPopup}
style={{ display: "block" }}
text={"Create a Corporation"}
/>
);
}
renderResleeving(): React.ReactNode {
if (!this.props.p.canAccessResleeving()) {
return null;
function renderResleeving(): React.ReactElement {
if (!player.canAccessResleeving()) {
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 (this.props.loc.name) {
case LocationName.NewTokyoVitaLife: {
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;
switch (props.loc.name) {
case LocationName.NewTokyoVitaLife: {
return renderResleeving();
}
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 { IPlayer } from "../../PersonObjects/IPlayer";
import { IRouter } from "../../ui/Router";
import { Settings } from "../../Settings/Settings";
import { StdButton } from "../../ui/React/StdButton";
import { createPopup } from "../../ui/React/createPopup";
import { Money } from "../../ui/React/Money";
import { WorldMap } from "../../ui/React/WorldMap";
import { dialogBoxCreate } from "../../../utils/DialogBox";
type IProps = {
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) {
travel();
travel(p, router, city);
return;
}
const popupId = `travel-confirmation`;
createPopup(popupId, TravelConfirmationPopup, {
player: p,
city: city,
travel: travel,
travel: () => travel(p, router, city),
popupId: popupId,
});
}
@ -45,7 +60,7 @@ function ASCIIWorldMap(props: IProps): React.ReactElement {
</p>
<WorldMap
currentCity={props.p.city}
onTravel={(city: CityName) => createTravelPopup(props.p, city, () => props.travel(city))}
onTravel={(city: CityName) => createTravelPopup(props.p, props.router, city)}
/>
</div>
);
@ -66,7 +81,7 @@ function ListWorldMap(props: IProps): React.ReactElement {
return (
<StdButton
key={city}
onClick={() => createTravelPopup(props.p, city, () => props.travel(match[1]))}
onClick={() => createTravelPopup(props.p, props.router, city as CityName)}
style={{ display: "block" }}
text={`Travel to ${city}`}
/>
@ -76,10 +91,15 @@ function ListWorldMap(props: IProps): React.ReactElement {
);
}
export function TravelAgencyLocation(props: IProps): React.ReactElement {
if (Settings.DisableASCIIArt) {
return <ListWorldMap p={props.p} travel={props.travel} />;
} else {
return <ASCIIWorldMap p={props.p} travel={props.travel} />;
}
export function TravelAgencyRoot(props: IProps): React.ReactElement {
return (
<>
<h1>Travel Agency</h1>
{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 { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { getServer } from "../../Server/ServerHelpers";
import { Server } from "../../Server/Server";
import { SpecialServerIps } from "../../Server/SpecialServerIps";
import { StdButton } from "../../ui/React/StdButton";
import { Money } from "../../ui/React/Money";
import { use } from "../../ui/Context";
type IProps = {
loc: Location;
p: IPlayer;
};
export class UniversityLocation extends React.Component<IProps, any> {
/**
* Stores button styling that sets them all to block display
*/
btnStyle: any;
export function UniversityLocation(props: IProps): React.ReactElement {
const player = use.Player();
const router = use.Router();
constructor(props: IProps) {
super(props);
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);
function calculateCost(): number {
const ip = SpecialServerIps.getIp(props.loc.name);
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;
return this.props.loc.costMult * discount;
return props.loc.costMult * discount;
}
take(stat: string): void {
const loc = this.props.loc;
this.props.p.startClass(this.calculateCost(), loc.expMult, stat);
function take(stat: string): void {
const loc = props.loc;
player.startClass(router, calculateCost(), loc.expMult, stat);
}
study(): void {
this.take(CONSTANTS.ClassStudyComputerScience);
function study(): void {
take(CONSTANTS.ClassStudyComputerScience);
}
dataStructures(): void {
this.take(CONSTANTS.ClassDataStructures);
function dataStructures(): void {
take(CONSTANTS.ClassDataStructures);
}
networks(): void {
this.take(CONSTANTS.ClassNetworks);
function networks(): void {
take(CONSTANTS.ClassNetworks);
}
algorithms(): void {
this.take(CONSTANTS.ClassAlgorithms);
function algorithms(): void {
take(CONSTANTS.ClassAlgorithms);
}
management(): void {
this.take(CONSTANTS.ClassManagement);
function management(): void {
take(CONSTANTS.ClassManagement);
}
leadership(): void {
this.take(CONSTANTS.ClassLeadership);
function leadership(): void {
take(CONSTANTS.ClassLeadership);
}
render(): React.ReactNode {
const costMult: number = this.calculateCost();
const costMult: number = calculateCost();
const dataStructuresCost = CONSTANTS.ClassDataStructuresBaseCost * costMult;
const networksCost = CONSTANTS.ClassNetworksBaseCost * costMult;
const algorithmsCost = CONSTANTS.ClassAlgorithmsBaseCost * costMult;
const managementCost = CONSTANTS.ClassManagementBaseCost * costMult;
const leadershipCost = CONSTANTS.ClassLeadershipBaseCost * costMult;
const dataStructuresCost = CONSTANTS.ClassDataStructuresBaseCost * costMult;
const networksCost = CONSTANTS.ClassNetworksBaseCost * costMult;
const algorithmsCost = CONSTANTS.ClassAlgorithmsBaseCost * costMult;
const managementCost = CONSTANTS.ClassManagementBaseCost * costMult;
const leadershipCost = CONSTANTS.ClassLeadershipBaseCost * costMult;
const earnHackingExpTooltip = `Gain hacking experience!`;
const earnCharismaExpTooltip = `Gain charisma experience!`;
const earnHackingExpTooltip = `Gain hacking experience!`;
const earnCharismaExpTooltip = `Gain charisma experience!`;
return (
<div>
<StdButton
onClick={this.study}
style={this.btnStyle}
text={`Study Computer Science (free)`}
tooltip={earnHackingExpTooltip}
/>
<StdButton
onClick={this.dataStructures}
style={this.btnStyle}
text={
<>
Take Data Structures course (
<Money money={dataStructuresCost} player={this.props.p} /> / sec)
</>
}
tooltip={earnHackingExpTooltip}
/>
<StdButton
onClick={this.networks}
style={this.btnStyle}
text={
<>
Take Networks course (
<Money money={networksCost} player={this.props.p} /> / sec)
</>
}
tooltip={earnHackingExpTooltip}
/>
<StdButton
onClick={this.algorithms}
style={this.btnStyle}
text={
<>
Take Algorithms course (
<Money money={algorithmsCost} player={this.props.p} /> / sec)
</>
}
tooltip={earnHackingExpTooltip}
/>
<StdButton
onClick={this.management}
style={this.btnStyle}
text={
<>
Take Management course (
<Money money={managementCost} player={this.props.p} /> / sec)
</>
}
tooltip={earnCharismaExpTooltip}
/>
<StdButton
onClick={this.leadership}
style={this.btnStyle}
text={
<>
Take Leadership course (
<Money money={leadershipCost} player={this.props.p} /> / sec)
</>
}
tooltip={earnCharismaExpTooltip}
/>
</div>
);
}
return (
<div>
<StdButton
onClick={study}
style={{ display: "block" }}
text={`Study Computer Science (free)`}
tooltip={earnHackingExpTooltip}
/>
<StdButton
onClick={dataStructures}
style={{ display: "block" }}
text={
<>
Take Data Structures course (
<Money money={dataStructuresCost} player={player} /> / sec)
</>
}
tooltip={earnHackingExpTooltip}
/>
<StdButton
onClick={networks}
style={{ display: "block" }}
text={
<>
Take Networks course (
<Money money={networksCost} player={player} /> / sec)
</>
}
tooltip={earnHackingExpTooltip}
/>
<StdButton
onClick={algorithms}
style={{ display: "block" }}
text={
<>
Take Algorithms course (
<Money money={algorithmsCost} player={player} /> / sec)
</>
}
tooltip={earnHackingExpTooltip}
/>
<StdButton
onClick={management}
style={{ display: "block" }}
text={
<>
Take Management course (
<Money money={managementCost} player={player} /> / sec)
</>
}
tooltip={earnCharismaExpTooltip}
/>
<StdButton
onClick={leadership}
style={{ display: "block" }}
text={
<>
Take Leadership course (
<Money money={leadershipCost} player={player} /> / sec)
</>
}
tooltip={earnCharismaExpTooltip}
/>
</div>
);
}

@ -1,6 +1,4 @@
import { CONSTANTS } from "./Constants";
import { Engine } from "./engine";
import { displayFactionContent } from "./Faction/FactionHelpers";
import { Player } from "./Player";
import { dialogBoxCreate } from "../utils/DialogBox";
@ -12,6 +10,7 @@ import { getRandomInt } from "../utils/helpers/getRandomInt";
import { isString } from "../utils/helpers/isString";
import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners";
import { Router } from "./ui/GameRoot";
// For some reason `jsplumb` needs to be imported exactly like this,
// 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
this.time -= storedCycles * Engine._idleSpeed;
this.time -= storedCycles * CONSTANTS._idleSpeed;
if (this.time <= 0) {
this.finishMission(false);
return;
@ -1595,18 +1594,7 @@ HackingMission.prototype.finishMission = function (win) {
} else {
dialogBoxCreate("Mission lost/forfeited! You did not gain any faction reputation.");
}
// 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);
Router.toFaction();
};
export { HackingMission, inMission, setInMission, currMission };

@ -3,4 +3,4 @@
*/
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) {
WorkerScriptStartStopEventEmitter.emitEvent();
WorkerScriptStartStopEventEmitter.emit();
}
} else {
console.error(`Invalid argument passed into removeWorkerScript():`);

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

@ -9,7 +9,6 @@ import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStart
import { generateNextPid } from "./Netscript/Pid";
import { CONSTANTS } from "./Constants";
import { Engine } from "./engine";
import { Interpreter } from "./JSInterpreter";
import { isScriptErrorMessage, makeRuntimeRejectMsg } from "./NetscriptEvaluator";
import { NetscriptFunctions } from "./NetscriptFunctions";
@ -45,7 +44,7 @@ export function prestigeWorkerScripts() {
killWorkerScript(ws);
}
WorkerScriptStartStopEventEmitter.emitEvent();
WorkerScriptStartStopEventEmitter.emit();
workerScripts.clear();
}
@ -501,7 +500,7 @@ export function createAndAddWorkerScript(runningScriptObj, server, parent) {
// Add the WorkerScript to the global pool
workerScripts.set(pid, s);
WorkerScriptStartStopEventEmitter.emitEvent();
WorkerScriptStartStopEventEmitter.emit();
// Start the script's execution
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
*/
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()) {
ws.scriptRef.onlineRunningTime += time;
}

@ -25,6 +25,7 @@ import { ICorporation } from "../Corporation/ICorporation";
import { IGang } from "../Gang/IGang";
import { IBladeburner } from "../Bladeburner/IBladeburner";
import { ICodingContractReward } from "../CodingContracts";
import { IRouter } from "../ui/Router";
export interface IPlayer {
// Class members
@ -121,8 +122,33 @@ export interface IPlayer {
bladeburner_analysis_mult: number;
bladeburner_success_chance_mult: number;
workRepGained: number;
createProgramName: string;
timeWorkedCreateProgram: number;
crimeType: string;
timeNeededToCompleteWork: number;
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
applyForAgentJob(sing?: boolean): boolean | void;
@ -175,9 +201,10 @@ export interface IPlayer {
setMoney(amt: number): void;
singularityStopWork(): 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;
startCrime(
router: IRouter,
crimeType: string,
hackExp: number,
strExp: number,
@ -189,13 +216,13 @@ export interface IPlayer {
time: number,
singParams: any,
): void;
startFactionFieldWork(faction: Faction): void;
startFactionHackWork(faction: Faction): void;
startFactionSecurityWork(faction: Faction): void;
startFactionFieldWork(router: IRouter, faction: Faction): void;
startFactionHackWork(router: IRouter, faction: Faction): void;
startFactionSecurityWork(router: IRouter, faction: Faction): void;
startFocusing(): void;
startGang(facName: string, isHacking: boolean): void;
startWork(companyName: string): void;
startWorkPartTime(companyName: string): void;
startWork(router: IRouter, companyName: string): void;
startWorkPartTime(router: IRouter, companyName: string): void;
takeDamage(amt: number): boolean;
travel(to: CityName): boolean;
giveExploit(exploit: Exploit): void;
@ -204,9 +231,17 @@ export interface IPlayer {
getCasinoWinnings(): number;
quitJob(company: string): 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;
receiveInvite(factionName: string): void;
updateSkillLevels(): void;
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 { determineCrimeSuccess } from "../../Crime/CrimeHelpers";
import { Crimes } from "../../Crime/Crimes";
import { Engine } from "../../engine";
import { Faction } from "../../Faction/Faction";
import { Factions } from "../../Faction/Factions";
import { displayFactionContent } from "../../Faction/FactionHelpers";
import { resetGangs } from "../../Gang/AllGangs";
import { hasHacknetServers } from "../../Hacknet/HacknetHelpers";
import { Cities } from "../../Locations/Cities";
@ -48,18 +46,12 @@ import Decimal from "decimal.js";
import { numeralWrapper } from "../../ui/numeralFormat";
import { MoneySourceTracker } from "../../utils/MoneySourceTracker";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { clearEventListeners } from "../../../utils/uiHelpers/clearEventListeners";
import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions";
import { Reputation } from "../../ui/React/Reputation";
import { Money } from "../../ui/React/Money";
import { MoneyRate } from "../../ui/React/MoneyRate";
import { ReputationRate } from "../../ui/React/ReputationRate";
import React from "react";
import ReactDOM from "react-dom";
const CYCLES_PER_SEC = 1000 / CONSTANTS.MilliPerCycle;
export function init() {
/* Initialize Player's home computer */
@ -518,8 +510,6 @@ export function resetWorkStatus(generalType, group, workType) {
this.currentWorkFactionDescription = "";
this.createProgramName = "";
this.className = "";
ReactDOM.unmountComponentAtNode(document.getElementById("work-in-progress-text"));
}
export function processWorkEarnings(numCycles = 1) {
@ -556,7 +546,7 @@ export function processWorkEarnings(numCycles = 1) {
}
/* Working for Company */
export function startWork(companyName) {
export function startWork(router, companyName) {
this.resetWorkStatus(CONSTANTS.WorkTypeCompany, companyName);
this.isWorking = true;
this.focus = true;
@ -573,25 +563,7 @@ export function startWork(companyName) {
this.workMoneyGainRate = this.getWorkMoneyGain();
this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer8Hours;
//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();
router.toWork();
}
export function cancelationPenalty() {
@ -607,78 +579,24 @@ export function work(numCycles) {
// Cap the number of cycles being processed to whatever would put you at
// the work time limit (8 hours)
var overMax = false;
if (this.timeWorked + Engine._idleSpeed * numCycles >= CONSTANTS.MillisecondsPer8Hours) {
if (this.timeWorked + CONSTANTS._idleSpeed * numCycles >= CONSTANTS.MillisecondsPer8Hours) {
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.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 (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer8Hours) {
return this.finishWork(false);
return true;
}
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,
);
return false;
}
export function finishWork(cancelled, sing = false) {
@ -731,10 +649,7 @@ export function finishWork(cancelled, sing = false) {
dialogBoxCreate(content);
}
var mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "visible";
this.isWorking = false;
Engine.loadLocationContent(false);
if (sing) {
var res =
@ -764,7 +679,7 @@ export function finishWork(cancelled, sing = false) {
this.resetWorkStatus();
}
export function startWorkPartTime(companyName) {
export function startWorkPartTime(router, companyName) {
this.resetWorkStatus(CONSTANTS.WorkTypeCompanyPartTime, companyName);
this.isWorking = true;
this.focus = true;
@ -781,27 +696,18 @@ export function startWorkPartTime(companyName) {
this.workMoneyGainRate = this.getWorkMoneyGain();
this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer8Hours;
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();
router.toWork();
}
export function workPartTime(numCycles) {
//Cap the number of cycles being processed to whatever would put you at the
//work time limit (8 hours)
var overMax = false;
if (this.timeWorked + Engine._idleSpeed * numCycles >= CONSTANTS.MillisecondsPer8Hours) {
if (this.timeWorked + CONSTANTS._idleSpeed * numCycles >= CONSTANTS.MillisecondsPer8Hours) {
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.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 (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer8Hours) {
return this.finishWorkPartTime();
return true;
}
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,
);
return false;
}
export function finishWorkPartTime(sing = false) {
@ -894,10 +748,7 @@ export function finishWorkPartTime(sing = false) {
dialogBoxCreate(content);
}
var mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "visible";
this.isWorking = false;
Engine.loadLocationContent(false);
if (sing) {
var res =
"You worked for " +
@ -928,21 +779,15 @@ export function finishWorkPartTime(sing = false) {
}
export function startFocusing() {
const mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "hidden";
this.focus = true;
Engine.loadWorkInProgressContent();
}
export function stopFocusing() {
const mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "visible";
this.focus = false;
Engine.loadTerminalContent();
}
/* Working for Faction */
export function startFactionWork(faction) {
export function startFactionWork(router, faction) {
//Update reputation gain rate to account for faction favor
var favorMult = 1 + faction.favor / 100;
if (isNaN(favorMult)) {
@ -957,27 +802,10 @@ export function startFactionWork(faction) {
this.currentWorkFactionName = faction.name;
this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer20Hours;
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();
router.toWork();
}
export function startFactionHackWork(faction) {
export function startFactionHackWork(router, faction) {
this.resetWorkStatus(CONSTANTS.WorkTypeFaction, faction.name, CONSTANTS.FactionWorkHacking);
this.workHackExpGainRate = 0.15 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
@ -989,10 +817,10 @@ export function startFactionHackWork(faction) {
this.factionWorkType = CONSTANTS.FactionWorkHacking;
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.workHackExpGainRate = 0.1 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
@ -1006,10 +834,10 @@ export function startFactionFieldWork(faction) {
this.factionWorkType = CONSTANTS.FactionWorkField;
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.workHackExpGainRate = 0.05 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
@ -1023,7 +851,7 @@ export function startFactionSecurityWork(faction) {
this.factionWorkType = CONSTANTS.FactionWorkSecurity;
this.currentWorkFactionDescription = "performing security detail";
this.startFactionWork(faction);
this.startFactionWork(router, faction);
}
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)
var overMax = false;
if (this.timeWorked + Engine._idleSpeed * numCycles >= CONSTANTS.MillisecondsPer20Hours) {
if (this.timeWorked + CONSTANTS._idleSpeed * numCycles >= CONSTANTS.MillisecondsPer20Hours) {
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);
//If timeWorked == 20 hours, then finish. You can only work for the faction for 20 hours
if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer20Hours) {
return this.finishFactionWork(false);
this.finishFactionWork(false);
return true;
}
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,
);
return 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;
Engine.loadFactionContent();
displayFactionContent(faction.name);
if (sing) {
var res =
"You worked for your faction " +
@ -1387,7 +1174,7 @@ export function getWorkRepGain() {
// }
/* Creating a Program */
export function startCreateProgramWork(programName, time, reqLevel) {
export function startCreateProgramWork(router, programName, time, reqLevel) {
this.resetWorkStatus();
this.isWorking = true;
this.focus = true;
@ -1419,19 +1206,7 @@ export function startCreateProgramWork(programName, time, reqLevel) {
}
this.createProgramName = programName;
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();
router.toWork();
}
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
//Skill multiplier directly applied to "time worked"
this.timeWorked += Engine._idleSpeed * numCycles;
this.timeWorkedCreateProgram += Engine._idleSpeed * numCycles * skillMult;
var programName = this.createProgramName;
this.timeWorked += CONSTANTS._idleSpeed * numCycles;
this.timeWorkedCreateProgram += CONSTANTS._idleSpeed * numCycles * skillMult;
if (this.timeWorkedCreateProgram >= this.timeNeededToCompleteWork) {
this.finishCreateProgramWork(false);
return true;
}
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,
);
return false;
}
export function finishCreateProgramWork(cancelled) {
@ -1483,17 +1244,13 @@ export function finishCreateProgramWork(cancelled) {
this.gainIntelligenceExp(this.createProgramReqLvl / CONSTANTS.IntelligenceProgramBaseExpGain);
}
var mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "visible";
this.isWorking = false;
Engine.loadTerminalContent();
this.resetWorkStatus();
}
/* Studying/Taking Classes */
export function startClass(costMult, expMult, className) {
export function startClass(router, costMult, expMult, className) {
this.resetWorkStatus();
this.isWorking = true;
this.focus = true;
@ -1501,7 +1258,7 @@ export function startClass(costMult, expMult, className) {
this.className = className;
const gameCPS = 1000 / Engine._idleSpeed;
const gameCPS = 1000 / CONSTANTS._idleSpeed;
//Find cost and exp gain per game cycle
var cost = 0;
@ -1564,62 +1321,13 @@ export function startClass(costMult, expMult, className) {
this.workDexExpGainRate = dexExp * this.dexterity_exp_mult * BitNodeMultipliers.ClassGymExpGain;
this.workAgiExpGainRate = agiExp * this.agility_exp_mult * BitNodeMultipliers.ClassGymExpGain;
this.workChaExpGainRate = chaExp * this.charisma_exp_mult * BitNodeMultipliers.ClassGymExpGain;
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();
router.toWork();
}
export function takeClass(numCycles) {
this.timeWorked += Engine._idleSpeed * numCycles;
var className = this.className;
this.timeWorked += CONSTANTS._idleSpeed * numCycles;
this.processWorkEarnings(numCycles);
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,
);
return false;
}
//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;
Engine.loadLocationContent(false);
if (sing) {
var res =
"After " +
@ -1686,7 +1390,19 @@ export function finishClass(sing = false) {
}
//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.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.timeNeededToCompleteWork = time;
//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();
router.toWork();
}
export function commitCrime(numCycles) {
this.timeWorked += Engine._idleSpeed * numCycles;
this.timeWorked += CONSTANTS._idleSpeed * numCycles;
if (this.timeWorked >= this.timeNeededToCompleteWork) {
this.finishCrime(false);
return;
return true;
}
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;");
return false;
}
export function finishCrime(cancelled) {
@ -1892,11 +1576,9 @@ export function finishCrime(cancelled) {
}
this.committingCrimeThruSingFn = false;
this.singFnCrimeWorkerScript = null;
var mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "visible";
this.isWorking = false;
this.crimeType = "";
this.resetWorkStatus();
Engine.loadLocationContent(false);
}
//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 {
return (
<>
{StatsTable(
[
<StatsTable
rows={[
["Money ", <Money money={props.sleeve.earningsForTask.money} />],
["Hacking Exp ", numeralWrapper.formatExp(props.sleeve.earningsForTask.hack)],
["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)],
["Agility Exp ", numeralWrapper.formatExp(props.sleeve.earningsForTask.agi)],
["Charisma Exp ", numeralWrapper.formatExp(props.sleeve.earningsForTask.cha)],
],
"Earnings for Current Task:",
)}
]}
title="Earnings for Current Task:"
/>
<br />
{StatsTable(
[
<StatsTable
rows={[
["Money: ", <Money money={props.sleeve.earningsForPlayer.money} />],
["Hacking Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForPlayer.hack)],
["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)],
["Agility Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForPlayer.agi)],
["Charisma Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForPlayer.cha)],
],
"Total Earnings for Host Consciousness:",
)}
]}
title="Total Earnings for Host Consciousness:"
/>
<br />
{StatsTable(
[
<StatsTable
rows={[
["Money: ", <Money money={props.sleeve.earningsForSleeves.money} />],
["Hacking Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForSleeves.hack)],
["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)],
["Agility Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForSleeves.agi)],
["Charisma Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForSleeves.cha)],
],
"Total Earnings for Other Sleeves:",
)}
]}
title="Total Earnings for Other Sleeves:"
/>
<br />
</>
);

@ -10,20 +10,20 @@ interface IProps {
export function MoreStatsContent(props: IProps): React.ReactElement {
return (
<>
{StatsTable(
[
<StatsTable
rows={[
["Hacking: ", props.sleeve.hacking_skill, `(${numeralWrapper.formatExp(props.sleeve.hacking_exp)} exp)`],
["Strength: ", props.sleeve.strength, `(${numeralWrapper.formatExp(props.sleeve.strength_exp)} exp)`],
["Defense: ", props.sleeve.defense, `(${numeralWrapper.formatExp(props.sleeve.defense_exp)} exp)`],
["Dexterity: ", props.sleeve.dexterity, `(${numeralWrapper.formatExp(props.sleeve.dexterity_exp)} exp)`],
["Agility: ", props.sleeve.agility, `(${numeralWrapper.formatExp(props.sleeve.agility_exp)} exp)`],
["Charisma: ", props.sleeve.charisma, `(${numeralWrapper.formatExp(props.sleeve.charisma_exp)} exp)`],
],
"Stats:",
)}
]}
title="Stats:"
/>
<br />
{StatsTable(
[
<StatsTable
rows={[
["Hacking Level multiplier: ", numeralWrapper.formatPercentage(props.sleeve.hacking_mult)],
["Hacking Experience multiplier: ", numeralWrapper.formatPercentage(props.sleeve.hacking_exp_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)],
["Crime Money multiplier: ", numeralWrapper.formatPercentage(props.sleeve.crime_money_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