Merge pull request #1283 from danielyxie/dev

New terminal
This commit is contained in:
hydroflame 2021-09-16 21:54:07 -04:00 committed by GitHub
commit 2cb762184f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
130 changed files with 8638 additions and 4728 deletions

5
.gitignore vendored

@ -1,3 +1,4 @@
.DS_Store
.vscode
Changelog.txt
Netburner.txt
@ -9,5 +10,9 @@ Netburner.txt
/test/*.css
.cypress
# tmp folder for electron
.package
.build
# editor files
.vscode

@ -5,120 +5,8 @@
* Styling for the Character Overview Panel (top-right panel)
*/
#character-overview-wrapper {
#character-overview {
position: fixed;
top: 0;
right: 0;
}
#character-overview-container {
display: none;
position: absolute; /* Stay in place */
right: 0;
top: 0;
height: auto; /* Full height */
padding: 10px 2px;
border: 2px solid var(--my-highlight-color);
width: auto;
max-width: 280px;
overflow: auto; /* Enable scroll if needed */
background-color: rgba(57, 54, 54, 0.9); /* Fallback color */
z-index: 1;
}
#character-overview-text {
color: $my-stat-physical;
table {
border-collapse: collapse;
margin: auto;
}
td {
padding: 2px;
vertical-align: middle;
}
}
.character-stat-text {
color: #fff;
background-color: #444;
}
.character-stat-cell {
text-align: right;
}
#character-str-wrapper td,
#character-cha-wrapper td {
padding-top: 10px;
}
.character-divider td {
border-top: 1px #aaa solid;
padding-top: 10px;
}
#character-hp-wrapper {
color: $my-stat-hp-color;
}
.character-hp-cell {
color: $my-stat-hp-color;
}
#character-money-wrapper {
color: $my-stat-money-color;
}
.character-money-cell {
color: $my-stat-money-color;
}
#character-hack-wrapper {
color: $my-stat-hack-color;
}
.character-hack-cell {
color: $my-stat-hack-color;
}
#character-cha-wrapper {
color: $my-stat-cha-color;
}
.character-cha-cell {
color: $my-stat-cha-color;
}
#character-int-wrapper {
color: $my-stat-int-color;
}
.character-int-cell {
color: $my-stat-int-color;
}
.character-combat-cell {
color: $my-stat-physical;
}
#character-work-wrapper {
color: $my-stat-hack-color;
}
.character-work-cell {
color: $my-stat-hack-color;
}
.character-overview-btn {
@include borderRadius(12px);
@include boxShadow(1px 1px 3px #000);
color: #cecece;
display: inline-block;
font-size: $defaultFontSize * 0.875;
font-weight: bold;
height: 25px;
background-color: #000;
padding: 5px 8px;
}
.character-quick-options {
margin-top: 10px;
text-align: center;
}
.character-overview-btn:hover,
.character-overview-btn:focus {
color: #fff;
text-decoration: none;
cursor: pointer;
}

@ -63,6 +63,8 @@
overflow: visible;
top: 10px;
width: 45%;
vertical-align: top;
margin-top: 10px;
}
.cmpy-mgmt-industry-overview-panel {

@ -4,18 +4,12 @@
/* CSS for different main menu pages, such as character info, script editor, etc (but excluding
terminal which has its own page) */
.generic-menupage-container {
padding-left: 2px;
#generic-react-container {
-ms-overflow-style: none; /* for Internet Explorer, Edge */
scrollbar-width: none; /* for Firefox */
flex-grow: 1;
}
#generic-react-container {
padding: 10px;
overflow-y: scroll;
}
#generic-react-container::-webkit-scrollbar {
display: none; /* for Chrome, Safari, and Opera */
}

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([1045,0]),o()}({1045:function(n,t,o){"use strict";o.r(t);o(1046),o(1048),o(1050),o(1052),o(1054),o(1056),o(1058),o(1060),o(1062),o(1064),o(1066),o(1068),o(1070),o(1072),o(1074),o(1076),o(1078),o(1080),o(1082),o(1084),o(1086),o(1088),o(1090),o(1092),o(1094),o(1096),o(1098),o(1100),o(1102),o(1104),o(1106)},1048:function(n,t,o){},1050:function(n,t,o){},1052:function(n,t,o){},1054:function(n,t,o){},1056:function(n,t,o){},1058:function(n,t,o){},1060:function(n,t,o){},1062:function(n,t,o){},1064:function(n,t,o){},1066:function(n,t,o){},1068:function(n,t,o){},1070:function(n,t,o){},1072:function(n,t,o){},1074:function(n,t,o){},1076:function(n,t,o){},1078:function(n,t,o){},1080:function(n,t,o){},1082:function(n,t,o){},1084:function(n,t,o){},1086:function(n,t,o){},1088:function(n,t,o){},1090:function(n,t,o){},1092:function(n,t,o){},1094:function(n,t,o){},1096:function(n,t,o){},1098:function(n,t,o){},1100:function(n,t,o){},1102:function(n,t,o){},1104:function(n,t,o){},1106: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([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){}});
//# sourceMappingURL=engineStyle.bundle.js.map

119
dist/engineStyle.css vendored

@ -1226,117 +1226,11 @@ button {
/**
* Styling for the Character Overview Panel (top-right panel)
*/
#character-overview-wrapper {
#character-overview {
position: fixed;
top: 0;
right: 0; }
#character-overview-container {
display: none;
position: absolute;
/* Stay in place */
right: 0;
top: 0;
height: auto;
/* Full height */
padding: 10px 2px;
border: 2px solid var(--my-highlight-color);
width: auto;
max-width: 280px;
overflow: auto;
/* Enable scroll if needed */
background-color: rgba(57, 54, 54, 0.9);
/* Fallback color */
z-index: 1; }
#character-overview-text {
color: #faffdf; }
#character-overview-text table {
border-collapse: collapse;
margin: auto; }
#character-overview-text td {
padding: 2px;
vertical-align: middle; }
.character-stat-text {
color: #fff;
background-color: #444; }
.character-stat-cell {
text-align: right; }
#character-str-wrapper td,
#character-cha-wrapper td {
padding-top: 10px; }
.character-divider td {
border-top: 1px #aaa solid;
padding-top: 10px; }
#character-hp-wrapper {
color: #dd3434; }
.character-hp-cell {
color: #dd3434; }
#character-money-wrapper {
color: #ffd700; }
.character-money-cell {
color: #ffd700; }
#character-hack-wrapper {
color: #adff2f; }
.character-hack-cell {
color: #adff2f; }
#character-cha-wrapper {
color: #a671d1; }
.character-cha-cell {
color: #a671d1; }
#character-int-wrapper {
color: #6495ed; }
.character-int-cell {
color: #6495ed; }
.character-combat-cell {
color: #faffdf; }
#character-work-wrapper {
color: #adff2f; }
.character-work-cell {
color: #adff2f; }
.character-overview-btn {
-webkit-border-radius: 12px;
-moz-border-radius: 12px;
border-radius: 12px;
-webkit-box-shadow: 1px 1px 3px #000;
-moz-box-shadow: 1px 1px 3px #000;
box-shadow: 1px 1px 3px #000;
color: #cecece;
display: inline-block;
font-size: 14px;
font-weight: bold;
height: 25px;
background-color: #000;
padding: 5px 8px; }
.character-quick-options {
margin-top: 10px;
text-align: center; }
.character-overview-btn:hover,
.character-overview-btn:focus {
color: #fff;
text-decoration: none;
cursor: pointer; }
/* COLORS */
/* Attributes */
#terminal-container {
@ -1639,18 +1533,13 @@ button {
/* Attributes */
/* CSS for different main menu pages, such as character info, script editor, etc (but excluding
terminal which has its own page) */
.generic-menupage-container {
padding-left: 2px;
#generic-react-container {
-ms-overflow-style: none;
/* for Internet Explorer, Edge */
scrollbar-width: none;
/* for Firefox */
flex-grow: 1; }
#generic-react-container {
padding: 10px;
overflow-y: scroll; }
#generic-react-container::-webkit-scrollbar {
display: none;
/* for Chrome, Safari, and Opera */ }
@ -3041,7 +2930,9 @@ input[type="checkbox"] {
overflow-x: auto;
overflow: visible;
top: 10px;
width: 45%; }
width: 45%;
vertical-align: top;
margin-top: 10px; }
.cmpy-mgmt-industry-overview-panel {
border: 1px solid #fff;

225
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

BIN
electron/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

19
electron/main.js Normal file

@ -0,0 +1,19 @@
const { app, BrowserWindow, Menu } = require("electron");
Menu.setApplicationMenu(false);
function createWindow() {
const win = new BrowserWindow({
show: false,
webPreferences: {
devTools: false,
},
});
win.removeMenu();
win.maximize();
win.loadFile("index.html");
win.show();
}
app.whenReady().then(() => {
createWindow();
});

7
electron/package.json Executable file

@ -0,0 +1,7 @@
{
"name": "bitburner",
"version": "1.0.0",
"description": "A cyberpunk-themed programming incremental game",
"main": "main.js",
"author": "Daniel Xie"
}

@ -42,22 +42,8 @@
<div id="mainmenu-container" style="display: flex; flex-direction: row">
<!-- Main menu -->
<div id="sidebar" style=""></div>
<!-- Terminal page -->
<div id="terminal-container" style="flex-grow: 1">
<table id="terminal">
<tr id="terminal-input">
<td id="terminal-input-td" tabindex="2">
$
<input type="text" id="terminal-input-text-box" class="terminal-input" tabindex="1" onfocus="this.value = this.value;" autocomplete="off"/>
</td>
</tr>
</table>
</div>
<div class="generic-menupage-container">
<div id="generic-react-container"></div>
</div>
</div>
<div id="infiltration-container" class="generic-fullscreen-container"></div>
<div id="mission-container" class="generic-fullscreen-container"></div>
@ -89,16 +75,7 @@
</div>
<!-- Character Overview Screen -->
<div id="character-overview-wrapper">
<div id="character-overview-container">
<div id="character-overview-text">
<!-- ReactJS Component -->
</div>
<div class="character-quick-options noselect">
<button id="character-overview-save-button" class="character-overview-btn">Save Game</button>
</div>
</div>
</div>
<div id="character-overview"></div>
<!-- Status text -->
<div id="status-text-container">

4072
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,4 +1,8 @@
{
"name": "bitburner",
"license": "SEE LICENSE IN license.txt",
"version": "0.53.0",
"main": "electron-main.js",
"author": {
"name": "Daniel Xie"
},
@ -6,13 +10,17 @@
"url": "https://github.com/danielyxie/bitburner/issues"
},
"dependencies": {
"@material-ui/core": "^4.11.3",
"@material-ui/icons": "^4.11.2",
"@emotion/react": "^11.4.1",
"@emotion/styled": "^11.3.0",
"@monaco-editor/react": "^4.2.2",
"@mui/icons-material": "^5.0.0-rc.1",
"@mui/lab": "^5.0.0-alpha.46",
"@mui/material": "^5.0.0-rc.1",
"@mui/styles": "^5.0.0-rc.1",
"@types/js-beautify": "^1.13.2",
"@types/numeral": "0.0.25",
"@types/react": "^16.8.6",
"@types/react-dom": "^16.8.2",
"@types/react": "^17.0.21",
"@types/react-dom": "^17.0.9",
"acorn": "^8.4.1",
"acorn-walk": "^8.1.1",
"ajv": "^5.1.5",
@ -42,8 +50,8 @@
"node-sass": "^6.0.1",
"normalize.css": "^8.0.0",
"numeral": "2.0.6",
"react": "^16.8.3",
"react-dom": "^16.8.3",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-modal": "^3.12.1",
"sprintf-js": "^1.1.1",
"tapable": "^1.0.0",
@ -59,6 +67,7 @@
"@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.15.0",
"@testing-library/cypress": "^8.0.1",
"@types/file-saver": "^2.0.3",
"@types/jest": "^27.0.1",
"@types/lodash": "^4.14.168",
"@types/node": "^16.9.1",
@ -71,6 +80,8 @@
"bundle-loader": "~0.5.0",
"css-loader": "^0.28.11",
"cypress": "^8.3.1",
"electron": "^14.0.1",
"electron-packager": "^15.4.0",
"es6-promise-polyfill": "^1.1.1",
"eslint": "^7.24.0",
"eslint-plugin-node": "^11.1.0",
@ -120,8 +131,6 @@
"node": ">=8 || <=9"
},
"homepage": "https://github.com/danielyxie/bitburner",
"license": "SEE LICENSE IN license.txt",
"name": "bitburner",
"repository": {
"type": "git",
"url": "git+https://github.com/danielyxie/bitburner.git"
@ -144,7 +153,7 @@
"test": "jest",
"test:watch": "jest --watch",
"watch": "webpack --watch --mode production",
"watch:dev": "webpack --watch --mode development"
},
"version": "0.53.0"
"watch:dev": "webpack --watch --mode development",
"package-electron": "electron-packager .package bitburner --all --out .build --overwrite --icon .package/icon.png"
}
}

14
package.sh Executable file

@ -0,0 +1,14 @@
# npm install electron --save-dev
# npm install electron-packager --save-dev
mkdir -p .package/dist || true
cp index.html .package
cp electron/* .package
cp dist/engine.bundle.js .package/dist
cp dist/engineStyle.css .package/dist
cp dist/vendor.css .package/dist
cp dist/engineStyle.bundle.js .package/dist
cp dist/vendor.bundle.js .package/dist
npm run package-electron

@ -1,5 +1,5 @@
import { IMap } from "./types";
import { post } from "./ui/postToTerminal";
import { Terminal } from "./Terminal";
export let Aliases: IMap<string> = {};
export let GlobalAliases: IMap<string> = {};
@ -24,12 +24,12 @@ export function loadGlobalAliases(saveString: string): void {
export function printAliases(): void {
for (const name in Aliases) {
if (Aliases.hasOwnProperty(name)) {
post("alias " + name + "=" + Aliases[name]);
Terminal.print("alias " + name + "=" + Aliases[name]);
}
}
for (const name in GlobalAliases) {
if (GlobalAliases.hasOwnProperty(name)) {
post("global alias " + name + "=" + GlobalAliases[name]);
Terminal.print("global alias " + name + "=" + GlobalAliases[name]);
}
}
}

@ -5,7 +5,7 @@ import { Money } from "../ui/React/Money";
import { Game } from "./Game";
import { Deck } from "./CardDeck/Deck";
import { Hand } from "./CardDeck/Hand";
import { InputAdornment } from "@material-ui/core";
import { InputAdornment } from "@mui/material";
import { ReactCard } from "./CardDeck/ReactCard";
import { MuiTextField } from "../ui/React/MuiTextField";
import { MuiButton } from "../ui/React/MuiButton";

@ -179,7 +179,6 @@ export class CodingContract {
removePopup(popupId);
},
onAttempt: (val: string) => {
console.error("attempting");
if (this.isSolution(val)) {
resolve(CodingContractResult.Success);
} else {

@ -1,10 +1,9 @@
import * as React from "react";
import { DarkWebItems } from "./DarkWebItems";
import { Player } from "../Player";
import { Terminal } from "../Terminal";
import { SpecialServerIps } from "../Server/SpecialServerIps";
import { post, postElement } from "../ui/postToTerminal";
import { Money } from "../ui/React/Money";
import { numeralWrapper } from "../ui/numeralFormat";
import { isValidIPAddress } from "../../utils/helpers/isValidIPAddress";
@ -16,7 +15,7 @@ export function checkIfConnectedToDarkweb(): void {
return;
}
if (darkwebIp == Player.getCurrentServer().ip) {
post(
Terminal.print(
"You are now connected to the dark web. From the dark web you can purchase illegal items. " +
"Use the 'buy -l' command to display a list of all the items you can buy. Use 'buy [item-name] " +
"to purchase an item.",
@ -35,9 +34,9 @@ export function executeDarkwebTerminalCommand(commandArray: string[]): void {
switch (commandArray[0]) {
case "buy": {
if (commandArray.length != 2) {
post("Incorrect number of arguments. Usage: ");
post("buy -l");
post("buy [item name]");
Terminal.error("Incorrect number of arguments. Usage: ");
Terminal.print("buy -l");
Terminal.print("buy [item name]");
return;
}
const arg = commandArray[1];
@ -49,23 +48,19 @@ export function executeDarkwebTerminalCommand(commandArray: string[]): void {
break;
}
default:
post("Command not found");
Terminal.error("Command not found");
break;
}
}
function listAllDarkwebItems(): void {
export function listAllDarkwebItems(): void {
for (const key in DarkWebItems) {
const item = DarkWebItems[key];
postElement(
<>
{item.program} - <Money money={item.price} player={Player} /> - {item.description}
</>,
);
Terminal.print(`${item.program} - ${numeralWrapper.formatMoney(item.price)} - ${item.description}`);
}
}
function buyDarkwebItem(itemName: string): void {
export function buyDarkwebItem(itemName: string): void {
itemName = itemName.toLowerCase();
// find the program that matches, if any
@ -79,24 +74,26 @@ function buyDarkwebItem(itemName: string): void {
// return if invalid
if (item === null) {
post("Unrecognized item: " + itemName);
Terminal.print("Unrecognized item: " + itemName);
return;
}
// return if the player already has it.
if (Player.hasProgram(item.program)) {
post("You already have the " + item.program + " program");
Terminal.print("You already have the " + item.program + " program");
return;
}
// return if the player doesn't have enough money
if (Player.money.lt(item.price)) {
post("Not enough money to purchase " + item.program);
Terminal.print("Not enough money to purchase " + item.program);
return;
}
// buy and push
Player.loseMoney(item.price);
Player.getHomeComputer().programs.push(item.program);
post("You have purchased the " + item.program + " program. The new program can be found on your home computer.");
Terminal.print(
"You have purchased the " + item.program + " program. The new program can be found on your home computer.",
);
}

@ -3,7 +3,7 @@ import { Bladeburner } from "./Bladeburner/Bladeburner";
import { IEngine } from "./IEngine";
import React from "react";
import { Theme } from "./ui/React/Theme";
import { TTheme as Theme } from "./ui/React/Theme";
import { General } from "./DevMenu/ui/General";
import { Stats } from "./DevMenu/ui/Stats";

@ -1,11 +1,11 @@
import React, { useState } from "react";
import AddIcon from "@material-ui/icons/Add";
import RemoveIcon from "@material-ui/icons/Remove";
import IconButton from "@material-ui/core/IconButton";
import ExposureZeroIcon from "@material-ui/icons/ExposureZero";
import DoubleArrowIcon from "@material-ui/icons/DoubleArrow";
import TextField from "@material-ui/core/TextField";
import Tooltip from "@material-ui/core/Tooltip";
import AddIcon from "@mui/icons-material/Add";
import RemoveIcon from "@mui/icons-material/Remove";
import IconButton from "@mui/material/IconButton";
import ClearIcon from "@mui/icons-material/Clear";
import DoubleArrowIcon from "@mui/icons-material/DoubleArrow";
import TextField from "@mui/material/TextField";
import Tooltip from "@mui/material/Tooltip";
interface IProps {
label: string;
@ -37,12 +37,12 @@ export function Adjuster(props: IProps): React.ReactElement {
startAdornment: (
<>
<Tooltip title="Add a lot">
<IconButton color="primary" onClick={tons}>
<IconButton color="primary" onClick={tons} size="large">
<DoubleArrowIcon style={{ transform: "rotate(-90deg)" }} />
</IconButton>
</Tooltip>
<Tooltip title="Add">
<IconButton color="primary" onClick={() => add(typeof value !== "string" ? value : 0)}>
<IconButton color="primary" onClick={() => add(typeof value !== "string" ? value : 0)} size="large">
<AddIcon />
</IconButton>
</Tooltip>
@ -51,13 +51,17 @@ export function Adjuster(props: IProps): React.ReactElement {
endAdornment: (
<>
<Tooltip title="Remove">
<IconButton color="primary" onClick={() => subtract(typeof value !== "string" ? value : 0)}>
<IconButton
color="primary"
onClick={() => subtract(typeof value !== "string" ? value : 0)}
size="large"
>
<RemoveIcon />
</IconButton>
</Tooltip>
<Tooltip title="Reset">
<IconButton color="primary" onClick={reset}>
<ExposureZeroIcon />
<IconButton color="primary" onClick={reset} size="large">
<ClearIcon />
</IconButton>
</Tooltip>
</>

@ -1,17 +1,18 @@
import React, { useState } from "react";
import Accordion from "@material-ui/core/Accordion";
import AccordionSummary from "@material-ui/core/AccordionSummary";
import AccordionDetails from "@material-ui/core/AccordionDetails";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
import AccordionDetails from "@mui/material/AccordionDetails";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import Select from "@material-ui/core/Select";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import MenuItem from "@material-ui/core/MenuItem";
import IconButton from "@material-ui/core/IconButton";
import ReplyAllIcon from "@material-ui/icons/ReplyAll";
import ReplyIcon from "@material-ui/icons/Reply";
import MenuItem from "@mui/material/MenuItem";
import IconButton from "@mui/material/IconButton";
import ReplyAllIcon from "@mui/icons-material/ReplyAll";
import ReplyIcon from "@mui/icons-material/Reply";
import ClearIcon from "@mui/icons-material/Clear";
interface IProps {
player: IPlayer;
@ -20,7 +21,7 @@ interface IProps {
export function Augmentations(props: IProps): React.ReactElement {
const [augmentation, setAugmentation] = useState("Augmented Targeting I");
function setAugmentationDropdown(event: React.ChangeEvent<{ value: unknown }>): void {
function setAugmentationDropdown(event: SelectChangeEvent<string>): void {
setAugmentation(event.target.value as string);
}
function queueAug(): void {
@ -33,6 +34,11 @@ export function Augmentations(props: IProps): React.ReactElement {
props.player.queueAugmentation(augName);
}
}
function clearAugs(): void {
props.player.augmentations = [];
}
return (
<Accordion>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
@ -53,14 +59,21 @@ export function Augmentations(props: IProps): React.ReactElement {
value={augmentation}
startAdornment={
<>
<IconButton color="primary" onClick={queueAllAugs}>
<IconButton color="primary" onClick={queueAllAugs} size="large">
<ReplyAllIcon />
</IconButton>
<IconButton color="primary" onClick={queueAug}>
<IconButton color="primary" onClick={queueAug} size="large">
<ReplyIcon />
</IconButton>
</>
}
endAdornment={
<>
<IconButton color="primary" onClick={clearAugs} size="large">
<ClearIcon />
</IconButton>
</>
}
>
{Object.values(AugmentationNames).map((aug) => (
<MenuItem key={aug} value={aug}>

@ -1,9 +1,9 @@
import React from "react";
import Accordion from "@material-ui/core/Accordion";
import AccordionSummary from "@material-ui/core/AccordionSummary";
import AccordionDetails from "@material-ui/core/AccordionDetails";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
import AccordionDetails from "@mui/material/AccordionDetails";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import { Adjuster } from "./Adjuster";
import { IPlayer } from "../../PersonObjects/IPlayer";

@ -1,19 +1,19 @@
import React, { useState } from "react";
import Accordion from "@material-ui/core/Accordion";
import AccordionSummary from "@material-ui/core/AccordionSummary";
import AccordionDetails from "@material-ui/core/AccordionDetails";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
import AccordionDetails from "@mui/material/AccordionDetails";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import Button from "@material-ui/core/Button";
import Select from "@material-ui/core/Select";
import MenuItem from "@material-ui/core/MenuItem";
import Button from "@mui/material/Button";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem";
import { generateContract, generateRandomContract, generateRandomContractOnHome } from "../../CodingContractGenerator";
import { CodingContractTypes } from "../../CodingContracts";
export function CodingContracts(): React.ReactElement {
const [codingcontract, setCodingcontract] = useState("Find Largest Prime Factor");
function setCodingcontractDropdown(event: React.ChangeEvent<{ value: unknown }>): void {
function setCodingcontractDropdown(event: SelectChangeEvent<string>): void {
setCodingcontract(event.target.value as string);
}

@ -1,21 +1,21 @@
import React, { useState } from "react";
import Accordion from "@material-ui/core/Accordion";
import AccordionSummary from "@material-ui/core/AccordionSummary";
import AccordionDetails from "@material-ui/core/AccordionDetails";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
import AccordionDetails from "@mui/material/AccordionDetails";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import Button from "@material-ui/core/Button";
import Select from "@material-ui/core/Select";
import Button from "@mui/material/Button";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import { Companies as AllCompanies } from "../../Company/Companies";
import MenuItem from "@material-ui/core/MenuItem";
import MenuItem from "@mui/material/MenuItem";
import { Adjuster } from "./Adjuster";
const bigNumber = 1e12;
export function Companies(): React.ReactElement {
const [company, setCompany] = useState("ECorp");
function setCompanyDropdown(event: React.ChangeEvent<{ value: unknown }>): void {
function setCompanyDropdown(event: SelectChangeEvent<string>): void {
setCompany(event.target.value as string);
}
function resetCompanyRep(): void {

@ -1,11 +1,11 @@
import React from "react";
import Accordion from "@material-ui/core/Accordion";
import AccordionSummary from "@material-ui/core/AccordionSummary";
import AccordionDetails from "@material-ui/core/AccordionDetails";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
import AccordionDetails from "@mui/material/AccordionDetails";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import Button from "@material-ui/core/Button";
import Button from "@mui/material/Button";
import { Adjuster } from "./Adjuster";
import { IPlayer } from "../../PersonObjects/IPlayer";

@ -1,21 +1,21 @@
import React, { useState } from "react";
import Accordion from "@material-ui/core/Accordion";
import AccordionSummary from "@material-ui/core/AccordionSummary";
import AccordionDetails from "@material-ui/core/AccordionDetails";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
import AccordionDetails from "@mui/material/AccordionDetails";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import Button from "@material-ui/core/Button";
import Select from "@material-ui/core/Select";
import Button from "@mui/material/Button";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import { Adjuster } from "./Adjuster";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Factions as AllFaction } from "../../Faction/Factions";
import FormControl from "@material-ui/core/FormControl";
import MenuItem from "@material-ui/core/MenuItem";
import IconButton from "@material-ui/core/IconButton";
import ReplyAllIcon from "@material-ui/icons/ReplyAll";
import ReplyIcon from "@material-ui/icons/Reply";
import InputLabel from "@material-ui/core/InputLabel";
import FormControl from "@mui/material/FormControl";
import MenuItem from "@mui/material/MenuItem";
import IconButton from "@mui/material/IconButton";
import ReplyAllIcon from "@mui/icons-material/ReplyAll";
import ReplyIcon from "@mui/icons-material/Reply";
import InputLabel from "@mui/material/InputLabel";
const bigNumber = 1e12;
@ -26,7 +26,7 @@ interface IProps {
export function Factions(props: IProps): React.ReactElement {
const [faction, setFaction] = useState("Illuminati");
function setFactionDropdown(event: React.ChangeEvent<{ value: unknown }>): void {
function setFactionDropdown(event: SelectChangeEvent<string>): void {
setFaction(event.target.value as string);
}
@ -119,10 +119,10 @@ export function Factions(props: IProps): React.ReactElement {
value={faction}
startAdornment={
<>
<IconButton color="primary" onClick={receiveAllInvites}>
<IconButton color="primary" onClick={receiveAllInvites} size="large">
<ReplyAllIcon />
</IconButton>
<IconButton color="primary" onClick={receiveInvite}>
<IconButton color="primary" onClick={receiveInvite} size="large">
<ReplyIcon />
</IconButton>
</>

@ -1,9 +1,9 @@
import React from "react";
import Accordion from "@material-ui/core/Accordion";
import AccordionSummary from "@material-ui/core/AccordionSummary";
import AccordionDetails from "@material-ui/core/AccordionDetails";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
import AccordionDetails from "@mui/material/AccordionDetails";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import { Adjuster } from "./Adjuster";
import { IPlayer } from "../../PersonObjects/IPlayer";

@ -1,11 +1,11 @@
import React from "react";
import Accordion from "@material-ui/core/Accordion";
import AccordionSummary from "@material-ui/core/AccordionSummary";
import AccordionDetails from "@material-ui/core/AccordionDetails";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
import AccordionDetails from "@mui/material/AccordionDetails";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import Button from "@material-ui/core/Button";
import Button from "@mui/material/Button";
import { Money } from "../../ui/React/Money";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { hackWorldDaemon } from "../../RedPill";

@ -1,15 +1,15 @@
import React, { useState } from "react";
import Accordion from "@material-ui/core/Accordion";
import AccordionSummary from "@material-ui/core/AccordionSummary";
import AccordionDetails from "@material-ui/core/AccordionDetails";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
import AccordionDetails from "@mui/material/AccordionDetails";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import Button from "@material-ui/core/Button";
import Select from "@material-ui/core/Select";
import Button from "@mui/material/Button";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Programs as AllPrograms } from "../../Programs/Programs";
import MenuItem from "@material-ui/core/MenuItem";
import MenuItem from "@mui/material/MenuItem";
interface IProps {
player: IPlayer;
@ -17,7 +17,7 @@ interface IProps {
export function Programs(props: IProps): React.ReactElement {
const [program, setProgram] = useState("NUKE.exe");
function setProgramDropdown(event: React.ChangeEvent<{ value: unknown }>): void {
function setProgramDropdown(event: SelectChangeEvent<string>): void {
setProgram(event.target.value as string);
}
function addProgram(): void {

@ -1,20 +1,20 @@
import React, { useState } from "react";
import Accordion from "@material-ui/core/Accordion";
import AccordionSummary from "@material-ui/core/AccordionSummary";
import AccordionDetails from "@material-ui/core/AccordionDetails";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
import AccordionDetails from "@mui/material/AccordionDetails";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import Button from "@material-ui/core/Button";
import Select from "@material-ui/core/Select";
import Button from "@mui/material/Button";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import { AllServers } from "../../Server/AllServers";
import { HacknetServer } from "../../Hacknet/HacknetServer";
import { GetServerByHostname } from "../../Server/ServerHelpers";
import MenuItem from "@material-ui/core/MenuItem";
import MenuItem from "@mui/material/MenuItem";
export function Servers(): React.ReactElement {
const [server, setServer] = useState("home");
function setServerDropdown(event: React.ChangeEvent<{ value: unknown }>): void {
function setServerDropdown(event: SelectChangeEvent<string>): void {
setServer(event.target.value as string);
}
function rootServer(): void {

@ -1,11 +1,11 @@
import React from "react";
import Accordion from "@material-ui/core/Accordion";
import AccordionSummary from "@material-ui/core/AccordionSummary";
import AccordionDetails from "@material-ui/core/AccordionDetails";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
import AccordionDetails from "@mui/material/AccordionDetails";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import Button from "@material-ui/core/Button";
import Button from "@mui/material/Button";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {

@ -1,14 +1,14 @@
import React from "react";
import Accordion from "@material-ui/core/Accordion";
import AccordionSummary from "@material-ui/core/AccordionSummary";
import AccordionDetails from "@material-ui/core/AccordionDetails";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
import AccordionDetails from "@mui/material/AccordionDetails";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import Button from "@material-ui/core/Button";
import Button from "@mui/material/Button";
import { PlayerOwnedSourceFile } from "../../SourceFile/PlayerOwnedSourceFile";
import { IPlayer } from "../../PersonObjects/IPlayer";
import ButtonGroup from "@material-ui/core/ButtonGroup";
import ButtonGroup from "@mui/material/ButtonGroup";
// Update as additional BitNodes get implemented
const validSFN = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];

@ -1,11 +1,11 @@
import React from "react";
import Accordion from "@material-ui/core/Accordion";
import AccordionSummary from "@material-ui/core/AccordionSummary";
import AccordionDetails from "@material-ui/core/AccordionDetails";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
import AccordionDetails from "@mui/material/AccordionDetails";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import Button from "@material-ui/core/Button";
import Button from "@mui/material/Button";
import { Adjuster } from "./Adjuster";
import { IPlayer } from "../../PersonObjects/IPlayer";

@ -1,12 +1,12 @@
import React, { useState } from "react";
import Accordion from "@material-ui/core/Accordion";
import AccordionSummary from "@material-ui/core/AccordionSummary";
import AccordionDetails from "@material-ui/core/AccordionDetails";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
import AccordionDetails from "@mui/material/AccordionDetails";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import Button from "@material-ui/core/Button";
import TextField from "@material-ui/core/TextField";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
import { Money } from "../../ui/React/Money";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { StockMarket as SM } from "../../StockMarket/StockMarket";

@ -1,11 +1,11 @@
import React from "react";
import Accordion from "@material-ui/core/Accordion";
import AccordionSummary from "@material-ui/core/AccordionSummary";
import AccordionDetails from "@material-ui/core/AccordionDetails";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
import AccordionDetails from "@mui/material/AccordionDetails";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import Button from "@material-ui/core/Button";
import Button from "@mui/material/Button";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { saveObject } from "../../SaveObject";
import { IEngine } from "../../IEngine";

@ -3,18 +3,18 @@ import { AllServers } from "../Server/AllServers";
import { Modal } from "../ui/React/Modal";
import { numeralWrapper } from "../ui/numeralFormat";
import Table from "@material-ui/core/Table";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
import TableContainer from "@material-ui/core/TableContainer";
import TableHead from "@material-ui/core/TableHead";
import TableRow from "@material-ui/core/TableRow";
import Typography from "@material-ui/core/Typography";
import Paper from "@material-ui/core/Paper";
import Accordion from "@material-ui/core/Accordion";
import AccordionSummary from "@material-ui/core/AccordionSummary";
import AccordionDetails from "@material-ui/core/AccordionDetails";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import Typography from "@mui/material/Typography";
import Paper from "@mui/material/Paper";
import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
import AccordionDetails from "@mui/material/AccordionDetails";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
interface IServerProps {
ip: string;

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

@ -7,7 +7,7 @@ export interface IEngine {
_lastUpdate: number;
hideAllContent: () => void;
loadTerminalContent: () => void;
loadScriptEditorContent: () => void;
loadScriptEditorContent: (filename?: string, code?: string) => void;
loadActiveScriptsContent: () => void;
loadCreateProgramContent: () => void;
loadCharacterContent: () => void;

@ -1,5 +1,5 @@
import React, { useState } from "react";
import Grid from "@material-ui/core/Grid";
import Grid from "@mui/material/Grid";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";

@ -1,5 +1,5 @@
import React, { useState } from "react";
import Grid from "@material-ui/core/Grid";
import Grid from "@mui/material/Grid";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";

@ -1,5 +1,5 @@
import React, { useState } from "react";
import Grid from "@material-ui/core/Grid";
import Grid from "@mui/material/Grid";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";

@ -1,5 +1,5 @@
import React, { useState } from "react";
import Grid from "@material-ui/core/Grid";
import Grid from "@mui/material/Grid";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";

@ -1,5 +1,5 @@
import React, { useState, useEffect } from "react";
import Grid from "@material-ui/core/Grid";
import Grid from "@mui/material/Grid";
interface IProps {
onFinish: () => void;

@ -1,5 +1,5 @@
import React, { useState } from "react";
import Grid from "@material-ui/core/Grid";
import Grid from "@mui/material/Grid";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";

@ -1,7 +1,7 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import React, { useState } from "react";
import Grid from "@material-ui/core/Grid";
import Grid from "@mui/material/Grid";
import { Countdown } from "./Countdown";
import { BracketGame } from "./BracketGame";
import { SlashGame } from "./SlashGame";

@ -1,7 +1,7 @@
import LinearProgress from "@material-ui/core/LinearProgress";
import LinearProgress from "@mui/material/LinearProgress";
import React, { useState, useEffect } from "react";
import { withStyles } from "@material-ui/core/styles";
import Grid from "@material-ui/core/Grid";
import withStyles from '@mui/styles/withStyles';
import Grid from "@mui/material/Grid";
const TimerProgress = withStyles(() => ({
bar: {

@ -2,7 +2,7 @@ import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import React from "react";
import { StdButton } from "../../ui/React/StdButton";
import Grid from "@material-ui/core/Grid";
import Grid from "@mui/material/Grid";
interface IProps {
Player: IPlayer;

@ -1,5 +1,5 @@
import React, { useState, useEffect } from "react";
import Grid from "@material-ui/core/Grid";
import Grid from "@mui/material/Grid";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";

@ -1,5 +1,5 @@
import React, { useState, useEffect } from "react";
import Grid from "@material-ui/core/Grid";
import Grid from "@mui/material/Grid";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";

@ -3,7 +3,7 @@ import { IEngine } from "../../IEngine";
import { Factions } from "../../Faction/Factions";
import React, { useState } from "react";
import { StdButton } from "../../ui/React/StdButton";
import Grid from "@material-ui/core/Grid";
import Grid from "@mui/material/Grid";
import { Money } from "../../ui/React/Money";
import { Reputation } from "../../ui/React/Reputation";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";

@ -1,5 +1,5 @@
import React, { useState } from "react";
import Grid from "@material-ui/core/Grid";
import Grid from "@mui/material/Grid";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";

1
src/Message/MessageHelpers.d.ts vendored Normal file

@ -0,0 +1 @@
export declare function showMessage(msg: Message): void;

@ -1590,7 +1590,7 @@ HackingMission.prototype.finishMission = function (win) {
Mission won! You earned {Reputation(gain)} reputation with {this.faction.name}
</>,
);
Player.gainIntelligenceExp(this.difficulty * CONSTANTS.IntelligenceHackingMissionBaseExpGain);
Player.gainIntelligenceExp(Math.pow(this.difficulty * CONSTANTS.IntelligenceHackingMissionBaseExpGain, 0.5));
this.faction.playerReputation += gain;
} else {
dialogBoxCreate("Mission lost/forfeited! You did not gain any faction reputation.");

@ -145,7 +145,6 @@ import { findSleevePurchasableAugs } from "./PersonObjects/Sleeve/SleeveHelpers"
import { Exploit } from "./Exploits/Exploit.ts";
import { numeralWrapper } from "./ui/numeralFormat";
import { post } from "./ui/postToTerminal";
import { setTimeoutRef } from "./utils/SetTimeoutRef";
import { is2DArray } from "./utils/helpers/is2DArray";
import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
@ -1059,10 +1058,10 @@ function NetscriptFunctions(workerScript) {
if (arguments.length === 0) {
throw makeRuntimeErrorMsg("tprint", "Takes at least 1 argument.");
}
post(`${workerScript.scriptRef.filename}: ${argsToString(arguments)}`);
Terminal.print(`${workerScript.scriptRef.filename}: ${argsToString(arguments)}`);
},
tprintf: function (format, ...args) {
post(vsprintf(format, args));
Terminal.print(vsprintf(format, args));
},
clearLog: function () {
workerScript.scriptRef.clearLog();
@ -3134,8 +3133,7 @@ function NetscriptFunctions(workerScript) {
Player.getCurrentServer().isConnectedTo = false;
Player.currentServer = Player.getHomeComputer().ip;
Player.getCurrentServer().isConnectedTo = true;
Terminal.currDir = "/";
Terminal.resetTerminalInput(true);
Terminal.setcwd("/");
return true;
}
@ -3146,8 +3144,7 @@ function NetscriptFunctions(workerScript) {
Player.getCurrentServer().isConnectedTo = false;
Player.currentServer = target.ip;
Player.getCurrentServer().isConnectedTo = true;
Terminal.currDir = "/";
Terminal.resetTerminalInput(true);
Terminal.setcwd("/");
return true;
}
}

1
src/NetscriptWorker.d.ts vendored Normal file

@ -0,0 +1 @@
export declare function startWorkerScript(script: RunningScript, server: BaseServer): boolean;

@ -24,6 +24,7 @@ import { Exploit } from "../Exploits/Exploit";
import { ICorporation } from "../Corporation/ICorporation";
import { IGang } from "../Gang/IGang";
import { IBladeburner } from "../Bladeburner/IBladeburner";
import { ICodingContractReward } from "../CodingContracts";
export interface IPlayer {
// Class members
@ -207,4 +208,5 @@ export interface IPlayer {
queueAugmentation(augmentationName: string): void;
receiveInvite(factionName: string): void;
updateSkillLevels(): void;
gainCodingContractReward(reward: ICodingContractReward, difficulty?: number): string;
}

@ -174,11 +174,8 @@ export function prestigeAugmentation() {
this.hacknetNodes.length = 0;
this.hashManager.prestige();
// Reset player multipliers
this.resetMultipliers();
// Re-calculate skills and reset HP
this.updateSkillLevels();
// Reapply augs, re-calculate skills and reset HP
this.reapplyAllAugmentations(true);
this.hp = this.max_hp;
}
@ -200,12 +197,6 @@ export function prestigeSourceFile() {
}
}
const characterMenuHeader = document.getElementById("character-menu-header");
if (characterMenuHeader instanceof HTMLElement) {
characterMenuHeader.click();
characterMenuHeader.click();
}
this.timeWorked = 0;
// Gang
@ -2068,9 +2059,6 @@ export function applyForJob(entryPosType, sing = false) {
this.jobs[company.name] = pos.name;
this.companyName = this.location;
document.getElementById("world-menu-header").click();
document.getElementById("world-menu-header").click();
if (sing) {
return true;
}
@ -2186,8 +2174,6 @@ export function applyForEmployeeJob(sing = false) {
if (this.isQualified(company, CompanyPositions[posNames.MiscCompanyPositions[1]])) {
this.companyName = company.name;
this.jobs[company.name] = posNames.MiscCompanyPositions[1];
document.getElementById("world-menu-header").click();
document.getElementById("world-menu-header").click();
if (sing) {
return true;
}
@ -2204,8 +2190,6 @@ export function applyForPartTimeEmployeeJob(sing = false) {
var company = Companies[this.location]; //Company being applied to
if (this.isQualified(company, CompanyPositions[posNames.PartTimeCompanyPositions[1]])) {
this.jobs[company.name] = posNames.PartTimeCompanyPositions[1];
document.getElementById("world-menu-header").click();
document.getElementById("world-menu-header").click();
if (sing) {
return true;
}
@ -2223,8 +2207,6 @@ export function applyForWaiterJob(sing = false) {
if (this.isQualified(company, CompanyPositions[posNames.MiscCompanyPositions[0]])) {
this.companyName = company.name;
this.jobs[company.name] = posNames.MiscCompanyPositions[0];
document.getElementById("world-menu-header").click();
document.getElementById("world-menu-header").click();
if (sing) {
return true;
}
@ -2242,8 +2224,6 @@ export function applyForPartTimeWaiterJob(sing = false) {
if (this.isQualified(company, CompanyPositions[posNames.PartTimeCompanyPositions[0]])) {
this.companyName = company.name;
this.jobs[company.name] = posNames.PartTimeCompanyPositions[0];
document.getElementById("world-menu-header").click();
document.getElementById("world-menu-header").click();
if (sing) {
return true;
}
@ -2307,6 +2287,8 @@ export function reapplyAllAugmentations(resetMultipliers = true) {
}
applyAugmentation(this.augmentations[i], true);
}
this.updateSkillLevels();
}
export function reapplyAllSourceFiles() {

@ -81,7 +81,7 @@ export class CovenantSleeveMemoryUpgrade extends React.Component<IProps, IState>
if (isNaN(this.state.amt)) {
purchaseBtnContent = <>Invalid value</>;
} else if (this.state.amt > maxMemory) {
purchaseBtnContent = <>Memory cannot exceed 100?</>;
purchaseBtnContent = <>Memory cannot exceed 100</>;
} else {
purchaseBtnContent = (
<>

@ -23,7 +23,7 @@ import { prestigeHomeComputer } from "./Server/ServerHelpers";
import { SourceFileFlags, updateSourceFileFlags } from "./SourceFile/SourceFileFlags";
import { SpecialServerIps, prestigeSpecialServerIps, SpecialServerNames } from "./Server/SpecialServerIps";
import { deleteStockMarket, initStockMarket, initSymbolToStockMap } from "./StockMarket/StockMarket";
import { Terminal, postVersion } from "./Terminal";
import { Terminal } from "./Terminal";
import { Page, routing } from "./ui/navigationTracking";
@ -57,21 +57,13 @@ function prestigeAugmentation() {
"Fulcrum Secret Technologies",
];
let maintainMembership = Player.factions.filter(function (faction) {
const maintainMembership = Player.factions.filter(function (faction) {
return megaCorpFactions.includes(faction);
});
Player.prestigeAugmentation();
Player.factions = Player.factions.concat(maintainMembership);
// Now actually go to the Terminal Screen (and reset it)
var mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "visible";
Terminal.resetTerminalInput();
Terminal.clear();
Engine.loadTerminalContent();
$("#terminal tr:not(:last)").remove();
postVersion();
// Delete all Worker Scripts objects
prestigeWorkerScripts();
@ -125,6 +117,9 @@ function prestigeAugmentation() {
// Re-initialize things - This will update any changes
initFactions(); // Factions must be initialized before augmentations
Player.factions = Player.factions.concat(maintainMembership);
Player.factions.map((f) => (Factions[f].isMember = true));
initAugmentations(); // Calls reapplyAllAugmentations() and resets Player multipliers
Player.reapplyAllSourceFiles();
initCompanies();
@ -246,16 +241,12 @@ function prestigeSourceFile(flume) {
Player.reapplyAllSourceFiles();
initCompanies();
// Clear terminal
$("#terminal tr:not(:last)").remove();
postVersion();
// Messages
initMessages();
var mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "visible";
Terminal.resetTerminalInput();
Terminal.clear();
Engine.loadTerminalContent();
// BitNode 3: Corporatocracy

@ -1,7 +1,6 @@
export interface IPlayer {
hacking_skill: number;
sourceFiles: any[];
}
import { BaseServer } from "../Server/BaseServer";
import { ITerminal } from "../Terminal/ITerminal";
import { IPlayer } from "../PersonObjects/IPlayer";
export interface IProgramCreate {
level: number;
@ -13,10 +12,16 @@ export interface IProgramCreate {
export class Program {
name = "";
create: IProgramCreate | null;
run: (terminal: ITerminal, player: IPlayer, server: BaseServer, args: string[]) => void;
constructor(name: string, create: IProgramCreate | null) {
constructor(
name: string,
create: IProgramCreate | null,
run: (terminal: ITerminal, player: IPlayer, server: BaseServer, args: string[]) => void,
) {
this.name = name;
this.create = create;
this.run = run;
}
htmlID(): string {

@ -5,5 +5,5 @@ import { IMap } from "../types";
export const Programs: IMap<Program> = {};
for (const params of programsMetadata) {
Programs[params.key] = new Program(params.name, params.create);
Programs[params.key] = new Program(params.name, params.create, params.run);
}

@ -1,5 +1,18 @@
import { IPlayer, IProgramCreate } from "../Program";
import { IProgramCreate } from "../Program";
import { CONSTANTS } from "../../Constants";
import { BaseServer } from "../../Server/BaseServer";
import { Server } from "../../Server/Server";
import { ITerminal } from "../../Terminal/ITerminal";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { HacknetServer } from "../../Hacknet/HacknetServer";
import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions";
import { getServer } from "../../Server/ServerHelpers";
import { numeralWrapper } from "../../ui/numeralFormat";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { createPopup } from "../../ui/React/createPopup";
import { BitFlumePopup } from "../../BitNode/ui/BitFlumePopup";
import { calculateHackingTime, calculateGrowTime, calculateWeakenTime } from "../../Hacking";
function requireHackingLevel(lvl: number) {
return function (p: IPlayer) {
@ -17,6 +30,7 @@ export interface IProgramCreationParams {
key: string;
name: string;
create: IProgramCreate | null;
run: (terminal: ITerminal, player: IPlayer, server: BaseServer, args: string[]) => void;
}
export const programsMetadata: IProgramCreationParams[] = [
@ -29,6 +43,25 @@ export const programsMetadata: IProgramCreationParams[] = [
req: requireHackingLevel(1),
time: CONSTANTS.MillisecondsPerFiveMinutes,
},
run: (terminal: ITerminal, player: IPlayer, server: BaseServer): void => {
if (!(server instanceof Server)) {
terminal.error("Cannot nuke this kind of server.");
return;
}
if (server.hasAdminRights) {
terminal.print("You already have root access to this computer. There is no reason to run NUKE.exe");
return;
}
if (server.openPortCount >= player.getCurrentServer().numOpenPortsRequired) {
server.hasAdminRights = true;
terminal.print("NUKE successful! Gained root access to " + player.getCurrentServer().hostname);
// TODO: Make this take time rather than be instant
return;
}
terminal.print("NUKE unsuccessful. Not enough ports have been opened");
},
},
{
key: "BruteSSHProgram",
@ -39,6 +72,20 @@ export const programsMetadata: IProgramCreationParams[] = [
req: requireHackingLevel(50),
time: CONSTANTS.MillisecondsPerFiveMinutes * 2,
},
run: (terminal: ITerminal, player: IPlayer, server: BaseServer): void => {
if (!(server instanceof Server)) {
terminal.error("Cannot run BruteSSH.exe on this kind of server.");
return;
}
if (server.sshPortOpen) {
terminal.print("SSH Port (22) is already open!");
return;
}
server.sshPortOpen = true;
terminal.print("Opened SSH Port(22)!");
server.openPortCount++;
},
},
{
key: "FTPCrackProgram",
@ -49,6 +96,20 @@ export const programsMetadata: IProgramCreationParams[] = [
req: requireHackingLevel(100),
time: CONSTANTS.MillisecondsPerHalfHour,
},
run: (terminal: ITerminal, player: IPlayer, server: BaseServer): void => {
if (!(server instanceof Server)) {
terminal.error("Cannot run FTPCrack.exe on this kind of server.");
return;
}
if (server.ftpPortOpen) {
terminal.print("FTP Port (21) is already open!");
return;
}
server.ftpPortOpen = true;
terminal.print("Opened FTP Port (21)!");
server.openPortCount++;
},
},
{
key: "RelaySMTPProgram",
@ -59,6 +120,20 @@ export const programsMetadata: IProgramCreationParams[] = [
req: requireHackingLevel(250),
time: CONSTANTS.MillisecondsPer2Hours,
},
run: (terminal: ITerminal, player: IPlayer, server: BaseServer): void => {
if (!(server instanceof Server)) {
terminal.error("Cannot run relaySMTP.exe on this kind of server.");
return;
}
if (server.smtpPortOpen) {
terminal.print("SMTP Port (25) is already open!");
return;
}
server.smtpPortOpen = true;
terminal.print("Opened SMTP Port (25)!");
server.openPortCount++;
},
},
{
key: "HTTPWormProgram",
@ -69,6 +144,20 @@ export const programsMetadata: IProgramCreationParams[] = [
req: requireHackingLevel(500),
time: CONSTANTS.MillisecondsPer4Hours,
},
run: (terminal: ITerminal, player: IPlayer, server: BaseServer): void => {
if (!(server instanceof Server)) {
terminal.error("Cannot run HTTPWorm.exe on this kind of server.");
return;
}
if (server.httpPortOpen) {
terminal.print("HTTP Port (80) is already open!");
return;
}
server.httpPortOpen = true;
terminal.print("Opened HTTP Port (80)!");
server.openPortCount++;
},
},
{
key: "SQLInjectProgram",
@ -79,6 +168,20 @@ export const programsMetadata: IProgramCreationParams[] = [
req: requireHackingLevel(750),
time: CONSTANTS.MillisecondsPer8Hours,
},
run: (terminal: ITerminal, player: IPlayer, server: BaseServer): void => {
if (!(server instanceof Server)) {
terminal.error("Cannot run SQLInject.exe on this kind of server.");
return;
}
if (server.sqlPortOpen) {
terminal.print("SQL Port (1433) is already open!");
return;
}
server.sqlPortOpen = true;
terminal.print("Opened SQL Port (1433)!");
server.openPortCount++;
},
},
{
key: "DeepscanV1",
@ -89,6 +192,10 @@ export const programsMetadata: IProgramCreationParams[] = [
req: requireHackingLevel(75),
time: CONSTANTS.MillisecondsPerQuarterHour,
},
run: (terminal: ITerminal): void => {
terminal.print("This executable cannot be run.");
terminal.print("DeepscanV1.exe lets you run 'scan-analyze' with a depth up to 5.");
},
},
{
key: "DeepscanV2",
@ -99,6 +206,10 @@ export const programsMetadata: IProgramCreationParams[] = [
req: requireHackingLevel(400),
time: CONSTANTS.MillisecondsPer2Hours,
},
run: (terminal: ITerminal): void => {
terminal.print("This executable cannot be run.");
terminal.print("DeepscanV2.exe lets you run 'scan-analyze' with a depth up to 10.");
},
},
{
key: "ServerProfiler",
@ -109,6 +220,46 @@ export const programsMetadata: IProgramCreationParams[] = [
req: requireHackingLevel(75),
time: CONSTANTS.MillisecondsPerHalfHour,
},
run: (terminal: ITerminal, player: IPlayer, server: BaseServer, args: string[]): void => {
if (args.length !== 1) {
terminal.print("Must pass a server hostname or IP as an argument for ServerProfiler.exe");
return;
}
const targetServer = getServer(args[0]);
if (targetServer == null) {
terminal.print("Invalid server IP/hostname");
return;
}
if (targetServer instanceof HacknetServer) {
terminal.print(`ServerProfiler.exe cannot be run on a Hacknet Server.`);
return;
}
terminal.print(targetServer.hostname + ":");
terminal.print("Server base security level: " + targetServer.baseDifficulty);
terminal.print("Server current security level: " + targetServer.hackDifficulty);
terminal.print("Server growth rate: " + targetServer.serverGrowth);
terminal.print(
`Netscript hack() execution time: ${convertTimeMsToTimeElapsedString(
calculateHackingTime(targetServer, player) * 1000,
true,
)}`,
);
terminal.print(
`Netscript grow() execution time: ${convertTimeMsToTimeElapsedString(
calculateGrowTime(targetServer, player) * 1000,
true,
)}`,
);
terminal.print(
`Netscript weaken() execution time: ${convertTimeMsToTimeElapsedString(
calculateWeakenTime(targetServer, player) * 1000,
true,
)}`,
);
},
},
{
key: "AutoLink",
@ -119,6 +270,11 @@ export const programsMetadata: IProgramCreationParams[] = [
req: requireHackingLevel(25),
time: CONSTANTS.MillisecondsPerQuarterHour,
},
run: (terminal: ITerminal): void => {
terminal.print("This executable cannot be run.");
terminal.print("AutoLink.exe lets you automatically connect to other servers when using 'scan-analyze'.");
terminal.print("When using scan-analyze, click on a server's hostname to connect to it.");
},
},
{
key: "BitFlume",
@ -129,10 +285,31 @@ export const programsMetadata: IProgramCreationParams[] = [
req: bitFlumeRequirements(),
time: CONSTANTS.MillisecondsPerFiveMinutes / 20,
},
run: (terminal: ITerminal, player: IPlayer): void => {
const popupId = "bitflume-popup";
createPopup(popupId, BitFlumePopup, {
player: player,
popupId: popupId,
});
},
},
{
key: "Flight",
name: "fl1ght.exe",
create: null,
run: (terminal: ITerminal, player: IPlayer): void => {
const numAugReq = Math.round(BitNodeMultipliers.DaedalusAugsRequirement * 30);
const fulfilled =
player.augmentations.length >= numAugReq && player.money.gt(1e11) && player.hacking_skill >= 2500;
if (!fulfilled) {
terminal.print(`Augmentations: ${player.augmentations.length} / ${numAugReq}`);
terminal.print(`Money: ${numeralWrapper.formatMoney(player.money.toNumber())} / $100b`);
terminal.print(`Hacking skill: ${player.hacking_skill} / 2500`);
return;
}
terminal.print("We will contact you.");
terminal.print("-- Daedalus --");
},
},
];

@ -2,6 +2,9 @@ import React, { useState, useEffect } from "react";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { getAvailableCreatePrograms } from "../ProgramHelpers";
import { Box, ButtonGroup, Tooltip, Typography } from "@mui/material";
import Button from "@mui/material/Button";
interface IProps {
player: IPlayer;
}
@ -19,34 +22,29 @@ export function ProgramsRoot(props: IProps): React.ReactElement {
return (
<>
<p id="create-program-page-text">
<div>
<Box>
<Typography>
This page displays any programs that you are able to create. Writing the code for a program takes time, which
can vary based on how complex the program is. If you are working on creating a program you can cancel at any
time. Your progress will be saved and you can continue later.
</p>
<ul id="create-program-list">
</Typography>
</Box>
<ButtonGroup>
{getAvailableCreatePrograms(props.player).map((program) => {
const create = program.create;
if (create === null) return <></>;
return (
<a
key={program.name}
className="a-link-button tooltip"
onClick={() => props.player.startCreateProgramWork(program.name, create.time, create.level)}
>
<Tooltip title={create.tooltip}>
<Button onClick={() => props.player.startCreateProgramWork(program.name, create.time, create.level)}>
{program.name}
<span className="tooltiptext">{create.tooltip}</span>
</a>
);
</Button>
</Tooltip>
)
})}
</ul>
<br />
<br />
<br />
<br />
<br />
</ButtonGroup>
</div>
</>
);
)
}

@ -92,13 +92,6 @@ function enterBitNode(flume, destroyedBitNode, newBitNode) {
// Set new Bit Node
Player.bitNodeN = newBitNode;
// Reenable terminal
$("#hack-progress-bar").attr("id", "old-hack-progress-bar");
$("#hack-progress").attr("id", "old-hack-progress");
document.getElementById("terminal-input-td").innerHTML =
'$ <input type="text" id="terminal-input-text-box" class="terminal-input" tabindex="1"/>';
$("input[class=terminal-input]").prop("disabled", false);
prestigeSourceFile(flume);
}

@ -6,7 +6,7 @@ import { Script } from "./Script";
import { FconfSettings } from "../Fconf/FconfSettings";
import { Settings } from "../Settings/Settings";
import { IMap } from "../types";
import { post } from "../ui/postToTerminal";
import { Terminal } from "../Terminal";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
import { getTimestamp } from "../../utils/helpers/getTimestamp";
@ -85,7 +85,7 @@ export class RunningScript {
displayLog(): void {
for (let i = 0; i < this.logs.length; ++i) {
post(this.logs[i]);
Terminal.print(this.logs[i]);
}
}

6
src/Script/ScriptHelpers.d.ts vendored Normal file

@ -0,0 +1,6 @@
export declare function findRunningScript(
filename: string,
args: (string | number)[],
server: BaseServer,
): RunningScript | null;
export declare function findRunningScriptByPid(pid: number, server: BaseServer): RunningScript | null;

@ -1,5 +1,6 @@
import { AllServers, createUniqueRandomIp, ipExists } from "./AllServers";
import { Server, IConstructorParams } from "./Server";
import { BaseServer } from "./BaseServer";
import { calculateServerGrowth } from "./formulas/grow";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
@ -141,7 +142,7 @@ export function getServer(s: string): Server | HacknetServer | null {
// Returns the i-th server on the specified server's network
// A Server's serverOnNetwork property holds only the IPs. This function returns
// the actual Server object
export function getServerOnNetwork(server: Server, i: number): Server | HacknetServer | null {
export function getServerOnNetwork(server: BaseServer, i: number): Server | HacknetServer | null {
if (i > server.serversOnNetwork.length) {
console.error("Tried to get server on network that was out of range");
return null;

@ -45,6 +45,11 @@ interface IDefaultSettings {
*/
MaxPortCapacity: number;
/**
* Limit the number of entries in the terminal.
*/
MaxTerminalCapacity: number;
/**
* Whether the player should be asked to confirm purchasing each and every augmentation.
*/
@ -104,6 +109,7 @@ const defaultSettings: IDefaultSettings = {
Locale: "en",
MaxLogCapacity: 50,
MaxPortCapacity: 50,
MaxTerminalCapacity: 200,
SuppressBuyAugmentationConfirmation: false,
SuppressFactionInvites: false,
SuppressHospitalizationPopup: false,
@ -125,6 +131,7 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
Locale: "en",
MaxLogCapacity: defaultSettings.MaxLogCapacity,
MaxPortCapacity: defaultSettings.MaxPortCapacity,
MaxTerminalCapacity: defaultSettings.MaxTerminalCapacity,
OwnedAugmentationsOrder: OwnedAugmentationsOrderSetting.AcquirementTime,
PurchaseAugmentationsOrder: PurchaseAugmentationsOrderSetting.Default,
SuppressBuyAugmentationConfirmation: defaultSettings.SuppressBuyAugmentationConfirmation,

@ -1,51 +1,48 @@
import React, { useState, useEffect } from "react";
import clsx from "clsx";
import { createStyles, makeStyles, useTheme, Theme } from "@material-ui/core/styles";
import Drawer from "@material-ui/core/Drawer";
import List from "@material-ui/core/List";
import Divider from "@material-ui/core/Divider";
import IconButton from "@material-ui/core/IconButton";
import ChevronLeftIcon from "@material-ui/icons/ChevronLeft";
import ChevronRightIcon from "@material-ui/icons/ChevronRight";
import ListItem from "@material-ui/core/ListItem";
import ListSubheader from "@material-ui/core/ListSubheader";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItemText from "@material-ui/core/ListItemText";
import Typography from "@material-ui/core/Typography";
import Collapse from "@material-ui/core/Collapse";
import InboxIcon from "@material-ui/icons/MoveToInbox";
import MailIcon from "@material-ui/icons/Mail";
import { styled, useTheme, Theme, CSSObject } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import makeStyles from "@mui/styles/makeStyles";
import MuiDrawer from "@mui/material/Drawer";
import List from "@mui/material/List";
import Divider from "@mui/material/Divider";
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
import ListItem from "@mui/material/ListItem";
import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText";
import Typography from "@mui/material/Typography";
import Collapse from "@mui/material/Collapse";
import Badge from "@mui/material/Badge";
import { Theme as BBTheme, colors } from "../../ui/React/Theme";
import { TTheme as BBTheme, colors } from "../../ui/React/Theme";
import ComputerIcon from "@material-ui/icons/Computer";
import LastPageIcon from "@material-ui/icons/LastPage"; // Terminal
import CreateIcon from "@material-ui/icons/Create"; // Create Script
import StorageIcon from "@material-ui/icons/Storage"; // Active Scripts
import BugReportIcon from "@material-ui/icons/BugReport"; // Create Program
import EqualizerIcon from "@material-ui/icons/Equalizer"; // Stats
import ContactsIcon from "@material-ui/icons/Contacts"; // Factions
import DoubleArrowIcon from "@material-ui/icons/DoubleArrow"; // Augmentations
import AccountTreeIcon from "@material-ui/icons/AccountTree"; // Hacknet
import PeopleAltIcon from "@material-ui/icons/PeopleAlt"; // Sleeves
import LocationCityIcon from "@material-ui/icons/LocationCity"; // City
import AirplanemodeActiveIcon from "@material-ui/icons/AirplanemodeActive"; // Travel
import WorkIcon from "@material-ui/icons/Work"; // Job
import TrendingUpIcon from "@material-ui/icons/TrendingUp"; // Stock Market
import FormatBoldIcon from "@material-ui/icons/FormatBold"; // Bladeburner
import BusinessIcon from "@material-ui/icons/Business"; // Corp
import SportsMmaIcon from "@material-ui/icons/SportsMma"; // Gang
import CheckIcon from "@material-ui/icons/Check"; // Milestones
import HelpIcon from "@material-ui/icons/Help"; // Tutorial
import SettingsIcon from "@material-ui/icons/Settings"; // options
import DeveloperBoardIcon from "@material-ui/icons/DeveloperBoard"; // Dev
// import MemoryIcon from "@material-ui/icons/Memory";
// import ShareIcon from "@material-ui/icons/Share";
import AccountBoxIcon from "@material-ui/icons/AccountBox";
import PublicIcon from "@material-ui/icons/Public";
import LiveHelpIcon from "@material-ui/icons/LiveHelp";
import ExpandLessIcon from "@material-ui/icons/ExpandLess";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import ComputerIcon from "@mui/icons-material/Computer";
import LastPageIcon from "@mui/icons-material/LastPage"; // Terminal
import CreateIcon from "@mui/icons-material/Create"; // Create Script
import StorageIcon from "@mui/icons-material/Storage"; // Active Scripts
import BugReportIcon from "@mui/icons-material/BugReport"; // Create Program
import EqualizerIcon from "@mui/icons-material/Equalizer"; // Stats
import ContactsIcon from "@mui/icons-material/Contacts"; // Factions
import DoubleArrowIcon from "@mui/icons-material/DoubleArrow"; // Augmentations
import AccountTreeIcon from "@mui/icons-material/AccountTree"; // Hacknet
import PeopleAltIcon from "@mui/icons-material/PeopleAlt"; // Sleeves
import LocationCityIcon from "@mui/icons-material/LocationCity"; // City
import AirplanemodeActiveIcon from "@mui/icons-material/AirplanemodeActive"; // Travel
import WorkIcon from "@mui/icons-material/Work"; // Job
import TrendingUpIcon from "@mui/icons-material/TrendingUp"; // Stock Market
import FormatBoldIcon from "@mui/icons-material/FormatBold"; // Bladeburner
import BusinessIcon from "@mui/icons-material/Business"; // Corp
import SportsMmaIcon from "@mui/icons-material/SportsMma"; // Gang
import CheckIcon from "@mui/icons-material/Check"; // Milestones
import HelpIcon from "@mui/icons-material/Help"; // Tutorial
import SettingsIcon from "@mui/icons-material/Settings"; // options
import DeveloperBoardIcon from "@mui/icons-material/DeveloperBoard"; // Dev
import AccountBoxIcon from "@mui/icons-material/AccountBox";
import PublicIcon from "@mui/icons-material/Public";
import LiveHelpIcon from "@mui/icons-material/LiveHelp";
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
@ -63,31 +60,44 @@ import { Page, routing } from "../../ui/navigationTracking";
const drawerWidth = 240;
const useStyles = makeStyles((theme: Theme) =>
createStyles({
drawer: {
width: drawerWidth,
flexShrink: 0,
whiteSpace: "nowrap",
},
drawerOpen: {
width: drawerWidth,
const openedMixin = (theme: Theme): CSSObject => ({
width: theme.spacing(31),
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
drawerClose: {
overflowX: "hidden",
});
const closedMixin = (theme: Theme): CSSObject => ({
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
overflowX: "hidden",
width: theme.spacing(7) + 1,
width: `calc(${theme.spacing(2)} + 1px)`,
[theme.breakpoints.up("sm")]: {
width: theme.spacing(9) + 1,
},
width: `calc(${theme.spacing(7)} + 1px)`,
},
});
const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== "open" })(({ theme, open }) => ({
width: theme.spacing(31),
flexShrink: 0,
whiteSpace: "nowrap",
boxSizing: "border-box",
...(open && {
...openedMixin(theme),
"& .MuiDrawer-paper": openedMixin(theme),
}),
...(!open && {
...closedMixin(theme),
"& .MuiDrawer-paper": closedMixin(theme),
}),
}));
const useStyles = makeStyles((theme: Theme) =>
createStyles({
active: {
borderLeft: "3px solid " + colors.primary,
},
@ -130,6 +140,8 @@ export function SidebarRoot(props: IProps): React.ReactElement {
const flashTutorial = ITutorial.currStep === iTutorialSteps.WorldDescription;
const augmentationCount = props.player.queuedAugmentations.length;
const invitationsCount = props.player.factionInvitations.length;
const programCount = getAvailableCreatePrograms(props.player).length;
const canCreateProgram =
programCount > 0 ||
@ -341,34 +353,24 @@ export function SidebarRoot(props: IProps): React.ReactElement {
const classes = useStyles();
const [open, setOpen] = useState(true);
const toggleDrawer = () => setOpen((old) => !old);
const toggleDrawer = (): void => setOpen((old) => !old);
return (
<BBTheme>
<Drawer
variant="permanent"
className={clsx(classes.drawer, {
[classes.drawerOpen]: open,
[classes.drawerClose]: !open,
})}
classes={{
paper: clsx({
[classes.drawerOpen]: open,
[classes.drawerClose]: !open,
}),
}}
>
<Drawer open={open} anchor="left" variant="permanent">
<ListItem button onClick={toggleDrawer}>
<ListItemIcon>{!open ? <ChevronRightIcon /> : <ChevronLeftIcon />}</ListItemIcon>
<ListItemIcon>
{!open ? <ChevronRightIcon color={"primary"} /> : <ChevronLeftIcon color={"primary"} />}
</ListItemIcon>
<ListItemText primary={<Typography color="primary">Bitburner v{CONSTANTS.Version}</Typography>} />
</ListItem>
<Divider />
<List>
<ListItem button onClick={() => setHackingOpen((old) => !old)}>
<ListItemIcon>
<ComputerIcon />
<ComputerIcon color={"primary"} />
</ListItemIcon>
<ListItemText primary={<Typography color="primary">Hacking</Typography>} />
{hackingOpen ? <ExpandLessIcon /> : <ExpandMoreIcon />}
{hackingOpen ? <ExpandLessIcon color={"primary"} /> : <ExpandMoreIcon color={"primary"} />}
</ListItem>
<Collapse in={hackingOpen} timeout="auto" unmountOnExit>
<List>
@ -435,7 +437,9 @@ export function SidebarRoot(props: IProps): React.ReactElement {
onClick={clickCreateProgram}
>
<ListItemIcon>
<Badge badgeContent={programCount > 0 ? programCount : undefined} color="error">
<BugReportIcon color={activeTab !== "CreateProgram" ? "secondary" : "primary"} />
</Badge>
</ListItemIcon>
<ListItemText>
<Typography color={activeTab !== "CreateProgram" ? "secondary" : "primary"}>
@ -450,10 +454,10 @@ export function SidebarRoot(props: IProps): React.ReactElement {
<Divider />
<ListItem button onClick={() => setCharacterOpen((old) => !old)}>
<ListItemIcon>
<AccountBoxIcon />
<AccountBoxIcon color={"primary"} />
</ListItemIcon>
<ListItemText primary={<Typography color="primary">Character</Typography>} />
{characterOpen ? <ExpandLessIcon /> : <ExpandMoreIcon />}
{characterOpen ? <ExpandLessIcon color={"primary"} /> : <ExpandMoreIcon color={"primary"} />}
</ListItem>
<Collapse in={characterOpen} timeout="auto" unmountOnExit>
<ListItem
@ -483,7 +487,9 @@ export function SidebarRoot(props: IProps): React.ReactElement {
onClick={clickFactions}
>
<ListItemIcon>
<Badge badgeContent={invitationsCount !== 0 ? invitationsCount : undefined} color="error">
<ContactsIcon color={activeTab !== "Factions" ? "secondary" : "primary"} />
</Badge>
</ListItemIcon>
<ListItemText>
<Typography color={activeTab !== "Factions" ? "secondary" : "primary"}>Factions</Typography>
@ -500,10 +506,12 @@ export function SidebarRoot(props: IProps): React.ReactElement {
onClick={clickAugmentations}
>
<ListItemIcon>
<Badge badgeContent={augmentationCount !== 0 ? augmentationCount : undefined} color="error">
<DoubleArrowIcon
style={{ transform: "rotate(-90deg)" }}
color={activeTab !== "Augmentations" ? "secondary" : "primary"}
/>
</Badge>
</ListItemIcon>
<ListItemText>
<Typography color={activeTab !== "Augmentations" ? "secondary" : "primary"}>Augmentations</Typography>
@ -549,10 +557,10 @@ export function SidebarRoot(props: IProps): React.ReactElement {
<Divider />
<ListItem button onClick={() => setWorldOpen((old) => !old)}>
<ListItemIcon>
<PublicIcon />
<PublicIcon color={"primary"} />
</ListItemIcon>
<ListItemText primary={<Typography color="primary">World</Typography>} />
{worldOpen ? <ExpandLessIcon /> : <ExpandMoreIcon />}
{worldOpen ? <ExpandLessIcon color={"primary"} /> : <ExpandMoreIcon color={"primary"} />}
</ListItem>
<Collapse in={worldOpen} timeout="auto" unmountOnExit>
<ListItem
@ -677,10 +685,10 @@ export function SidebarRoot(props: IProps): React.ReactElement {
<Divider />
<ListItem button onClick={() => setHelpOpen((old) => !old)}>
<ListItemIcon>
<LiveHelpIcon />
<LiveHelpIcon color={"primary"} />
</ListItemIcon>
<ListItemText primary={<Typography color="primary">Help</Typography>} />
{helpOpen ? <ExpandLessIcon /> : <ExpandMoreIcon />}
{helpOpen ? <ExpandLessIcon color={"primary"} /> : <ExpandMoreIcon color={"primary"} />}
</ListItem>
<Collapse in={helpOpen} timeout="auto" unmountOnExit>
<ListItem

1
src/Terminal.d.ts vendored Normal file

@ -0,0 +1 @@
export declare const Terminal: ITerminal;

File diff suppressed because it is too large Load Diff

@ -1,282 +1,428 @@
/* tslint:disable:max-line-length completed-docs variable-name*/
import { IMap } from "../types";
export const TerminalHelpText: string =
"Type 'help name' to learn more about the command 'name'<br><br>" +
'alias [-g] [name="value"] Create or display Terminal aliases<br>' +
"analyze Get information about the current machine <br>" +
"backdoor Install a backdoor on the current machine <br>" +
"buy [-l/program] Purchase a program through the Dark Web<br>" +
"cat [file] Display a .msg, .lit, or .txt file<br>" +
"cd [dir] Change to a new directory<br>" +
"check [script] [args...] Print a script's logs to Terminal<br>" +
"clear Clear all text on the terminal <br>" +
"cls See 'clear' command <br>" +
"connect [ip/hostname] Connects to a remote server<br>" +
"download [script/text file] Downloads scripts or text files to your computer<br>" +
"expr [math expression] Evaluate a mathematical expression<br>" +
"free Check the machine's memory (RAM) usage<br>" +
"hack Hack the current machine<br>" +
"help [command] Display this help text, or the help text for a command<br>" +
"home Connect to home computer<br>" +
"hostname Displays the hostname of the machine<br>" +
"ifconfig Displays the IP address of the machine<br>" +
"kill [script/pid] [args...] Stops the specified script on the current server <br>" +
"killall Stops all running scripts on the current machine<br>" +
"ls [dir] [| grep pattern] Displays all files on the machine<br>" +
"lscpu Displays the number of CPU cores on the machine<br>" +
"mem [script] [-t] [n] Displays the amount of RAM required to run the script<br>" +
"mv [src] [dest] Move/rename a text or script file<br>" +
"nano [file] Text editor - Open up and edit a script or text file<br>" +
"ps Display all scripts that are currently running<br>" +
"rm [file] Delete a file from the server<br>" +
"run [name] [-t] [n] [args...] Execute a program or script<br>" +
"scan Prints all immediately-available network connections<br>" +
"scan-analyze [d] [-a] Prints info for all servers up to <i>d</i> nodes away<br>" +
"scp [file] [server] Copies a file to a destination server<br>" +
"sudov Shows whether you have root access on this computer<br>" +
"tail [script] [args...] Displays dynamic logs for the specified script<br>" +
"theme [preset] | bg txt hlgt Change the color scheme of the UI<br>" +
"top Displays all running scripts and their RAM usage<br>" +
"unalias [alias name] Deletes the specified alias<br>" +
"wget [url] [target file] Retrieves code/text from a web server<br>";
export const TerminalHelpText: string[] = [
"Type 'help name' to learn more about the command ",
"",
'alias [-g] [name="value"] Create or display Terminal aliases',
"analyze Get information about the current machine ",
"backdoor Install a backdoor on the current machine ",
"buy [-l/program] Purchase a program through the Dark Web",
"cat [file] Display a .msg, .lit, or .txt file",
"cd [dir] Change to a new directory",
"check [script] [args...] Print a script's logs to Terminal",
"clear Clear all text on the terminal ",
"cls See 'clear' command ",
"connect [ip/hostname] Connects to a remote server",
"download [script/text file] Downloads scripts or text files to your computer",
"expr [math expression] Evaluate a mathematical expression",
"free Check the machine's memory (RAM) usage",
"hack Hack the current machine",
"help [command] Display this help text, or the help text for a command",
"home Connect to home computer",
"hostname Displays the hostname of the machine",
"ifconfig Displays the IP address of the machine",
"kill [script/pid] [args...] Stops the specified script on the current server ",
"killall Stops all running scripts on the current machine",
"ls [dir] [| grep pattern] Displays all files on the machine",
"lscpu Displays the number of CPU cores on the machine",
"mem [script] [-t] [n] Displays the amount of RAM required to run the script",
"mv [src] [dest] Move/rename a text or script file",
"nano [file] Text editor - Open up and edit a script or text file",
"ps Display all scripts that are currently running",
"rm [file] Delete a file from the server",
"run [name] [-t] [n] [args...] Execute a program or script",
"scan Prints all immediately-available network connections",
"scan-analyze [d] [-a] Prints info for all servers up to <i>d</i> nodes away",
"scp [file] [server] Copies a file to a destination server",
"sudov Shows whether you have root access on this computer",
"tail [script] [args...] Displays dynamic logs for the specified script",
"theme [preset] | bg txt hlgt Change the color scheme of the UI",
"top Displays all running scripts and their RAM usage",
"unalias [alias name] Deletes the specified alias",
"wget [url] [target file] Retrieves code/text from a web server",
];
export const HelpTexts: IMap<string> = {
alias:
'alias [-g] [name="value"] <br>' +
"Create or display aliases. An alias enables a replacement of a word with another string. " +
"It can be used to abbreviate a commonly used command, or commonly used parts of a command. The NAME " +
"of an alias defines the word that will be replaced, while the VALUE defines what it will be replaced by. For example, " +
"you could create the alias 'nuke' for the Terminal command 'run NUKE.exe' using the following: <br><br>" +
'alias nuke="run NUKE.exe"<br><br>' +
"Then, to run the NUKE.exe program you would just have to enter 'nuke' in Terminal rather than the full command. " +
"It is important to note that 'default' aliases will only be substituted for the first word of a Terminal command. For " +
"example, if the following alias was set: <br><br>" +
'alias worm="HTTPWorm.exe"<br><br>' +
"and then you tried to run the following terminal command: <br><br>" +
"run worm<br><br>" +
"This would fail because the worm alias is not the first word of a Terminal command. To allow an alias to be substituted " +
"anywhere in a Terminal command, rather than just the first word, you must set it to be a global alias using the -g flag: <br><br>" +
'alias -g worm="HTTPWorm.exe"<br><br>' +
"Now, the 'worm' alias will be substituted anytime it shows up as an individual word in a Terminal command. <br><br>" +
"Entering just the command 'alias' without any arguments prints the list of all defined aliases in the reusable form " +
"'alias NAME=VALUE' on the Terminal. <br><br>" +
"The 'unalias' command can be used to remove aliases.<br><br>",
analyze:
"analze<br>" +
"Prints details and statistics about the current server. The information that is printed includes basic " +
"server details such as the hostname, whether the player has root access, what ports are opened/closed, and also " +
"hacking-related information such as an estimated chance to successfully hack, an estimate of how much money is " +
export const HelpTexts: IMap<string[]> = {
alias: [
'alias [-g] [name="value"] ',
" ",
"Create or display aliases. An alias enables a replacement of a word with another string. ",
"It can be used to abbreviate a commonly used command, or commonly used parts of a command. The NAME ",
"of an alias defines the word that will be replaced, while the VALUE defines what it will be replaced by. For example, ",
"you could create the alias 'nuke' for the Terminal command 'run NUKE.exe' using the following: ",
" ",
'alias nuke="run NUKE.exe"',
" ",
"Then, to run the NUKE.exe program you would just have to enter 'nuke' in Terminal rather than the full command. ",
"It is important to note that 'default' aliases will only be substituted for the first word of a Terminal command. For ",
"example, if the following alias was set: ",
" ",
'alias worm="HTTPWorm.exe"',
" ",
"and then you tried to run the following terminal command: ",
" ",
"run worm",
" ",
"This would fail because the worm alias is not the first word of a Terminal command. To allow an alias to be substituted ",
"anywhere in a Terminal command, rather than just the first word, you must set it to be a global alias using the -g flag: ",
" ",
'alias -g worm="HTTPWorm.exe"',
" ",
"Now, the 'worm' alias will be substituted anytime it shows up as an individual word in a Terminal command. ",
" ",
"Entering just the command 'alias' without any arguments prints the list of all defined aliases in the reusable form ",
"'alias NAME=VALUE' on the Terminal. ",
" ",
"The 'unalias' command can be used to remove aliases.",
" ",
],
analyze: [
"analze",
" ",
"Prints details and statistics about the current server. The information that is printed includes basic ",
"server details such as the hostname, whether the player has root access, what ports are opened/closed, and also ",
"hacking-related information such as an estimated chance to successfully hack, an estimate of how much money is ",
"available on the server, etc.",
backdoor:
"backdoor<br>" +
"Install a backdoor on the current machine, grants a secret bonus depending on the machine.<br>" +
"Requires root access to run.<br>",
buy:
"buy [-l / program]<br>" +
"Purchase a program through the Dark Web. Requires a TOR router to use.<br><br>" +
"If this command is ran with the '-l' flag, it will display a list of all programs that can be bought through the " +
"dark web to the Terminal, as well as their costs.<br><br>" +
],
backdoor: [
"backdoor",
" ",
"Install a backdoor on the current machine, grants a secret bonus depending on the machine.",
" ",
"Requires root access to run.",
" ",
],
buy: [
"buy [-l / program]",
" ",
"Purchase a program through the Dark Web. Requires a TOR router to use.",
" ",
"If this command is ran with the '-l' flag, it will display a list of all programs that can be bought through the ",
"dark web to the Terminal, as well as their costs.",
" ",
"Otherwise, the name of the program must be passed in as a parameter. This name is NOT case-sensitive.",
cat:
"cat [file]<br>" +
"Display message (.msg), literature (.lit), or text (.txt) files. Examples:<br><br>" +
"cat j1.msg<br>" +
"cat foo.lit<br>" +
],
cat: [
"cat [file]",
" ",
"Display message (.msg), literature (.lit), or text (.txt) files. Examples:",
" ",
"cat j1.msg",
" ",
"cat foo.lit",
" ",
"cat servers.txt",
cd:
"cd [dir]<br>" +
"Change to the specified directory. Note that this works even for directories that don't exist. If you " +
"change to a directory that does not exist, it will not be 'created'. Examples:<br><br>" +
"cd scripts/hacking<br>" +
"cd /logs<br>" +
],
cd: [
"cd [dir]",
" ",
"Change to the specified directory. Note that this works even for directories that don't exist. If you ",
"change to a directory that does not exist, it will not be 'created'. Examples:",
" ",
"cd scripts/hacking",
" ",
"cd /logs",
" ",
"cd ../",
check:
"check [script name] [args...]<br>" +
"Print the logs of the script specified by the script name and arguments to the Terminal. Each argument must be separated by " +
"a space. Remember that a running script is uniquely " +
"identified both by its name and the arguments that are used to start it. So, if a script was ran with the following arguments: <br><br>" +
"run foo.script 1 2 foodnstuff<br><br>" +
"Then to run the 'check' command on this script you would have to pass the same arguments in: <br><br>" +
],
check: [
"check [script name] [args...]",
" ",
"Print the logs of the script specified by the script name and arguments to the Terminal. Each argument must be separated by ",
"a space. Remember that a running script is uniquely ",
"identified both by its name and the arguments that are used to start it. So, if a script was ran with the following arguments: ",
" ",
"run foo.script 1 2 foodnstuff",
" ",
"Then to run the 'check' command on this script you would have to pass the same arguments in: ",
" ",
"check foo.script 1 2 foodnstuff",
clear:
"clear<br>" +
"Clear the Terminal screen, deleting all of the text. Note that this does not delete the user's command history, so using the up " +
],
clear: [
"clear",
" ",
"Clear the Terminal screen, deleting all of the text. Note that this does not delete the user's command history, so using the up ",
"and down arrow keys is still valid. Also note that this is permanent and there is no way to undo this. Synonymous with 'cls' command",
cls:
"cls<br>" +
"Clear the Terminal screen, deleting all of the text. Note that this does not delete the user's command history, so using the up " +
],
cls: [
"cls",
" ",
"Clear the Terminal screen, deleting all of the text. Note that this does not delete the user's command history, so using the up ",
"and down arrow keys is still valid. Also note that this is permanent and there is no way to undo this. Synonymous with 'clear' command",
connect:
"connect [hostname/ip]<br>" +
"Connect to a remote server. The hostname or IP address of the remote server must be given as the argument " +
"to this command. Note that only servers that are immediately adjacent to the current server in the network can be connected to. To " +
],
connect: [
"connect [hostname/ip]",
" ",
"Connect to a remote server. The hostname or IP address of the remote server must be given as the argument ",
"to this command. Note that only servers that are immediately adjacent to the current server in the network can be connected to. To ",
"see which servers can be connected to, use the 'scan' command.",
download:
"download [script/text file]<br>" +
"Downloads a script or text file to your computer (like your real life computer).<br>" +
"You can also download all of your scripts/text files as a zip file using the following Terminal commands:<br><br>" +
"Download all scripts and text files: download *<br>" +
"Download all scripts: download *.script<br>" +
"Download all text files: download *.txt<br>",
expr:
"expr [mathematical expression]<br>" +
"Evaluate a simple mathematical expression. Supports native JavaScript operators:<br>" +
"+, -, /, *, **, %<br>" +
"Example:<br>" +
"expr 25 * 2 ** 10<br>" +
],
download: [
"download [script/text file]",
" ",
"Downloads a script or text file to your computer (like your real life computer).",
" ",
"You can also download all of your scripts/text files as a zip file using the following Terminal commands:",
" ",
"Download all scripts and text files: download *",
" ",
"Download all scripts: download *.script",
" ",
"Download all text files: download *.txt",
" ",
],
expr: [
"expr [mathematical expression]",
" ",
"Evaluate a simple mathematical expression. Supports native JavaScript operators:",
" ",
"+, -, /, *, **, %",
" ",
"Example:",
" ",
"expr 25 * 2 ** 10",
" ",
"Note that letters (non-digits) are not allowed and will be removed from the input.",
free:
"free<br>" +
"Display's the memory usage on the current machine. Print the amount of RAM that is available on the current server as well as " +
],
free: [
"free",
" ",
"Display's the memory usage on the current machine. Print the amount of RAM that is available on the current server as well as ",
"how much of it is being used.",
hack:
"hack<br>" +
"Attempt to hack the current server. Requires root access in order to be run. See the wiki page for hacking mechanics<br>",
help:
"help [command]<br>" +
"Display Terminal help information. Without arguments, 'help' prints a list of all valid Terminal commands and a brief " +
"description of their functionality. You can also pass the name of a Terminal command as an argument to 'help' to print " +
"more detailed information about the Terminal command. Examples: <br><br>" +
"help alias<br>" +
],
hack: [
"hack",
" ",
"Attempt to hack the current server. Requires root access in order to be run. See the wiki page for hacking mechanics",
" ",
],
help: [
"help [command]",
" ",
"Display Terminal help information. Without arguments, 'help' prints a list of all valid Terminal commands and a brief ",
"description of their functionality. You can also pass the name of a Terminal command as an argument to 'help' to print ",
"more detailed information about the Terminal command. Examples: ",
" ",
"help alias",
" ",
"help scan-analyze",
home:
"home<br>" + "Connect to your home computer. This will work no matter what server you are currently connected to.",
hostname: "hostname<br>" + "Prints the hostname of the current server",
ifconfig: "ipconfig<br>" + "Prints the IP address of the current server",
kill:
"kill [script name] [args...]<br>" +
"kill [pid]<br>" +
"Kill the script specified by the script name and arguments OR by its PID.<br><br>" +
"If you are killing the script using its filename and arguments, then each " +
"argument must be separated by a space. Remember that a running script is " +
"uniquely identified by both its name and the arguments that are used to start " +
"it. So, if a script was ran with the following arguments:<br><br>" +
"run foo.script 1 sigma-cosmetics<br><br>" +
"Then to kill this script the same arguments would have to be used:<br><br>" +
"kill foo.script 1 sigma-cosmetics<br><br>" +
],
home: [
"home" + "Connect to your home computer. This will work no matter what server you are currently connected to.",
],
hostname: ["hostname", " ", "Prints the hostname of the current server"],
ifconfig: ["ipconfig", " ", "Prints the IP address of the current server"],
kill: [
"kill [script name] [args...]",
" ",
"kill [pid]",
" ",
"Kill the script specified by the script name and arguments OR by its PID.",
" ",
"If you are killing the script using its filename and arguments, then each ",
"argument must be separated by a space. Remember that a running script is ",
"uniquely identified by both its name and the arguments that are used to start ",
"it. So, if a script was ran with the following arguments:",
" ",
"run foo.script 1 sigma-cosmetics",
" ",
"Then to kill this script the same arguments would have to be used:",
" ",
"kill foo.script 1 sigma-cosmetics",
" ",
"If you are killing the script using its PID, then the PID argument must be numeric",
killall:
"killall<br>" +
"Kills all scripts on the current server. " +
"Note that after the 'kill' command is issued for a script, it may take a while for the script to actually stop running. " +
"This will happen if the script is in the middle of a command such as grow() or weaken() that takes time to execute. " +
],
killall: [
"killall",
" ",
"Kills all scripts on the current server. ",
"Note that after the 'kill' command is issued for a script, it may take a while for the script to actually stop running. ",
"This will happen if the script is in the middle of a command such as grow() or weaken() that takes time to execute. ",
"The script will not be stopped/killed until after that time has elapsed.",
ls:
"ls [dir] [| grep pattern]<br>" +
"The ls command, with no arguments, prints all files and directories on the current server's directory to the Terminal screen. " +
"The files will be displayed in alphabetical order. <br><br>" +
"The 'dir' optional parameter can be used to display files/directories in another directory.<br><br>" +
"The '| grep pattern' optional parameter can be used to only display files whose filenames match the specified pattern.<br><br>" +
"Examples:<br><br>" +
"List all files with the '.script' extension in the current directory:<br>" +
"ls | grep .script<br><br>" +
"List all files with the '.js' extension in the root directory:<br>" +
"ls / | grep .js<br><br>" +
"List all files with the word 'purchase' in the filename, in the 'scripts' directory:<br>" +
],
ls: [
"ls [dir] [| grep pattern]",
" ",
"The ls command, with no arguments, prints all files and directories on the current server's directory to the Terminal screen. ",
"The files will be displayed in alphabetical order. ",
" ",
"The 'dir' optional parameter can be used to display files/directories in another directory.",
" ",
"The '| grep pattern' optional parameter can be used to only display files whose filenames match the specified pattern.",
" ",
"Examples:",
" ",
"List all files with the '.script' extension in the current directory:",
" ",
"ls | grep .script",
" ",
"List all files with the '.js' extension in the root directory:",
" ",
"ls / | grep .js",
" ",
"List all files with the word 'purchase' in the filename, in the 'scripts' directory:",
" ",
"ls scripts | grep purchase",
lscpu: "lscpu<br>" + "Prints the number of CPU Cores the current server has",
mem:
"mem [script name] [-t] [num threads]<br>" +
"Displays the amount of RAM needed to run the specified script with a single thread. The command can also be used to print " +
"the amount of RAM needed to run a script with multiple threads using the '-t' flag. If the '-t' flag is specified, then " +
"an argument for the number of threads must be passed in afterwards. Examples:<br><br>" +
"mem foo.script<br>" +
"mem foo.script -t 50<br>" +
"The first example above will print the amount of RAM needed to run 'foo.script' with a single thread. The second example " +
],
lscpu: ["lscpu", " ", "Prints the number of CPU Cores the current server has"],
mem: [
"mem [script name] [-t] [num threads]",
" ",
"Displays the amount of RAM needed to run the specified script with a single thread. The command can also be used to print ",
"the amount of RAM needed to run a script with multiple threads using the '-t' flag. If the '-t' flag is specified, then ",
"an argument for the number of threads must be passed in afterwards. Examples:",
" ",
"mem foo.script",
" ",
"mem foo.script -t 50",
" ",
"The first example above will print the amount of RAM needed to run 'foo.script' with a single thread. The second example ",
"above will print the amount of RAM needed to run 'foo.script' with 50 threads.",
mv:
"mv [src] [dest]<br>" +
"Move the source file to the specified destination. This can also be used to rename files. " +
"This command only works for scripts and text files (.txt). This command CANNOT be used to " +
"convert to different file types<br><br>" +
"Note that, unlike the Linux 'mv' command, the destination argument must be the " +
"full filepath. " +
"Examples: <br><br>" +
"mv hacking-controller.script scripts/hacking-controller.script<br>" +
],
mv: [
"mv [src] [dest]",
" ",
"Move the source file to the specified destination. This can also be used to rename files. ",
"This command only works for scripts and text files (.txt). This command CANNOT be used to ",
"convert to different file types",
" ",
"Note that, unlike the Linux 'mv' command, the destination argument must be the ",
"full filepath. ",
"Examples: ",
" ",
"mv hacking-controller.script scripts/hacking-controller.script",
" ",
"mv myScript.js myOldScript.js",
nano:
"nano [file name]<br>" +
"Opens up the specified file in the Text Editor. Only scripts (.script) or text files (.txt) can be " +
"edited using the Text Editor. If the file does not already exist, then a new, empty one " +
],
nano: [
"nano [file name]",
" ",
"Opens up the specified file in the Text Editor. Only scripts (.script) or text files (.txt) can be ",
"edited using the Text Editor. If the file does not already exist, then a new, empty one ",
"will be created",
ps: "ps<br>" + "Prints all scripts that are running on the current server",
rm:
"rm [file]<br>" +
"Removes the specified file from the current server. A file can be a script, a program, or a message file. <br><br>" +
],
ps: ["ps", " ", "Prints all scripts that are running on the current server"],
rm: [
"rm [file]",
" ",
"Removes the specified file from the current server. A file can be a script, a program, or a message file. ",
" ",
"WARNING: This is permanent and cannot be undone",
run:
"run [file name] [-t] [num threads] [args...]<br>" +
"Execute a program or a script.<br><br>" +
"The '[-t]', '[num threads]', and '[args...]' arguments are only valid when running a script. The '-t' flag is used " +
"to indicate that the script should be run with the specified number of threads. If the flag is omitted, " +
"then the script will be run with a single thread by default. " +
"If the '-t' flag is used, then it MUST come immediately " +
"after the script name, and the [num threads] argument MUST come immediately afterwards. <br><br>" +
"[args...] represents a variable number of arguments that will be passed into the script. See the documentation " +
"about script arguments. Each specified argument must be separated by a space. <br><br>",
scan:
"scan<br>" +
"Prints all immediately-available network connection. This will print a list of all servers that you can currently connect " +
],
run: [
"run [file name] [-t] [num threads] [args...]",
" ",
"Execute a program or a script.",
" ",
"The '[-t]', '[num threads]', and '[args...]' arguments are only valid when running a script. The '-t' flag is used ",
"to indicate that the script should be run with the specified number of threads. If the flag is omitted, ",
"then the script will be run with a single thread by default. ",
"If the '-t' flag is used, then it MUST come immediately ",
"after the script name, and the [num threads] argument MUST come immediately afterwards. ",
" ",
"[args...] represents a variable number of arguments that will be passed into the script. See the documentation ",
"about script arguments. Each specified argument must be separated by a space. ",
" ",
],
scan: [
"scan",
" ",
"Prints all immediately-available network connection. This will print a list of all servers that you can currently connect ",
"to using the 'connect' Terminal command.",
"scan-analyze":
"scan-analyze [depth] [-a]<br>" +
"Prints detailed information about all servers up to [depth] nodes away on the network. Calling " +
"'scan-analyze 1' will display information for the same servers that are shown by the 'scan' Terminal " +
"command. This command also shows the relative paths to reach each server.<br><br>" +
"By default, the maximum depth that can be specified for 'scan-analyze' is 3. However, once you have " +
"the DeepscanV1.exe and DeepscanV2.exe programs, you can execute 'scan-analyze' with a depth up to " +
"5 and 10, respectively.<br><br>" +
"The information 'scan-analyze' displays about each server includes whether or not you have root access to it, " +
"its required hacking level, the number of open ports required to run NUKE.exe on it, and how much RAM " +
"it has.<br><br>" +
"By default, this command will not display servers that you have purchased. However, you can pass in the " +
],
"scan-analyze": [
"scan-analyze [depth] [-a]",
" ",
"Prints detailed information about all servers up to [depth] nodes away on the network. Calling ",
"'scan-analyze 1' will display information for the same servers that are shown by the 'scan' Terminal ",
"command. This command also shows the relative paths to reach each server.",
" ",
"By default, the maximum depth that can be specified for 'scan-analyze' is 3. However, once you have ",
"the DeepscanV1.exe and DeepscanV2.exe programs, you can execute 'scan-analyze' with a depth up to ",
"5 and 10, respectively.",
" ",
"The information 'scan-analyze' displays about each server includes whether or not you have root access to it, ",
"its required hacking level, the number of open ports required to run NUKE.exe on it, and how much RAM ",
"it has.",
" ",
"By default, this command will not display servers that you have purchased. However, you can pass in the ",
"-a flag at the end of the command if you would like to enable that.",
scp:
"scp [filename] [target server]<br>" +
"Copies the specified file from the current server to the target server. " +
"This command only works for script files (.script extension), literature files (.lit extension), " +
"and text files (.txt extension). " +
],
scp: [
"scp [filename] [target server]",
" ",
"Copies the specified file from the current server to the target server. ",
"This command only works for script files (.script extension), literature files (.lit extension), ",
"and text files (.txt extension). ",
"The second argument passed in must be the hostname or IP of the target server.",
sudov: "sudov<br>" + "Prints whether or not you have root access to the current machine",
tail:
"tail [script name] [args...]<br>" +
"Displays dynamic logs for the script specified by the script name and arguments. Each argument must be separated " +
"by a space. Remember that a running script is uniquely identified by both its name and the arguments that were used " +
"to run it. So, if a script was ran with the following arguments: <br><br>" +
"run foo.script 10 50000<br><br>" +
"Then in order to check its logs with 'tail' the same arguments must be used: <br><br>" +
],
sudov: ["sudov", " ", "Prints whether or not you have root access to the current machine"],
tail: [
"tail [script name] [args...]",
" ",
"Displays dynamic logs for the script specified by the script name and arguments. Each argument must be separated ",
"by a space. Remember that a running script is uniquely identified by both its name and the arguments that were used ",
"to run it. So, if a script was ran with the following arguments: ",
" ",
"run foo.script 10 50000",
" ",
"Then in order to check its logs with 'tail' the same arguments must be used: ",
" ",
"tail foo.script 10 50000",
theme:
"theme [preset] | [#background #text #highlight]<br>" +
"Change the color of the game's user interface<br><br>" +
"This command can be called with a preset theme. Currently, the supported presets are 'default', 'muted', and 'solarized'. " +
"However, you can also specify your own color scheme using hex values. To do so, you must specify three hex color values " +
"for the background color, the text color, and the highlight color. These hex values must be preceded by a pound sign (#) and " +
"must be either 3 or 6 digits. Example:<br><br>" +
"theme #ffffff #385 #235012<br><br>" +
"A color picker such as " +
"<a href='https://www.google.com/search?q=color+picker&oq=color+picker&aqs=chrome.0.0l6.951j0j1&sourceid=chrome&ie=UTF-8' target='_blank'>Google's</a> " +
"can be used to get your desired hex color values<br><br>" +
],
theme: [
"theme [preset] | [#background #text #highlight]",
" ",
"Change the color of the game's user interface",
" ",
"This command can be called with a preset theme. Currently, the supported presets are 'default', 'muted', and 'solarized'. ",
"However, you can also specify your own color scheme using hex values. To do so, you must specify three hex color values ",
"for the background color, the text color, and the highlight color. These hex values must be preceded by a pound sign (#) and ",
"must be either 3 or 6 digits. Example:",
" ",
"theme #ffffff #385 #235012",
" ",
"A color picker such as ",
"<a href='https://www.google.com/search?q=color+picker&oq=color+picker&aqs=chrome.0.0l6.951j0j1&sourceid=chrome&ie=UTF-8' target='_blank'>Google's</a> ",
"can be used to get your desired hex color values",
" ",
"Themes are not saved, so when the game is closed and then re-opened or reloaded then it will revert back to the default theme.",
top:
"top<br>" +
"Prints a list of all scripts running on the current server as well as their thread count and how much " +
],
top: [
"top",
" ",
"Prints a list of all scripts running on the current server as well as their thread count and how much ",
"RAM they are using in total.",
unalias:
"unalias [alias name]<br>" +
"Deletes the specified alias. Note that the double quotation marks are required. <br><br>" +
"As an example, if an alias was declared using:<br><br>" +
'alias r="run"<br><br>' +
"Then it could be removed using:<br><br>" +
"unalias r<br><br>" +
],
unalias: [
"unalias [alias name]",
" ",
"Deletes the specified alias. Note that the double quotation marks are required. ",
" ",
"As an example, if an alias was declared using:",
" ",
'alias r="run"',
" ",
"Then it could be removed using:",
" ",
"unalias r",
" ",
"It is not necessary to differentiate between global and non-global aliases when using 'unalias'",
wget:
"wget [url] [target file]<br>" +
"Retrieves data from a URL and downloads it to a file on the current server. The data can only " +
"be downloaded to a script (.script, .ns, .js) or a text file (.txt). If the file already exists, " +
"it will be overwritten by this command.<br><br>" +
"Note that it will not be possible to download data from many websites because they do not allow " +
"cross-origin resource sharing (CORS). Example:<br><br>" +
],
wget: [
"wget [url] [target file]",
" ",
"Retrieves data from a URL and downloads it to a file on the current server. The data can only ",
"be downloaded to a script (.script, .ns, .js) or a text file (.txt). If the file already exists, ",
"it will be overwritten by this command.",
" ",
"Note that it will not be possible to download data from many websites because they do not allow ",
"cross-origin resource sharing (CORS). Example:",
" ",
"wget https://raw.githubusercontent.com/danielyxie/bitburner/master/README.md game_readme.txt",
],
};

80
src/Terminal/ITerminal.ts Normal file

@ -0,0 +1,80 @@
import { TextFile } from "../TextFile";
import { Script } from "../Script/Script";
import { IPlayer } from "../PersonObjects/IPlayer";
import { IEngine } from "../IEngine";
export class Output {
text: string;
color: "inherit" | "initial" | "primary" | "secondary" | "error" | "textPrimary" | "textSecondary" | undefined;
constructor(
text: string,
color: "inherit" | "initial" | "primary" | "secondary" | "error" | "textPrimary" | "textSecondary" | undefined,
) {
this.text = text;
this.color = color;
}
}
export class Link {
hostname: string;
constructor(hostname: string) {
this.hostname = hostname;
}
}
export class TTimer {
time: number;
timeLeft: number;
action: "h" | "b" | "a";
constructor(time: number, action: "h" | "b" | "a") {
this.time = time;
this.timeLeft = time;
this.action = action;
}
}
export interface ITerminal {
action: TTimer | null;
commandHistory: string[];
commandHistoryIndex: number;
outputHistory: (Output | Link)[];
// True if a Coding Contract prompt is opened
contractOpen: boolean;
// Full Path of current directory
// Excludes the trailing forward slash
currDir: string;
print(s: string): void;
error(s: string): void;
clear(): void;
startAnalyze(): void;
startBackdoor(player: IPlayer): void;
startHack(player: IPlayer): void;
finishHack(player: IPlayer, cancelled?: boolean): void;
finishBackdoor(player: IPlayer, cancelled?: boolean): void;
finishAnalyze(player: IPlayer, cancelled?: boolean): void;
finishAction(player: IPlayer, cancelled?: boolean): void;
getFilepath(filename: string): string;
getFile(player: IPlayer, filename: string): Script | TextFile | string | null;
getScript(player: IPlayer, filename: string): Script | null;
getTextFile(player: IPlayer, filename: string): TextFile | null;
getLitFile(player: IPlayer, filename: string): string | null;
cwd(): string;
setcwd(dir: string): void;
runContract(player: IPlayer, name: string): void;
executeScanAnalyzeCommand(player: IPlayer, depth?: number, all?: boolean): void;
connectToServer(player: IPlayer, server: string): void;
executeCommand(engine: IEngine, player: IPlayer, command: string): void;
executeCommands(engine: IEngine, player: IPlayer, commands: string): void;
// If there was any changes, will return true, once.
pollChanges(): boolean;
process(player: IPlayer, cycles: number): void;
prestige(): void;
getProgressText(): string;
}

127
src/Terminal/Parser.ts Normal file

@ -0,0 +1,127 @@
import { substituteAliases } from "../Alias";
// Helper function that checks if an argument (which is a string) is a valid number
function isNumber(str: string): boolean {
if (typeof str != "string") {
return false;
} // Only process strings
return !isNaN(parseFloat(str));
}
export function ParseCommands(commands: string): string[] {
// Sanitize input
commands = commands.trim();
// Replace all extra whitespace in command with a single space
commands = commands.replace(/\s\s+/g, " ");
const match = commands.match(/(?:'[^']*'|"[^"]*"|[^;"])*/g);
if (!match) return [];
// Split commands and execute sequentially
const allCommands = match
.map(substituteAliases)
.map((c) => c.match(/(?:'[^']*'|"[^"]*"|[^;"])*/g))
.flat();
const out: string[] = [];
for (const c of allCommands) {
if (c === null) continue;
if (c.match(/^\s*$/)) {
continue;
} // Don't run commands that only have whitespace
out.push(c.trim());
}
return out;
}
export function ParseCommand(command: string): (string | number)[] {
// This will be used to keep track of whether we're in a quote. This is for situations
// like the alias command:
// alias run="run NUKE.exe"
// We want the run="run NUKE.exe" to be parsed as a single command, so this flag
// will keep track of whether we have a quote in
let inQuote = ``;
// Returns an array with the command and its arguments in each index
// Properly handles quotation marks (e.g. `run foo.script "the sun"` will return [run, foo.script, the sun])
const args = [];
let start = 0,
i = 0;
let prevChar = ""; // Previous character
while (i < command.length) {
let escaped = false; // Check for escaped quotation marks
if (i >= 1) {
prevChar = command.charAt(i - 1);
if (prevChar === "\\") {
escaped = true;
}
}
const c = command.charAt(i);
if (c === '"') {
// Double quotes
if (!escaped && prevChar === " ") {
const endQuote = command.indexOf('"', i + 1);
if (endQuote !== -1 && (endQuote === command.length - 1 || command.charAt(endQuote + 1) === " ")) {
args.push(command.substr(i + 1, endQuote - i - 1));
if (endQuote === command.length - 1) {
start = i = endQuote + 1;
} else {
start = i = endQuote + 2; // Skip the space
}
continue;
}
} else {
if (inQuote === ``) {
inQuote = `"`;
} else if (inQuote === `"`) {
inQuote = ``;
}
}
} else if (c === "'") {
// Single quotes, same thing as above
if (!escaped && prevChar === " ") {
const endQuote = command.indexOf("'", i + 1);
if (endQuote !== -1 && (endQuote === command.length - 1 || command.charAt(endQuote + 1) === " ")) {
args.push(command.substr(i + 1, endQuote - i - 1));
if (endQuote === command.length - 1) {
start = i = endQuote + 1;
} else {
start = i = endQuote + 2; // Skip the space
}
continue;
}
} else {
if (inQuote === ``) {
inQuote = `'`;
} else if (inQuote === `'`) {
inQuote = ``;
}
}
} else if (c === " " && inQuote === ``) {
const arg = command.substr(start, i - start);
// If this is a number, convert it from a string to number
if (isNumber(arg)) {
args.push(parseFloat(arg));
} else {
args.push(arg);
}
start = i + 1;
}
++i;
}
// Add the last argument
if (start !== i) {
const arg = command.substr(start, i - start);
// If this is a number, convert it from string to number
if (isNumber(arg)) {
args.push(parseFloat(arg));
} else {
args.push(arg);
}
}
return args;
}

726
src/Terminal/Terminal.ts Normal file

@ -0,0 +1,726 @@
import { ITerminal, Output, Link, TTimer } from "./ITerminal";
import { IEngine } from "../IEngine";
import { IPlayer } from "../PersonObjects/IPlayer";
import { HacknetServer } from "../Hacknet/HacknetServer";
import { BaseServer } from "../Server/BaseServer";
import { hackWorldDaemon } from "../RedPill";
import { Programs } from "../Programs/Programs";
import { CodingContractResult } from "../CodingContracts";
import { TextFile } from "../TextFile";
import { Script } from "../Script/Script";
import { isScriptFilename } from "../Script/ScriptHelpersTS";
import { CONSTANTS } from "../Constants";
import { AllServers } from "../Server/AllServers";
import { removeLeadingSlash, isInRootDirectory, evaluateFilePath } from "./DirectoryHelpers";
import { checkIfConnectedToDarkweb } from "../DarkWeb/DarkWeb";
import { logBoxCreate } from "../../utils/LogBox";
import { iTutorialNextStep, iTutorialSteps, ITutorial } from "../InteractiveTutorial";
import { findRunningScript } from "../Script/ScriptHelpers";
import { TerminalHelpText } from "./HelpText";
import { GetServerByHostname, getServer, getServerOnNetwork } from "../Server/ServerHelpers";
import { ParseCommand, ParseCommands } from "./Parser";
import { SpecialServerIps, SpecialServerNames } from "../Server/SpecialServerIps";
import { Settings } from "../Settings/Settings";
import { createProgressBarText } from "../../utils/helpers/createProgressBarText";
import {
calculateHackingChance,
calculateHackingExpGain,
calculatePercentMoneyHacked,
calculateHackingTime,
} from "../Hacking";
import { numeralWrapper } from "../ui/numeralFormat";
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
import { alias } from "./commands/alias";
import { analyze } from "./commands/analyze";
import { backdoor } from "./commands/backdoor";
import { buy } from "./commands/buy";
import { cat } from "./commands/cat";
import { cd } from "./commands/cd";
import { check } from "./commands/check";
import { connect } from "./commands/connect";
import { download } from "./commands/download";
import { expr } from "./commands/expr";
import { free } from "./commands/free";
import { hack } from "./commands/hack";
import { help } from "./commands/help";
import { home } from "./commands/home";
import { hostname } from "./commands/hostname";
import { ifconfig } from "./commands/ifconfig";
import { kill } from "./commands/kill";
import { killall } from "./commands/killall";
import { ls } from "./commands/ls";
import { lscpu } from "./commands/lscpu";
import { mem } from "./commands/mem";
import { mv } from "./commands/mv";
import { nano } from "./commands/nano";
import { ps } from "./commands/ps";
import { rm } from "./commands/rm";
import { run } from "./commands/run";
import { scan } from "./commands/scan";
import { scananalyze } from "./commands/scananalyze";
import { scp } from "./commands/scp";
import { sudov } from "./commands/sudov";
import { tail } from "./commands/tail";
import { theme } from "./commands/theme";
import { top } from "./commands/top";
import { unalias } from "./commands/unalias";
import { wget } from "./commands/wget";
export class Terminal implements ITerminal {
hasChanges = false;
// Flags to determine whether the player is currently running a hack or an analyze
action: TTimer | null = null;
commandHistory: string[] = [];
commandHistoryIndex = 0;
outputHistory: (Output | Link)[] = [new Output(`Bitburner v${CONSTANTS.Version}`, "primary")];
// True if a Coding Contract prompt is opened
contractOpen = false;
// Full Path of current directory
// Excludes the trailing forward slash
currDir = "/";
process(player: IPlayer, cycles: number): void {
if (this.action === null) return;
this.action.timeLeft -= (CONSTANTS._idleSpeed * cycles) / 1000;
this.hasChanges = true;
if (this.action.timeLeft < 0) this.finishAction(player, false);
}
pollChanges(): boolean {
if (this.hasChanges) {
this.hasChanges = false;
return true;
}
return false;
}
append(item: Output | Link): void {
this.outputHistory.push(item);
if (this.outputHistory.length > Settings.MaxTerminalCapacity) {
this.outputHistory.slice(this.outputHistory.length - Settings.MaxTerminalCapacity);
}
}
print(s: string): void {
this.append(new Output(s, "primary"));
this.hasChanges = true;
}
error(s: string): void {
this.append(new Output(s, "error"));
this.hasChanges = true;
}
startHack(player: IPlayer): void {
// Hacking through Terminal should be faster than hacking through a script
this.startAction(calculateHackingTime(player.getCurrentServer(), player) / 4, "h");
}
startBackdoor(player: IPlayer): void {
// Backdoor should take the same amount of time as hack
this.startAction(calculateHackingTime(player.getCurrentServer(), player) / 4, "b");
}
startAnalyze(): void {
this.print("Analyzing system...");
this.startAction(1, "a");
}
startAction(n: number, action: "h" | "b" | "a"): void {
this.action = new TTimer(n, action);
}
// Complete the hack/analyze command
finishHack(player: IPlayer, cancelled = false): void {
if (cancelled) return;
const server = player.getCurrentServer();
// Calculate whether hack was successful
const hackChance = calculateHackingChance(server, player);
const rand = Math.random();
const expGainedOnSuccess = calculateHackingExpGain(server, player);
const expGainedOnFailure = expGainedOnSuccess / 4;
if (rand < hackChance) {
// Success!
if (
SpecialServerIps[SpecialServerNames.WorldDaemon] &&
SpecialServerIps[SpecialServerNames.WorldDaemon] == server.ip
) {
if (player.bitNodeN == null) {
player.bitNodeN = 1;
}
hackWorldDaemon(player.bitNodeN);
return;
}
server.backdoorInstalled = true;
let moneyGained = calculatePercentMoneyHacked(server, player);
moneyGained = Math.floor(server.moneyAvailable * moneyGained);
if (moneyGained <= 0) {
moneyGained = 0;
} // Safety check
server.moneyAvailable -= moneyGained;
player.gainMoney(moneyGained);
player.recordMoneySource(moneyGained, "hacking");
player.gainHackingExp(expGainedOnSuccess);
player.gainIntelligenceExp(expGainedOnSuccess / CONSTANTS.IntelligenceTerminalHackBaseExpGain);
server.fortify(CONSTANTS.ServerFortifyAmount);
this.print(
`Hack successful! Gained ${numeralWrapper.formatMoney(moneyGained)} and ${numeralWrapper.formatExp(
expGainedOnSuccess,
)} hacking exp`,
);
} else {
// Failure
// player only gains 25% exp for failure? TODO Can change this later to balance
player.gainHackingExp(expGainedOnFailure);
this.print(
`Failed to hack ${server.hostname}. Gained ${numeralWrapper.formatExp(expGainedOnFailure)} hacking exp`,
);
}
}
finishBackdoor(player: IPlayer, cancelled = false): void {
if (!cancelled) {
const server = player.getCurrentServer();
if (
SpecialServerIps[SpecialServerNames.WorldDaemon] &&
SpecialServerIps[SpecialServerNames.WorldDaemon] == server.ip
) {
if (player.bitNodeN == null) {
player.bitNodeN = 1;
}
hackWorldDaemon(player.bitNodeN);
return;
}
server.backdoorInstalled = true;
this.print("Backdoor successful!");
}
}
finishAnalyze(player: IPlayer, cancelled = false): void {
if (!cancelled) {
const currServ = player.getCurrentServer();
const isHacknet = currServ instanceof HacknetServer;
this.print(currServ.hostname + ": ");
const org = currServ.organizationName;
this.print("Organization name: " + (!isHacknet ? org : "player"));
const hasAdminRights = (!isHacknet && currServ.hasAdminRights) || isHacknet;
this.print("Root Access: " + (hasAdminRights ? "YES" : "NO"));
const hackingSkill = currServ.requiredHackingSkill;
this.print("Required hacking skill: " + (!isHacknet ? hackingSkill : "N/A"));
const security = currServ.hackDifficulty;
this.print("Server security level: " + (!isHacknet ? numeralWrapper.formatServerSecurity(security) : "N/A"));
const hackingChance = calculateHackingChance(currServ, player);
this.print("Chance to hack: " + (!isHacknet ? numeralWrapper.formatPercentage(hackingChance) : "N/A"));
const hackingTime = calculateHackingTime(currServ, player) * 1000;
this.print("Time to hack: " + (!isHacknet ? convertTimeMsToTimeElapsedString(hackingTime, true) : "N/A"));
this.print(
`Total money available on server: ${!isHacknet ? numeralWrapper.formatMoney(currServ.moneyAvailable) : "N/A"}`,
);
const numPort = currServ.numOpenPortsRequired;
this.print("Required number of open ports for NUKE: " + (!isHacknet ? numPort : "N/A"));
this.print("SSH port: " + (currServ.sshPortOpen ? "Open" : "Closed"));
this.print("FTP port: " + (currServ.ftpPortOpen ? "Open" : "Closed"));
this.print("SMTP port: " + (currServ.smtpPortOpen ? "Open" : "Closed"));
this.print("HTTP port: " + (currServ.httpPortOpen ? "Open" : "Closed"));
this.print("SQL port: " + (currServ.sqlPortOpen ? "Open" : "Closed"));
}
}
finishAction(player: IPlayer, cancelled = false): void {
if (this.action === null) {
if (!cancelled) throw new Error("Finish action called when there was no action");
return;
}
this.print(this.getProgressText());
if (this.action.action === "h") {
this.finishHack(player, cancelled);
} else if (this.action.action === "b") {
this.finishBackdoor(player, cancelled);
} else if (this.action.action === "a") {
this.finishAnalyze(player, cancelled);
}
if (cancelled) {
this.print("Cancelled");
}
this.action = null;
}
getFile(player: IPlayer, filename: string): Script | TextFile | string | null {
if (isScriptFilename(filename)) {
return this.getScript(player, filename);
}
if (filename.endsWith(".lit")) {
return this.getLitFile(player, filename);
}
if (filename.endsWith(".txt")) {
return this.getTextFile(player, filename);
}
return null;
}
getFilepath(filename: string): string {
const path = evaluateFilePath(filename, this.cwd());
if (path == null) {
throw new Error(`Invalid file path specified: ${filename}`);
}
if (isInRootDirectory(path)) {
return removeLeadingSlash(path);
}
return path;
}
getScript(player: IPlayer, filename: string): Script | null {
const s = player.getCurrentServer();
const filepath = this.getFilepath(filename);
for (const script of s.scripts) {
if (filepath === script.filename) {
return script;
}
}
return null;
}
getTextFile(player: IPlayer, filename: string): TextFile | null {
const s = player.getCurrentServer();
const filepath = this.getFilepath(filename);
for (const txt of s.textFiles) {
if (filepath === txt.fn) {
return txt;
}
}
return null;
}
getLitFile(player: IPlayer, filename: string): string | null {
const s = player.getCurrentServer();
const filepath = this.getFilepath(filename);
for (const lit of s.messages) {
if (typeof lit === "string" && filepath === lit) {
return lit;
}
}
return null;
}
cwd(): string {
return this.currDir;
}
setcwd(dir: string): void {
this.currDir = dir;
this.hasChanges = true;
}
async runContract(player: IPlayer, contractName: string): Promise<void> {
// There's already an opened contract
if (this.contractOpen) {
return this.error("There's already a Coding Contract in Progress");
}
const serv = player.getCurrentServer();
const contract = serv.getContract(contractName);
if (contract == null) {
return this.error("No such contract");
}
this.contractOpen = true;
const res = await contract.prompt();
switch (res) {
case CodingContractResult.Success:
if (contract.reward !== null) {
const reward = player.gainCodingContractReward(contract.reward, contract.getDifficulty());
this.print(`Contract SUCCESS - ${reward}`);
}
serv.removeContract(contract);
break;
case CodingContractResult.Failure:
++contract.tries;
if (contract.tries >= contract.getMaxNumTries()) {
this.print("Contract <p style='color:red;display:inline'>FAILED</p> - Contract is now self-destructing");
serv.removeContract(contract);
} else {
this.print(
`Contract <p style='color:red;display:inline'>FAILED</p> - ${
contract.getMaxNumTries() - contract.tries
} tries remaining`,
);
}
break;
case CodingContractResult.Cancelled:
default:
this.print("Contract cancelled");
break;
}
this.contractOpen = false;
}
executeScanAnalyzeCommand(player: IPlayer, depth = 1, all = false): void {
// TODO Using array as stack for now, can make more efficient
this.print("~~~~~~~~~~ Beginning scan-analyze ~~~~~~~~~~");
this.print(" ");
// Map of all servers to keep track of which have been visited
const visited: {
[key: string]: number | undefined;
} = {};
for (const ip in AllServers) {
visited[ip] = 0;
}
const stack: BaseServer[] = [];
const depthQueue: number[] = [0];
const currServ = player.getCurrentServer();
stack.push(currServ);
while (stack.length != 0) {
const s = stack.pop();
if (!s) continue;
const d = depthQueue.pop();
if (d === undefined) continue;
const isHacknet = s instanceof HacknetServer;
if (!all && (s as any).purchasedByPlayer && s.hostname != "home") {
continue; // Purchased server
} else if (visited[s.ip] || d > depth) {
continue; // Already visited or out-of-depth
} else if (!all && isHacknet) {
continue; // Hacknet Server
} else {
visited[s.ip] = 1;
}
for (let i = s.serversOnNetwork.length - 1; i >= 0; --i) {
const newS = getServerOnNetwork(s, i);
if (newS === null) continue;
stack.push(newS);
depthQueue.push(d + 1);
}
if (d == 0) {
continue;
} // Don't print current server
const titleDashes = Array((d - 1) * 4 + 1).join("-");
if (player.hasProgram(Programs.AutoLink.name)) {
this.append(new Link(s.hostname));
} else {
this.print(s.hostname);
}
const dashes = titleDashes + "--";
let c = "NO";
if (s.hasAdminRights) {
c = "YES";
}
this.print(
`${dashes}Root Access: ${c}${!isHacknet ? ", Required hacking skill: " + (s as any).requiredHackingSkill : ""}`,
);
if (s.hasOwnProperty("numOpenPortsRequired")) {
this.print(dashes + "Number of open ports required to NUKE: " + (s as any).numOpenPortsRequired);
}
this.print(dashes + "RAM: " + numeralWrapper.formatRAM(s.maxRam));
this.print(" ");
}
const links = document.getElementsByClassName("scan-analyze-link");
for (let i = 0; i < links.length; ++i) {
(() => {
const hostname = links[i].innerHTML.toString();
links[i].addEventListener("onclick", () => {
if (this.action !== null) {
return;
}
this.connectToServer(player, hostname);
});
})(); // Immediate invocation
}
}
connectToServer(player: IPlayer, server: string): void {
const serv = getServer(server);
if (serv == null) {
this.error("Invalid server. Connection failed.");
return;
}
player.getCurrentServer().isConnectedTo = false;
player.currentServer = serv.ip;
player.getCurrentServer().isConnectedTo = true;
this.print("Connected to " + serv.hostname);
this.setcwd("/");
if (player.getCurrentServer().hostname == "darkweb") {
checkIfConnectedToDarkweb(); // Posts a 'help' message if connecting to dark web
}
}
executeCommands(engine: IEngine, player: IPlayer, commands: string): void {
// Sanitize input
commands = commands.trim();
commands = commands.replace(/\s\s+/g, " "); // Replace all extra whitespace in command with a single space
// Handle Terminal History - multiple commands should be saved as one
if (this.commandHistory[this.commandHistory.length - 1] != commands) {
this.commandHistory.push(commands);
if (this.commandHistory.length > 50) {
this.commandHistory.splice(0, 1);
}
}
this.commandHistoryIndex = this.commandHistory.length;
const allCommands = ParseCommands(commands);
for (let i = 0; i < allCommands.length; i++) {
this.executeCommand(engine, player, allCommands[i]);
}
}
clear(): void {
// TODO: remove this once we figure out the height issue.
this.outputHistory = [new Output(`Bitburner v${CONSTANTS.Version}`, "primary")];
this.hasChanges = true;
}
prestige(): void {
this.action = null;
this.clear();
}
executeCommand(engine: IEngine, player: IPlayer, command: string): void {
if (this.action !== null) {
this.error(`Cannot execute command (${command}) while an action is in progress`);
return;
}
// Allow usage of ./
if (command.startsWith("./")) {
command = "run " + command.slice(2);
}
// Only split the first space
const commandArray = ParseCommand(command);
if (commandArray.length == 0) {
return;
}
const s = player.getCurrentServer();
/****************** Interactive Tutorial Terminal Commands ******************/
if (ITutorial.isRunning) {
const n00dlesServ = GetServerByHostname("n00dles");
if (n00dlesServ == null) {
throw new Error("Could not get n00dles server");
return;
}
switch (ITutorial.currStep) {
case iTutorialSteps.TerminalHelp:
if (commandArray.length === 1 && commandArray[0] == "help") {
TerminalHelpText.forEach((line) => this.print(line));
iTutorialNextStep();
} else {
this.print("Bad command. Please follow the tutorial");
}
break;
case iTutorialSteps.TerminalLs:
if (commandArray.length === 1 && commandArray[0] == "ls") {
ls(this, engine, player, s, commandArray.slice(1));
iTutorialNextStep();
} else {
this.print("Bad command. Please follow the tutorial");
}
break;
case iTutorialSteps.TerminalScan:
if (commandArray.length === 1 && commandArray[0] == "scan") {
scan(this, engine, player, s, commandArray.slice(1));
iTutorialNextStep();
} else {
this.print("Bad command. Please follow the tutorial");
}
break;
case iTutorialSteps.TerminalScanAnalyze1:
if (commandArray.length == 1 && commandArray[0] == "scan-analyze") {
this.executeScanAnalyzeCommand(player, 1);
iTutorialNextStep();
} else {
this.print("Bad command. Please follow the tutorial");
}
break;
case iTutorialSteps.TerminalScanAnalyze2:
if (commandArray.length == 2 && commandArray[0] == "scan-analyze" && commandArray[1] === 2) {
this.executeScanAnalyzeCommand(player, 2);
iTutorialNextStep();
} else {
this.print("Bad command. Please follow the tutorial");
}
break;
case iTutorialSteps.TerminalConnect:
if (commandArray.length == 2) {
if (commandArray[0] == "connect" && (commandArray[1] == "n00dles" || commandArray[1] == n00dlesServ.ip)) {
player.getCurrentServer().isConnectedTo = false;
player.currentServer = n00dlesServ.ip;
player.getCurrentServer().isConnectedTo = true;
this.print("Connected to n00dles");
iTutorialNextStep();
} else {
this.print("Wrong command! Try again!");
return;
}
} else {
this.print("Bad command. Please follow the tutorial");
}
break;
case iTutorialSteps.TerminalAnalyze:
if (commandArray.length === 1 && commandArray[0] === "analyze") {
if (commandArray.length !== 1) {
this.print("Incorrect usage of analyze command. Usage: analyze");
return;
}
this.startAnalyze();
iTutorialNextStep();
} else {
this.print("Bad command. Please follow the tutorial");
}
break;
case iTutorialSteps.TerminalNuke:
if (commandArray.length == 2 && commandArray[0] == "run" && commandArray[1] == "NUKE.exe") {
n00dlesServ.hasAdminRights = true;
this.print("NUKE successful! Gained root access to n00dles");
iTutorialNextStep();
} else {
this.print("Bad command. Please follow the tutorial");
}
break;
case iTutorialSteps.TerminalManualHack:
if (commandArray.length == 1 && commandArray[0] == "hack") {
this.startHack(player);
iTutorialNextStep();
} else {
this.print("Bad command. Please follow the tutorial");
}
break;
case iTutorialSteps.TerminalCreateScript:
if (commandArray.length == 2 && commandArray[0] == "nano" && commandArray[1] == "n00dles.script") {
engine.loadScriptEditorContent("n00dles.script", "");
iTutorialNextStep();
} else {
this.print("Bad command. Please follow the tutorial");
}
break;
case iTutorialSteps.TerminalFree:
if (commandArray.length == 1 && commandArray[0] == "free") {
free(this, engine, player, s, commandArray.slice(1));
iTutorialNextStep();
} else {
this.print("Bad command. Please follow the tutorial");
}
break;
case iTutorialSteps.TerminalRunScript:
if (commandArray.length == 2 && commandArray[0] == "run" && commandArray[1] == "n00dles.script") {
run(this, engine, player, s, commandArray.slice(1));
iTutorialNextStep();
} else {
this.print("Bad command. Please follow the tutorial");
}
break;
case iTutorialSteps.ActiveScriptsToTerminal:
if (commandArray.length == 2 && commandArray[0] == "tail" && commandArray[1] == "n00dles.script") {
// Check that the script exists on this machine
const runningScript = findRunningScript("n00dles.script", [], player.getCurrentServer());
if (runningScript == null) {
this.print("Error: No such script exists");
return;
}
logBoxCreate(runningScript);
iTutorialNextStep();
} else {
this.print("Bad command. Please follow the tutorial");
}
break;
default:
this.print("Please follow the tutorial, or click 'Exit Tutorial' if you'd like to skip it");
return;
}
return;
}
/****************** END INTERACTIVE TUTORIAL ******************/
/* Command parser */
const commandName = commandArray[0];
if (typeof commandName === "number") {
this.error(`Command ${commandArray[0]} not found`);
return;
}
const commands: {
[key: string]: (
terminal: ITerminal,
engine: IEngine,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
) => void;
} = {
alias: alias,
analyze: analyze,
backdoor: backdoor,
buy: buy,
cat: cat,
cd: cd,
check: check,
cls: () => this.clear(),
clear: () => this.clear(),
connect: connect,
download: download,
expr: expr,
free: free,
hack: hack,
help: help,
home: home,
hostname: hostname,
ifconfig: ifconfig,
kill: kill,
killall: killall,
ls: ls,
lscpu: lscpu,
mem: mem,
mv: mv,
nano: nano,
ps: ps,
rm: rm,
run: run,
scan: scan,
"scan-analyze": scananalyze,
scp: scp,
sudov: sudov,
tail: tail,
theme: theme,
top: top,
unalias: unalias,
wget: wget,
};
const f = commands[commandName.toLowerCase()];
if (!f) {
this.error(`Command ${commandArray[0]} not found`);
return;
}
f(this, engine, player, s, commandArray.slice(1));
}
getProgressText(): string {
if (this.action === null) throw new Error("trying to get the progress text when there's no action");
return createProgressBarText({
progress: (this.action.time - this.action.timeLeft) / this.action.time,
totalTicks: 50,
});
}
}

@ -0,0 +1,33 @@
import { ITerminal } from "../ITerminal";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { parseAliasDeclaration, printAliases } from "../../Alias";
export function alias(
terminal: ITerminal,
engine: IEngine,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
if (args.length === 0) {
printAliases();
return;
}
if (args.length === 1) {
if (parseAliasDeclaration(args[0] + "")) {
terminal.print(`Set alias ${args[0]}`);
return;
}
}
if (args.length === 2) {
if (args[0] === "-g") {
if (parseAliasDeclaration(args[1] + "", true)) {
terminal.print(`Set global alias ${args[1]}`);
return;
}
}
}
terminal.error('Incorrect usage of alias command. Usage: alias [-g] [aliasname="value"]');
}

@ -0,0 +1,18 @@
import { ITerminal } from "../ITerminal";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
export function analyze(
terminal: ITerminal,
engine: IEngine,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
if (args.length !== 0) {
terminal.print("Incorrect usage of analyze command. Usage: analyze");
return;
}
terminal.startAnalyze();
}

@ -0,0 +1,41 @@
import { ITerminal } from "../ITerminal";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { Server } from "../../Server/Server";
import { HacknetServer } from "../../Hacknet/HacknetServer";
export function backdoor(
terminal: ITerminal,
engine: IEngine,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
if (args.length !== 0) {
terminal.print("Incorrect usage of backdoor command. Usage: backdoor");
return;
}
if (!(server instanceof Server)) {
terminal.error("Can only backdoor normal servers");
}
const normalServer = server as Server;
if (normalServer.purchasedByPlayer) {
terminal.error(
"Cannot use backdoor on your own machines! You are currently connected to your home PC or one of your purchased servers",
);
} else if (!normalServer.hasAdminRights) {
terminal.error("You do not have admin rights for this machine! Cannot backdoor");
} else if (normalServer.requiredHackingSkill > player.hacking_skill) {
terminal.error(
"Your hacking skill is not high enough to use backdoor on this machine. Try analyzing the machine to determine the required hacking skill",
);
} else if (normalServer instanceof HacknetServer) {
terminal.error("Cannot use backdoor on this type of Server");
} else {
terminal.startBackdoor(player);
}
}

@ -0,0 +1,33 @@
import { ITerminal } from "../ITerminal";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { listAllDarkwebItems, buyDarkwebItem } from "../../DarkWeb/DarkWeb";
import { SpecialServerIps } from "../../Server/SpecialServerIps";
export function buy(
terminal: ITerminal,
engine: IEngine,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
if (!SpecialServerIps.hasOwnProperty("Darkweb Server")) {
terminal.error(
"You need to be able to connect to the Dark Web to use the buy command. (Maybe there's a TOR router you can buy somewhere)",
);
return;
}
if (args.length != 1) {
terminal.print("Incorrect number of arguments. Usage: ");
terminal.print("buy -l");
terminal.print("buy [item name]");
return;
}
const arg = args[0] + "";
if (arg == "-l" || arg == "-1" || arg == "--list") {
listAllDarkwebItems();
} else {
buyDarkwebItem(arg);
}
}

@ -0,0 +1,53 @@
import { ITerminal } from "../ITerminal";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { showMessage } from "../../Message/MessageHelpers";
import { Message } from "../../Message/Message";
import { showLiterature } from "../../Literature/LiteratureHelpers";
export function cat(
terminal: ITerminal,
engine: IEngine,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
if (args.length !== 1) {
terminal.error("Incorrect usage of cat command. Usage: cat [file]");
return;
}
const filename = terminal.getFilepath(args[0] + "");
if (!filename.endsWith(".msg") && !filename.endsWith(".lit") && !filename.endsWith(".txt")) {
terminal.error(
"Only .msg, .txt, and .lit files are viewable with cat (filename must end with .msg, .txt, or .lit)",
);
return;
}
if (filename.endsWith(".msg") || filename.endsWith(".lit")) {
for (let i = 0; i < server.messages.length; ++i) {
if (filename.endsWith(".lit") && server.messages[i] === filename) {
const file = server.messages[i];
if (file instanceof Message) throw new Error(".lit file should not be a .msg");
showLiterature(file);
return;
} else if (filename.endsWith(".msg")) {
const file = server.messages[i];
if (typeof file === "string") throw new Error(".msg file should not be a .lit");
if (file.filename === filename) {
showMessage(file);
return;
}
}
}
} else if (filename.endsWith(".txt")) {
const txt = terminal.getTextFile(player, filename);
if (txt != null) {
txt.show();
return;
}
}
terminal.error(`No such file ${filename}`);
}

@ -0,0 +1,45 @@
import { ITerminal } from "../ITerminal";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { evaluateDirectoryPath, removeTrailingSlash } from "../DirectoryHelpers";
export function cd(
terminal: ITerminal,
engine: IEngine,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
if (args.length > 1) {
terminal.error("Incorrect number of arguments. Usage: cd [dir]");
} else {
let dir = args.length === 1 ? args[0] + "" : "/";
let evaledDir: string | null = "";
if (dir === "/") {
evaledDir = "/";
} else {
// Ignore trailing slashes
dir = removeTrailingSlash(dir);
evaledDir = evaluateDirectoryPath(dir, terminal.cwd());
if (evaledDir === null || evaledDir === "") {
terminal.error("Invalid path. Failed to change directories");
return;
}
const server = player.getCurrentServer();
if (
!server.scripts.some((script) => script.filename.startsWith(evaledDir + "")) &&
!server.textFiles.some((file) => file.fn.startsWith(evaledDir + ""))
) {
terminal.error("Invalid path. Failed to change directories");
return;
}
}
terminal.setcwd(evaledDir);
}
}

@ -0,0 +1,33 @@
import { ITerminal } from "../ITerminal";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { findRunningScript } from "../../Script/ScriptHelpers";
import { isScriptFilename } from "../../Script/ScriptHelpersTS";
export function check(
terminal: ITerminal,
engine: IEngine,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
if (args.length < 1) {
terminal.error("Incorrect number of arguments. Usage: check [script] [arg1] [arg2]...");
} else {
const scriptName = terminal.getFilepath(args[0] + "");
// Can only tail script files
if (!isScriptFilename(scriptName)) {
terminal.error("tail can only be called on .script files (filename must end with .script)");
return;
}
// Check that the script exists on this machine
const runningScript = findRunningScript(scriptName, args.slice(1), server);
if (runningScript == null) {
terminal.error("No such script exists");
return;
}
runningScript.displayLog();
}
}

@ -0,0 +1,32 @@
import { ITerminal } from "../ITerminal";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { getServerOnNetwork } from "../../Server/ServerHelpers";
export function connect(
terminal: ITerminal,
engine: IEngine,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
// Disconnect from current server in terminal and connect to new one
if (args.length !== 1) {
terminal.error("Incorrect usage of connect command. Usage: connect [ip/hostname]");
return;
}
const ip = args[0] + "";
for (let i = 0; i < server.serversOnNetwork.length; i++) {
const other = getServerOnNetwork(server, i);
if (other === null) throw new Error(`Server on network should not be null`);
if (other.ip == ip || other.hostname == ip) {
terminal.connectToServer(player, ip);
return;
}
}
terminal.error("Host not found");
}

@ -0,0 +1,79 @@
import { ITerminal } from "../ITerminal";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { isScriptFilename } from "../../Script/ScriptHelpersTS";
import FileSaver from "file-saver";
import JSZip from "jszip";
export function download(
terminal: ITerminal,
engine: IEngine,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
try {
if (args.length !== 1) {
terminal.error("Incorrect usage of download command. Usage: download [script/text file]");
return;
}
const fn = args[0] + "";
if (fn === "*" || fn === "*.script" || fn === "*.txt") {
// Download all scripts as a zip
const zip = new JSZip();
if (fn === "*" || fn === "*.script") {
for (let i = 0; i < server.scripts.length; ++i) {
const file = new Blob([server.scripts[i].code], {
type: "text/plain",
});
zip.file(server.scripts[i].filename + ".js", file);
}
}
if (fn === "*" || fn === "*.txt") {
for (let i = 0; i < server.textFiles.length; ++i) {
const file = new Blob([server.textFiles[i].text], {
type: "text/plain",
});
zip.file(server.textFiles[i].fn, file);
}
}
let zipFn = "";
switch (fn) {
case "*.script":
zipFn = "bitburnerScripts.zip";
break;
case "*.txt":
zipFn = "bitburnerTexts.zip";
break;
default:
zipFn = "bitburnerFiles.zip";
break;
}
zip.generateAsync({ type: "blob" }).then((content: any) => FileSaver.saveAs(content, zipFn));
return;
} else if (isScriptFilename(fn)) {
// Download a single script
const script = terminal.getScript(player, fn);
if (script != null) {
return script.download();
}
} else if (fn.endsWith(".txt")) {
// Download a single text file
const txt = terminal.getTextFile(player, fn);
if (txt != null) {
return txt.download();
}
} else {
terminal.error(`Cannot download this filetype`);
return;
}
terminal.error(`${fn} does not exist`);
return;
} catch (e) {
terminal.error(e + "");
return;
}
}

@ -0,0 +1,29 @@
import { ITerminal } from "../ITerminal";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
export function expr(
terminal: ITerminal,
engine: IEngine,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
if (args.length === 0) {
terminal.error("Incorrect usage of expr command. Usage: expr [math expression]");
return;
}
const expr = args.join("");
// Sanitize the math expression
const sanitizedExpr = expr.replace(/s+/g, "").replace(/[^-()\d/*+.]/g, "");
let result;
try {
result = eval(sanitizedExpr);
} catch (e) {
terminal.error(`Could not evaluate expression: ${sanitizedExpr}`);
return;
}
terminal.print(result);
}

@ -0,0 +1,29 @@
import { ITerminal } from "../ITerminal";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { numeralWrapper } from "../../ui/numeralFormat";
export function free(
terminal: ITerminal,
engine: IEngine,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
if (args.length !== 0) {
terminal.error("Incorrect usage of free command. Usage: free");
return;
}
const ram = numeralWrapper.formatRAM(player.getCurrentServer().maxRam);
const used = numeralWrapper.formatRAM(player.getCurrentServer().ramUsed);
const avail = numeralWrapper.formatRAM(player.getCurrentServer().maxRam - player.getCurrentServer().ramUsed);
const maxLength = Math.max(ram.length, Math.max(used.length, avail.length));
const usedPercent = numeralWrapper.formatPercentage(
player.getCurrentServer().ramUsed / player.getCurrentServer().maxRam,
);
terminal.print(`Total: ${" ".repeat(maxLength - ram.length)}${ram}`);
terminal.print(`Used: ${" ".repeat(maxLength - used.length)}${used} (${usedPercent})`);
terminal.print(`Available: ${" ".repeat(maxLength - avail.length)}${avail}`);
}

@ -0,0 +1,44 @@
import { ITerminal } from "../ITerminal";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { Server } from "../../Server/Server";
export function hack(
terminal: ITerminal,
engine: IEngine,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
if (args.length !== 0) {
terminal.error("Incorrect usage of hack command. Usage: hack");
return;
}
if (!(server instanceof Server)) {
terminal.error(
"Cannot hack your own machines! You are currently connected to your home PC or one of your purchased servers",
);
}
const normalServer = server as Server;
// Hack the current PC (usually for money)
// You can't hack your home pc or servers you purchased
if (normalServer.purchasedByPlayer) {
terminal.error(
"Cannot hack your own machines! You are currently connected to your home PC or one of your purchased servers",
);
return;
}
if (!normalServer.hasAdminRights) {
terminal.error("You do not have admin rights for this machine! Cannot hack");
return;
}
if (normalServer.requiredHackingSkill > player.hacking_skill) {
terminal.error(
"Your hacking skill is not high enough to attempt hacking this machine. Try analyzing the machine to determine the required hacking skill",
);
return;
}
terminal.startHack(player);
}

@ -0,0 +1,29 @@
import { ITerminal } from "../ITerminal";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { TerminalHelpText, HelpTexts } from "../HelpText";
export function help(
terminal: ITerminal,
engine: IEngine,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
if (args.length !== 0 && args.length !== 1) {
terminal.error("Incorrect usage of help command. Usage: help");
return;
}
if (args.length === 0) {
TerminalHelpText.forEach((line) => terminal.print(line));
} else {
const cmd = args[0];
const txt = HelpTexts[cmd];
if (txt == null) {
terminal.error("No help topics match '" + cmd + "'");
return;
}
txt.forEach((t) => terminal.print(t));
}
}

@ -0,0 +1,22 @@
import { ITerminal } from "../ITerminal";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
export function home(
terminal: ITerminal,
engine: IEngine,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
if (args.length !== 0) {
terminal.error("Incorrect usage of home command. Usage: home");
return;
}
player.getCurrentServer().isConnectedTo = false;
player.currentServer = player.getHomeComputer().ip;
player.getCurrentServer().isConnectedTo = true;
terminal.print("Connected to home");
terminal.setcwd("/");
}

@ -0,0 +1,18 @@
import { ITerminal } from "../ITerminal";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
export function hostname(
terminal: ITerminal,
engine: IEngine,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
if (args.length !== 0) {
terminal.error("Incorrect usage of hostname command. Usage: hostname");
return;
}
terminal.print(player.getCurrentServer().hostname);
}

@ -0,0 +1,18 @@
import { ITerminal } from "../ITerminal";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
export function ifconfig(
terminal: ITerminal,
engine: IEngine,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
if (args.length !== 0) {
terminal.error("Incorrect usage of ifconfig command. Usage: ifconfig");
return;
}
terminal.print(player.getCurrentServer().ip);
}

@ -0,0 +1,44 @@
import { ITerminal } from "../ITerminal";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { killWorkerScript } from "../../Netscript/killWorkerScript";
export function kill(
terminal: ITerminal,
engine: IEngine,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
try {
if (args.length < 1) {
terminal.error("Incorrect usage of kill command. Usage: kill [scriptname] [arg1] [arg2]...");
return;
}
// Kill by PID
if (typeof args[0] === "number") {
const pid = args[0];
const res = killWorkerScript(pid);
if (res) {
terminal.print(`Killing script with PID ${pid}`);
} else {
terminal.print(`Failed to kill script with PID ${pid}. No such script exists`);
}
return;
}
const scriptName = terminal.getFilepath(args[0]);
const runningScript = server.getRunningScript(scriptName, args.slice(1));
if (runningScript == null) {
terminal.error("No such script is running. Nothing to kill");
return;
}
killWorkerScript(runningScript, server.ip, false);
terminal.print(`Killing ${scriptName}`);
} catch (e) {
terminal.error(e + "");
}
}

@ -0,0 +1,14 @@
import { ITerminal } from "../ITerminal";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { killWorkerScript } from "../../Netscript/killWorkerScript";
import { WorkerScriptStartStopEventEmitter } from "../../Netscript/WorkerScriptStartStopEventEmitter";
export function killall(terminal: ITerminal, engine: IEngine, player: IPlayer, server: BaseServer): void {
for (let i = server.runningScripts.length - 1; i >= 0; --i) {
killWorkerScript(server.runningScripts[i], server.ip, false);
}
WorkerScriptStartStopEventEmitter.emitEvent();
terminal.print("Killing all running scripts");
}

@ -0,0 +1,148 @@
import { ITerminal } from "../ITerminal";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { Message } from "../../Message/Message";
import { getFirstParentDirectory, isValidDirectoryPath, evaluateDirectoryPath } from "../../Terminal/DirectoryHelpers";
export function ls(
terminal: ITerminal,
engine: IEngine,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
const numArgs = args.length;
function incorrectUsage(): void {
terminal.error("Incorrect usage of ls command. Usage: ls [dir] [| grep pattern]");
}
if (numArgs > 5 || numArgs === 3) {
return incorrectUsage();
}
// Grep
let filter = ""; // Grep
// Directory path
let prefix = terminal.cwd();
if (!prefix.endsWith("/")) {
prefix += "/";
}
// If there are 4+ arguments, then the last 3 must be for grep
if (numArgs >= 4) {
if (args[numArgs - 1] !== "grep" || args[numArgs - 2] !== "|") {
return incorrectUsage();
}
filter = args[numArgs] + "";
}
// If the second argument is not a pipe, then it must be for listing a directory
if (numArgs >= 2 && args[0] !== "|") {
const newPath = evaluateDirectoryPath(args[0] + "", terminal.cwd());
prefix = newPath ? newPath : "";
if (prefix != null) {
if (!prefix.endsWith("/")) {
prefix += "/";
}
if (!isValidDirectoryPath(prefix)) {
return incorrectUsage();
}
}
}
// Root directory, which is the same as no 'prefix' at all
if (prefix === "/") {
prefix = "";
}
// Display all programs and scripts
const allPrograms: string[] = [];
const allScripts: string[] = [];
const allTextFiles: string[] = [];
const allContracts: string[] = [];
const allMessages: string[] = [];
const folders: string[] = [];
function handleFn(fn: string, dest: string[]): void {
let parsedFn = fn;
if (prefix) {
if (!fn.startsWith(prefix)) {
return;
} else {
parsedFn = fn.slice(prefix.length, fn.length);
}
}
if (filter && !parsedFn.includes(filter)) {
return;
}
// If the fn includes a forward slash, it must be in a subdirectory.
// Therefore, we only list the "first" directory in its path
if (parsedFn.includes("/")) {
const firstParentDir = getFirstParentDirectory(parsedFn);
if (filter && !firstParentDir.includes(filter)) {
return;
}
if (!folders.includes(firstParentDir)) {
folders.push(firstParentDir);
}
return;
}
dest.push(parsedFn);
}
// Get all of the programs and scripts on the machine into one temporary array
const s = player.getCurrentServer();
for (const program of s.programs) handleFn(program, allPrograms);
for (const script of s.scripts) handleFn(script.filename, allScripts);
for (const txt of s.textFiles) handleFn(txt.fn, allTextFiles);
for (const contract of s.contracts) handleFn(contract.fn, allContracts);
for (const msgOrLit of s.messages)
msgOrLit instanceof Message ? handleFn(msgOrLit.filename, allMessages) : handleFn(msgOrLit, allMessages);
// Sort the files/folders alphabetically then print each
allPrograms.sort();
allScripts.sort();
allTextFiles.sort();
allContracts.sort();
allMessages.sort();
folders.sort();
function postSegments(segments: string[]): void {
const maxLength = Math.max(...segments.map((s) => s.length)) + 1;
const filesPerRow = Math.floor(80 / maxLength);
for (let i = 0; i < segments.length; i++) {
let row = "";
for (let col = 0; col < filesPerRow; col++) {
if (!(i < segments.length)) break;
row += segments[i];
row += " ".repeat(maxLength * (col + 1) - row.length);
i++;
}
i--;
terminal.print(row);
}
}
const groups = [
{ segments: folders },
{ segments: allMessages },
{ segments: allTextFiles },
{ segments: allPrograms },
{ segments: allContracts },
{ segments: allScripts },
].filter((g) => g.segments.length > 0);
for (let i = 0; i < groups.length; i++) {
if (i !== 0) {
terminal.print("");
terminal.print("");
}
postSegments(groups[i].segments);
}
}

@ -0,0 +1,7 @@
import { ITerminal } from "../ITerminal";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
export function lscpu(terminal: ITerminal, engine: IEngine, player: IPlayer): void {
terminal.print(player.getCurrentServer().cpuCores + " Core(s)");
}

@ -0,0 +1,44 @@
import { ITerminal } from "../ITerminal";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { numeralWrapper } from "../../ui/numeralFormat";
export function mem(
terminal: ITerminal,
engine: IEngine,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
try {
if (args.length !== 1 && args.length !== 3) {
terminal.error("Incorrect usage of mem command. usage: mem [scriptname] [-t] [number threads]");
return;
}
const scriptName = args[0] + "";
let numThreads = 1;
if (args.length === 3 && args[1] === "-t") {
numThreads = Math.round(parseInt(args[2] + ""));
if (isNaN(numThreads) || numThreads < 1) {
terminal.error("Invalid number of threads specified. Number of threads must be greater than 1");
return;
}
}
const script = terminal.getScript(player, scriptName);
if (script == null) {
terminal.error("No such script exists!");
return;
}
const ramUsage = script.ramUsage * numThreads;
terminal.print(
`This script requires ${numeralWrapper.formatRAM(ramUsage)} of RAM to run for ${numThreads} thread(s)`,
);
} catch (e) {
terminal.error(e + "");
}
}

@ -0,0 +1,91 @@
import { ITerminal } from "../ITerminal";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { isScriptFilename } from "../../Script/ScriptHelpersTS";
import { TextFile } from "../../TextFile";
import { Script } from "../../Script/Script";
export function mv(
terminal: ITerminal,
engine: IEngine,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
if (args.length !== 2) {
terminal.error(`Incorrect number of arguments. Usage: mv [src] [dest]`);
return;
}
try {
const source = args[0] + "";
const dest = args[1] + "";
if (!isScriptFilename(source) && !source.endsWith(".txt")) {
terminal.error(`'mv' can only be used on scripts and text files (.txt)`);
return;
}
const srcFile = terminal.getFile(player, source);
if (srcFile == null) {
terminal.error(`Source file ${source} does not exist`);
return;
}
const sourcePath = terminal.getFilepath(source);
const destPath = terminal.getFilepath(dest);
const destFile = terminal.getFile(player, dest);
// 'mv' command only works on scripts and txt files.
// Also, you can't convert between different file types
if (isScriptFilename(source)) {
const script = srcFile as Script;
if (!isScriptFilename(dest)) {
terminal.error(`Source and destination files must have the same type`);
return;
}
// Command doesnt work if script is running
if (server.isRunning(sourcePath)) {
terminal.error(`Cannot use 'mv' on a script that is running`);
return;
}
if (destFile != null) {
// Already exists, will be overwritten, so we'll delete it
const status = server.removeFile(destPath);
if (!status.res) {
terminal.error(`Something went wrong...please contact game dev (probably a bug)`);
return;
} else {
terminal.print("Warning: The destination file was overwritten");
}
}
script.filename = destPath;
} else if (srcFile instanceof TextFile) {
const textFile = srcFile as TextFile;
if (!dest.endsWith(".txt")) {
terminal.error(`Source and destination files must have the same type`);
return;
}
if (destFile != null) {
// Already exists, will be overwritten, so we'll delete it
const status = server.removeFile(destPath);
if (!status.res) {
terminal.error(`Something went wrong...please contact game dev (probably a bug)`);
return;
} else {
terminal.print("Warning: The destination file was overwritten");
}
}
textFile.fn = destPath;
}
} catch (e) {
terminal.error(e + "");
}
}

@ -0,0 +1,57 @@
import { ITerminal } from "../ITerminal";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { isScriptFilename } from "../../Script/ScriptHelpersTS";
import { createFconf } from "../../Fconf/Fconf";
export function nano(
terminal: ITerminal,
engine: IEngine,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
if (args.length !== 1) {
terminal.error("Incorrect usage of nano command. Usage: nano [scriptname]");
return;
}
try {
const filename = args[0] + "";
if (filename === ".fconf") {
const text = createFconf();
engine.loadScriptEditorContent(filename, text);
return;
} else if (isScriptFilename(filename)) {
const filepath = terminal.getFilepath(filename);
const script = terminal.getScript(player, filename);
if (script == null) {
let code = "";
if (filename.endsWith(".ns") || filename.endsWith(".js")) {
code = `export async function main(ns) {
}`;
}
engine.loadScriptEditorContent(filepath, code);
} else {
engine.loadScriptEditorContent(filepath, script.code);
}
} else if (filename.endsWith(".txt")) {
const filepath = terminal.getFilepath(filename);
const txt = terminal.getTextFile(player, filename);
if (txt == null) {
engine.loadScriptEditorContent(filepath);
} else {
engine.loadScriptEditorContent(filepath, txt.text);
}
} else {
terminal.error(
"Invalid file. Only scripts (.script, .ns, .js), text files (.txt), or .fconf can be edited with nano",
);
return;
}
} catch (e) {
terminal.error(e + "");
}
}

@ -0,0 +1,25 @@
import { ITerminal } from "../ITerminal";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
export function ps(
terminal: ITerminal,
engine: IEngine,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
if (args.length !== 0) {
terminal.error("Incorrect usage of ps command. Usage: ps");
return;
}
for (let i = 0; i < server.runningScripts.length; i++) {
const rsObj = server.runningScripts[i];
let res = `(PID - ${rsObj.pid}) ${rsObj.filename}`;
for (let j = 0; j < rsObj.args.length; ++j) {
res += " " + rsObj.args[j].toString();
}
terminal.print(res);
}
}

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