Merge pull request #2154 from danielyxie/dev

bugfix
This commit is contained in:
hydroflame 2021-12-23 11:16:56 -05:00 committed by GitHub
commit 1e21689e95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
88 changed files with 3489 additions and 1708 deletions

@ -1,7 +1,27 @@
node_modules/
doc/build/
dist/
tests/*.bundle.*
input/
.dist
.tmp
.package
assets/
css/
.cypress/
cypress/
doc/
markdown/
netscript_tests/
scripts/
electron/lib
electron/greenworks.js
src/ThirdParty/*
src/JSInterpreter.js
main.bundle.js
test/*.bundle.*
editor.main.js
main.bundle.js
webpack.config.js
webpack.config-test.js

55
.github/workflows/ci.yml vendored Normal file

@ -0,0 +1,55 @@
name: CI
on:
# Triggers the workflow on push or pull request events but only for the dev branch
push:
branches: [ dev ]
pull_request:
branches: [ dev ]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 16.13.1
uses: actions/setup-node@v2
with:
node-version: 16.13.1
cache: 'npm'
- name: Install npm dependencies
run: npm ci
- name: Build the production app
run: npm run build
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 16.13.1
uses: actions/setup-node@v2
with:
node-version: 16.13.1
cache: 'npm'
- name: Install npm dependencies
run: npm ci
- name: Run linter
run: npm run lint:report
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 16.13.1
uses: actions/setup-node@v2
with:
node-version: 16.13.1
cache: 'npm'
- name: Install npm dependencies
run: npm ci
- name: Run linter
run: npm run test

1
.nojekyll Normal file

@ -0,0 +1 @@

7
dist/ext/monaco-vim.js vendored Normal file

File diff suppressed because one or more lines are too long

66
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

@ -3,7 +3,8 @@
Netscript Basic Functions
=========================
This page contains the complete documentation for all functions that are available in Netscript.
This page contains a subset of functions that are available in Bitburner.
For the complete list see https://github.com/danielyxie/bitburner/tree/dev/markdown
This includes information such as function signatures, what they do, and their return values.
.. toctree::

@ -1,16 +1,45 @@
const { app, BrowserWindow, Menu, shell } = require("electron");
/* eslint-disable no-process-exit */
/* eslint-disable @typescript-eslint/no-var-requires */
const { app, BrowserWindow, Menu, shell, dialog } = require("electron");
const log = require('electron-log');
const greenworks = require("./greenworks");
log.catchErrors();
log.info(`Started app: ${JSON.stringify(process.argv)}`);
process.on('uncaughtException', function () {
// The exception will already have been logged by electron-log
process.exit(1);
});
if (greenworks.init()) {
console.log("Steam API has been initialized.");
log.info("Steam API has been initialized.");
} else {
console.log("Steam API has failed to initialize.");
log.warn("Steam API has failed to initialize.");
}
const debug = false;
let win = null;
require("http")
.createServer(async function (req, res) {
let body = "";
req.on("data", (chunk) => {
body += chunk.toString(); // convert Buffer to string
});
req.on("end", () => {
const data = JSON.parse(body);
win.webContents.executeJavaScript(`document.saveFile("${data.filename}", "${data.code}")`).then((result) => {
res.write(result);
res.end();
});
});
})
.listen(9990, "127.0.0.1");
function createWindow(killall) {
const win = new BrowserWindow({
win = new BrowserWindow({
show: false,
backgroundThrottling: false,
backgroundColor: "#000000",
@ -40,7 +69,6 @@ function createWindow(killall) {
const achievements = greenworks.getAchievementNames();
const intervalID = setInterval(async () => {
const achs = await win.webContents.executeJavaScript("document.achievements");
console.log(achs);
for (const ach of achs) {
if (!achievements.includes(ach)) continue;
greenworks.activateAchievement(ach, () => undefined);
@ -48,6 +76,37 @@ function createWindow(killall) {
}, 1000);
win.achievementsIntervalID = intervalID;
const reloadAndKill = (killScripts = true) => {
log.info('Reloading & Killing all scripts...');
setStopProcessHandler(app, win, false);
if (intervalID) clearInterval(intervalID);
win.webContents.forcefullyCrashRenderer();
win.close();
createWindow(killScripts);
};
const promptForReload = () => {
win.off('unresponsive', promptForReload);
dialog.showMessageBox({
type: 'error',
title: 'Bitburner > Application Unresponsive',
message: 'The application is unresponsive, possibly due to an infinite loop in your scripts.',
detail:' Did you forget a ns.sleep(x)?\n\n' +
'The application will be restarted for you, do you want to kill all running scripts?',
buttons: ['Restart', 'Cancel'],
defaultId: 0,
checkboxLabel: 'Kill all running scripts',
checkboxChecked: true,
noLink: true,
}).then(({response, checkboxChecked}) => {
if (response === 0) {
reloadAndKill(checkboxChecked);
} else {
win.on('unresponsive', promptForReload)
}
});
}
win.on('unresponsive', promptForReload);
// Create the Application's main menu
Menu.setApplicationMenu(
Menu.buildFromTemplate([
@ -75,13 +134,7 @@ function createWindow(killall) {
},
{
label: "reload & kill all scripts",
click: () => {
setStopProcessHandler(app, win, false);
if (intervalID) clearInterval(intervalID);
win.webContents.forcefullyCrashRenderer();
win.close();
createWindow(true);
},
click: reloadAndKill
},
],
},
@ -125,10 +178,11 @@ function setStopProcessHandler(app, window, enabled) {
};
const stopProcessHandler = () => {
if (process.platform !== "darwin") {
app.quit();
process.exit(0);
}
log.info('Quitting the app...');
app.isQuiting = true;
app.quit();
// eslint-disable-next-line no-process-exit
process.exit(0);
};
if (enabled) {
@ -141,6 +195,7 @@ function setStopProcessHandler(app, window, enabled) {
}
app.whenReady().then(() => {
log.info('Application is ready!');
const win = createWindow(process.argv.includes("--no-scripts"));
setStopProcessHandler(app, win, true);
});

@ -17,12 +17,13 @@
<link rel="stylesheet" data-name="vs/editor/editor.main" href="dist/ext/monaco-editor/min/vs/editor/editor.main.css"/>
<script>
var require = { paths: { vs: "dist/ext/monaco-editor/min/vs" } };
var require = { paths: { vs: "dist/ext/monaco-editor/min/vs", "monaco-vim": "dist/ext/monaco-vim" } };
</script>
<script src="dist/ext/monaco-editor/min/vs/loader.js"></script>
<script src="dist/ext/monaco-editor/min/vs/editor/editor.main.nls.js"></script>
<script src="dist/ext/monaco-editor/min/vs/editor/editor.main.js"></script>
<script src="dist/ext/monaco-vim.js"></script>
<!-- Google Analytics -->
<script>

@ -4,6 +4,8 @@ module.exports = {
transform: {
"^.+\\.(js|jsx|ts|tsx)$": "babel-jest",
},
// testMatch: ["**/?(*.)+(test).[jt]s?(x)"],
testPathIgnorePatterns: [
'.cypress', 'node_modules', 'dist',
],
testEnvironment: "jsdom",
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -66,6 +66,7 @@
| [StockOrder](./bitburner.stockorder.md) | Return value of [getOrders](./bitburner.tix.getorders.md) |
| [StockOrderObject](./bitburner.stockorderobject.md) | Value in map of [StockOrder](./bitburner.stockorder.md) |
| [TIX](./bitburner.tix.md) | Stock market API |
| [UserInterface](./bitburner.userinterface.md) | User Interface API. |
| [Warehouse](./bitburner.warehouse.md) | Warehouse for a division in a city |
| [WarehouseAPI](./bitburner.warehouseapi.md) | Corporation Warehouse API |

@ -51,6 +51,7 @@ export async function main(ns) {
| [sleeve](./bitburner.ns.sleeve.md) | [Sleeve](./bitburner.sleeve.md) | Namespace for sleeve functions. |
| [stanek](./bitburner.ns.stanek.md) | [Stanek](./bitburner.stanek.md) | Namespace for stanek functions. RAM cost: 0 GB |
| [stock](./bitburner.ns.stock.md) | [TIX](./bitburner.tix.md) | Namespace for stock functions. |
| [ui](./bitburner.ns.ui.md) | [UserInterface](./bitburner.userinterface.md) | Namespace for user interface functions. RAM cost: 0 GB |
## Methods

@ -31,9 +31,9 @@ The hostname of the newly purchased server.
Purchased a server with the specified hostname and amount of RAM.
The hostname argument can be any data type, but it will be converted to a string and have whitespace removed. Anything that resolves to an empty string will cause the function to fail. If there is already a server with the specified hostname, then the function will automatically append a number at the end of the hostname argument value until it finds a unique hostname. For example, if the script calls `purchaseServer(“foo”, 4)` but a server named “foo” already exists, the it will automatically change the hostname to `foo-0`<!-- -->. If there is already a server with the hostname `foo-0`<!-- -->, then it will change the hostname to `foo-1`<!-- -->, and so on.
The hostname argument can be any data type, but it will be converted to a string and have whitespace removed. Anything that resolves to an empty string will cause the function to fail. If there is already a server with the specified hostname, then the function will automatically append a number at the end of the hostname argument value until it finds a unique hostname. For example, if the script calls `purchaseServer(“foo”, 4)` but a server named “foo” already exists, it will automatically change the hostname to `foo-0`<!-- -->. If there is already a server with the hostname `foo-0`<!-- -->, then it will change the hostname to `foo-1`<!-- -->, and so on.
Note that there is a maximum limit to the amount of servers you can purchase.
Note that there is a maximum limit to the number of servers you can purchase.
Returns the hostname of the newly purchased server as a string. If the function fails to purchase a server, then it will return an empty string. The function will fail if the arguments passed in are invalid, if the player does not have enough money to purchase the specified server, or if the player has exceeded the maximum amount of servers.

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [NS](./bitburner.ns.md) &gt; [ui](./bitburner.ns.ui.md)
## NS.ui property
Namespace for user interface functions. RAM cost: 0 GB
<b>Signature:</b>
```typescript
readonly ui: UserInterface;
```

@ -0,0 +1,23 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [UserInterface](./bitburner.userinterface.md) &gt; [getTheme](./bitburner.userinterface.gettheme.md)
## UserInterface.getTheme() method
Get the current theme
<b>Signature:</b>
```typescript
getTheme(): UserInterfaceTheme;
```
<b>Returns:</b>
UserInterfaceTheme
An object containing the theme's colors
## Remarks
RAM cost: cost: 0 GB

@ -0,0 +1,20 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [UserInterface](./bitburner.userinterface.md)
## UserInterface interface
User Interface API.
<b>Signature:</b>
```typescript
interface UserInterface
```
## Methods
| Method | Description |
| --- | --- |
| [getTheme()](./bitburner.userinterface.gettheme.md) | Get the current theme |

221
package-lock.json generated

@ -1,12 +1,11 @@
{
"name": "bitburner",
"version": "1.1.0",
"version": "1.2.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "bitburner",
"version": "1.1.0",
"version": "1.2.0",
"hasInstallScript": true,
"license": "SEE LICENSE IN license.txt",
"dependencies": {
@ -22,6 +21,7 @@
"@types/escodegen": "^0.0.7",
"@types/numeral": "0.0.25",
"@types/react": "^17.0.21",
"@types/react-beautiful-dnd": "^13.1.2",
"@types/react-dom": "^17.0.9",
"@types/react-resizable": "^1.7.3",
"acorn": "^8.4.1",
@ -41,6 +41,7 @@
"numeral": "2.0.6",
"raw-loader": "^4.0.2",
"react": "^17.0.2",
"react-beautiful-dnd": "^13.1.0",
"react-dom": "^17.0.2",
"react-draggable": "^4.4.4",
"react-resizable": "^3.0.4",
@ -61,6 +62,7 @@
"babel-loader": "^8.0.5",
"cypress": "^8.3.1",
"electron": "^14.0.2",
"electron-log": "^4.4.3",
"electron-packager": "^15.4.0",
"eslint": "^7.24.0",
"fork-ts-checker-webpack-plugin": "^6.3.3",
@ -3984,6 +3986,15 @@
"@types/node": "*"
}
},
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
"dependencies": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
@ -4062,6 +4073,14 @@
"csstype": "^3.0.2"
}
},
"node_modules/@types/react-beautiful-dnd": {
"version": "13.1.2",
"resolved": "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.2.tgz",
"integrity": "sha512-+OvPkB8CdE/bGdXKyIhc/Lm2U7UAYCCJgsqmopFmh9gbAudmslkI8eOrPDjg4JhwSE6wytz4a3/wRjKtovHVJg==",
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/react-dom": {
"version": "17.0.9",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.9.tgz",
@ -4078,6 +4097,17 @@
"@types/react": "*"
}
},
"node_modules/@types/react-redux": {
"version": "7.1.20",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.20.tgz",
"integrity": "sha512-q42es4c8iIeTgcnB+yJgRTTzftv3eYYvCZOh1Ckn2eX/3o5TdsQYKUWpLoLuGlcY/p+VAhV9IOEZJcWk/vfkXw==",
"dependencies": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0",
"redux": "^4.0.0"
}
},
"node_modules/@types/react-resizable": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/@types/react-resizable/-/react-resizable-1.7.4.tgz",
@ -6659,6 +6689,14 @@
"node": "*"
}
},
"node_modules/css-box-model": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
"integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==",
"dependencies": {
"tiny-invariant": "^1.0.6"
}
},
"node_modules/css-select": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz",
@ -7592,6 +7630,12 @@
"node": ">= 8.6"
}
},
"node_modules/electron-log": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/electron-log/-/electron-log-4.4.3.tgz",
"integrity": "sha512-IWxkiVLSpbI4if61kTSLMErYwz+Jq/gnHeTtQ8jcAjtlU8rgTIScWBgZJxk3fVnyvW6M+Ci3Bn9ogHgjgDSvNg==",
"dev": true
},
"node_modules/electron-notarize": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/electron-notarize/-/electron-notarize-1.1.1.tgz",
@ -14566,6 +14610,11 @@
"node": ">= 4.0.0"
}
},
"node_modules/memoize-one": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
},
"node_modules/memory-fs": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
@ -16303,6 +16352,11 @@
}
]
},
"node_modules/raf-schd": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
"integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ=="
},
"node_modules/ramda": {
"version": "0.27.1",
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz",
@ -16429,6 +16483,24 @@
"node": ">=0.10.0"
}
},
"node_modules/react-beautiful-dnd": {
"version": "13.1.0",
"resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.0.tgz",
"integrity": "sha512-aGvblPZTJowOWUNiwd6tNfEpgkX5OxmpqxHKNW/4VmvZTNTbeiq7bA3bn5T+QSF2uibXB0D1DmJsb1aC/+3cUA==",
"dependencies": {
"@babel/runtime": "^7.9.2",
"css-box-model": "^1.2.0",
"memoize-one": "^5.1.1",
"raf-schd": "^4.0.2",
"react-redux": "^7.2.0",
"redux": "^4.0.4",
"use-memo-one": "^1.1.1"
},
"peerDependencies": {
"react": "^16.8.5 || ^17.0.0",
"react-dom": "^16.8.5 || ^17.0.0"
}
},
"node_modules/react-dom": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
@ -16460,6 +16532,30 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
},
"node_modules/react-redux": {
"version": "7.2.6",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.6.tgz",
"integrity": "sha512-10RPdsz0UUrRL1NZE0ejTkucnclYSgXp5q+tB5SWx2qeG2ZJQJyymgAhwKy73yiL/13btfB6fPr+rgbMAaZIAQ==",
"dependencies": {
"@babel/runtime": "^7.15.4",
"@types/react-redux": "^7.1.20",
"hoist-non-react-statics": "^3.3.2",
"loose-envify": "^1.4.0",
"prop-types": "^15.7.2",
"react-is": "^17.0.2"
},
"peerDependencies": {
"react": "^16.8.3 || ^17"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
}
}
},
"node_modules/react-refresh": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.10.0.tgz",
@ -16625,6 +16721,14 @@
"node": ">=8.10.0"
}
},
"node_modules/redux": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.1.2.tgz",
"integrity": "sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==",
"dependencies": {
"@babel/runtime": "^7.9.2"
}
},
"node_modules/regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@ -19143,6 +19247,11 @@
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
},
"node_modules/tiny-invariant": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
"integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg=="
},
"node_modules/tiny-warning": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
@ -19717,6 +19826,14 @@
"node": ">=0.10.0"
}
},
"node_modules/use-memo-one": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.2.tgz",
"integrity": "sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0"
}
},
"node_modules/util": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
@ -24597,6 +24714,15 @@
"@types/node": "*"
}
},
"@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
"requires": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"@types/istanbul-lib-coverage": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
@ -24675,6 +24801,14 @@
"csstype": "^3.0.2"
}
},
"@types/react-beautiful-dnd": {
"version": "13.1.2",
"resolved": "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.2.tgz",
"integrity": "sha512-+OvPkB8CdE/bGdXKyIhc/Lm2U7UAYCCJgsqmopFmh9gbAudmslkI8eOrPDjg4JhwSE6wytz4a3/wRjKtovHVJg==",
"requires": {
"@types/react": "*"
}
},
"@types/react-dom": {
"version": "17.0.9",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.9.tgz",
@ -24691,6 +24825,17 @@
"@types/react": "*"
}
},
"@types/react-redux": {
"version": "7.1.20",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.20.tgz",
"integrity": "sha512-q42es4c8iIeTgcnB+yJgRTTzftv3eYYvCZOh1Ckn2eX/3o5TdsQYKUWpLoLuGlcY/p+VAhV9IOEZJcWk/vfkXw==",
"requires": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0",
"redux": "^4.0.0"
}
},
"@types/react-resizable": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/@types/react-resizable/-/react-resizable-1.7.4.tgz",
@ -26762,6 +26907,14 @@
"randomfill": "^1.0.3"
}
},
"css-box-model": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
"integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==",
"requires": {
"tiny-invariant": "^1.0.6"
}
},
"css-select": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz",
@ -27550,6 +27703,12 @@
}
}
},
"electron-log": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/electron-log/-/electron-log-4.4.3.tgz",
"integrity": "sha512-IWxkiVLSpbI4if61kTSLMErYwz+Jq/gnHeTtQ8jcAjtlU8rgTIScWBgZJxk3fVnyvW6M+Ci3Bn9ogHgjgDSvNg==",
"dev": true
},
"electron-notarize": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/electron-notarize/-/electron-notarize-1.1.1.tgz",
@ -32913,6 +33072,11 @@
"fs-monkey": "1.0.3"
}
},
"memoize-one": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
},
"memory-fs": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
@ -34288,6 +34452,11 @@
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"dev": true
},
"raf-schd": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
"integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ=="
},
"ramda": {
"version": "0.27.1",
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz",
@ -34384,6 +34553,20 @@
"object-assign": "^4.1.1"
}
},
"react-beautiful-dnd": {
"version": "13.1.0",
"resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.0.tgz",
"integrity": "sha512-aGvblPZTJowOWUNiwd6tNfEpgkX5OxmpqxHKNW/4VmvZTNTbeiq7bA3bn5T+QSF2uibXB0D1DmJsb1aC/+3cUA==",
"requires": {
"@babel/runtime": "^7.9.2",
"css-box-model": "^1.2.0",
"memoize-one": "^5.1.1",
"raf-schd": "^4.0.2",
"react-redux": "^7.2.0",
"redux": "^4.0.4",
"use-memo-one": "^1.1.1"
}
},
"react-dom": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
@ -34408,6 +34591,19 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
},
"react-redux": {
"version": "7.2.6",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.6.tgz",
"integrity": "sha512-10RPdsz0UUrRL1NZE0ejTkucnclYSgXp5q+tB5SWx2qeG2ZJQJyymgAhwKy73yiL/13btfB6fPr+rgbMAaZIAQ==",
"requires": {
"@babel/runtime": "^7.15.4",
"@types/react-redux": "^7.1.20",
"hoist-non-react-statics": "^3.3.2",
"loose-envify": "^1.4.0",
"prop-types": "^15.7.2",
"react-is": "^17.0.2"
}
},
"react-refresh": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.10.0.tgz",
@ -34537,6 +34733,14 @@
"picomatch": "^2.2.1"
}
},
"redux": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.1.2.tgz",
"integrity": "sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==",
"requires": {
"@babel/runtime": "^7.9.2"
}
},
"regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@ -36569,6 +36773,11 @@
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
},
"tiny-invariant": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
"integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg=="
},
"tiny-warning": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
@ -37025,6 +37234,12 @@
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ=="
},
"use-memo-one": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.2.tgz",
"integrity": "sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==",
"requires": {}
},
"util": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",

@ -22,6 +22,7 @@
"@types/escodegen": "^0.0.7",
"@types/numeral": "0.0.25",
"@types/react": "^17.0.21",
"@types/react-beautiful-dnd": "^13.1.2",
"@types/react-dom": "^17.0.9",
"@types/react-resizable": "^1.7.3",
"acorn": "^8.4.1",
@ -41,6 +42,7 @@
"numeral": "2.0.6",
"raw-loader": "^4.0.2",
"react": "^17.0.2",
"react-beautiful-dnd": "^13.1.0",
"react-dom": "^17.0.2",
"react-draggable": "^4.4.4",
"react-resizable": "^3.0.4",
@ -62,6 +64,7 @@
"babel-loader": "^8.0.5",
"cypress": "^8.3.1",
"electron": "^14.0.2",
"electron-log": "^4.4.3",
"electron-packager": "^15.4.0",
"eslint": "^7.24.0",
"fork-ts-checker-webpack-plugin": "^6.3.3",
@ -104,12 +107,14 @@
"build": "webpack --mode production",
"build:dev": "webpack --mode development",
"lint": "eslint --fix . --ext js,jsx,ts,tsx",
"lint:report": "eslint --ext js,jsx,ts,tsx .",
"preinstall": "node ./scripts/engines-check.js",
"test": "jest",
"test:watch": "jest --watch",
"watch": "webpack --watch --mode production",
"watch:dev": "webpack --watch --mode development",
"electron": "cp -r electron/* .package && cp index.html .package && cp main.bundle.js .package && cp dist/vendor.bundle.js .package/dist/ && cp -r dist/ext .package/dist/ && electron-packager .package bitburner --all --out .build --overwrite --icon .package/icon.png",
"electron": "sh ./package.sh",
"electron:packager": "electron-packager .package bitburner --all --out .build --overwrite --icon .package/icon.png --no-prune",
"allbuild": "npm run build && npm run electron && git add --all && git commit --amend --no-edit && git push -f -u origin dev"
}
}

@ -1,11 +1,13 @@
# npm install electron --save-dev
# npm install electron-packager --save-dev
#!/bin/sh
mkdir -p .package/dist/src/ThirdParty || true
mkdir -p .package/src/ThirdParty || true
mkdir -p .package/node_modules || true
cp index.html .package
cp electron/* .package
cp -r electron/* .package
cp -r dist/ext .package/dist
# The css files
cp dist/vendor.css .package/dist
cp main.css .package/main.css
@ -14,6 +16,7 @@ cp main.css .package/main.css
cp dist/vendor.bundle.js .package/dist/vendor.bundle.js
cp main.bundle.js .package/main.bundle.js
cp src/ThirdParty/raphael.min.js .package/src/ThirdParty/raphael.min.js
# Adding electron-log dependency
cp -r node_modules/electron-log .package/node_modules/electron-log
npm run package-electron
npm run electron:packager

@ -36,7 +36,7 @@ export function printAliases(): void {
// Returns true if successful, false otherwise
export function parseAliasDeclaration(dec: string, global = false): boolean {
const re = /^([_|\w|!|%|,|@]+)="(.+)"$/;
const re = /^([\w|!|%|,|@|-]+)="(.+)"$/;
const matches = dec.match(re);
if (matches == null || matches.length != 3) {
return false;

@ -1844,7 +1844,7 @@ function initAugmentations(): void {
moneyCost: 3.75e8,
info:
"A drug that genetically modifies the neurons in the brain " +
"resulting in neurons never die, continuously " +
"resulting in neurons that never die, continuously " +
"regenerate, and strengthen themselves.",
hacking_exp_mult: 1.4,
});

@ -542,7 +542,7 @@ BitNodes["BitNode13"] = new BitNode(
<br />
<br />
Their leader, Allison "Mother" Stanek is said to have created her own Augmentation whose power goes beyond any
other. Find her in Chongquing and gain her trust.
other. Find her in Chongqing and gain her trust.
<br />
<br />
In this BitNode:

@ -34,6 +34,12 @@ import { getTimestamp } from "../utils/helpers/getTimestamp";
import { joinFaction } from "../Faction/FactionHelpers";
import { WorkerScript } from "../Netscript/WorkerScript";
interface BlackOpsAttempt {
error?: string;
isAvailable?: boolean;
action?: BlackOperation;
}
export class Bladeburner implements IBladeburner {
numHosp = 0;
moneyLost = 0;
@ -113,6 +119,43 @@ export class Bladeburner implements IBladeburner {
return Math.min(1, this.stamina / (0.5 * this.maxStamina));
}
canAttemptBlackOp(actionId: IActionIdentifier): BlackOpsAttempt {
// Safety measure - don't repeat BlackOps that are already done
if (this.blackops[actionId.name] != null) {
return { error: "Tried to start a Black Operation that had already been completed" };
}
const action = this.getActionObject(actionId);
if (!(action instanceof BlackOperation)) throw new Error(`Action should be BlackOperation but isn't`);
if (action == null) throw new Error("Failed to get BlackOperation object for: " + actionId.name);
if (action.reqdRank > this.rank) {
return { error: "Tried to start a Black Operation without the rank requirement" };
}
// Can't start a BlackOp if you haven't done the one before it
const blackops = [];
for (const nm in BlackOperations) {
if (BlackOperations.hasOwnProperty(nm)) {
blackops.push(nm);
}
}
blackops.sort(function (a, b) {
return BlackOperations[a].reqdRank - BlackOperations[b].reqdRank; // Sort black ops in intended order
});
const i = blackops.indexOf(actionId.name);
if (i === -1) {
return { error: `Invalid Black Op: '${name}'` };
}
if (i > 0 && this.blackops[blackops[i - 1]] == null) {
return { error: `Preceding Black Op must be completed before starting '${actionId.name}'.` };
}
return { isAvailable: true, action };
}
startAction(player: IPlayer, actionId: IActionIdentifier): void {
if (actionId == null) return;
this.action = actionId;
@ -156,18 +199,16 @@ export class Bladeburner implements IBladeburner {
case ActionTypes["BlackOp"]:
case ActionTypes["BlackOperation"]: {
try {
// Safety measure - don't repeat BlackOps that are already done
if (this.blackops[actionId.name] != null) {
const testBlackOp = this.canAttemptBlackOp(actionId);
if (!testBlackOp.isAvailable) {
this.resetAction();
this.log("Error: Tried to start a Black Operation that had already been completed");
this.log(`Error: ${testBlackOp.error}`);
break;
}
const action = this.getActionObject(actionId);
if (action == null) {
throw new Error("Failed to get BlackOperation object for: " + actionId.name);
if (testBlackOp.action === undefined) {
throw new Error("action should not be null");
}
this.actionTimeToComplete = action.getActionTime(this);
this.actionTimeToComplete = testBlackOp.action.getActionTime(this);
} catch (e: any) {
exceptionAlert(e);
}
@ -502,6 +543,7 @@ export class Bladeburner implements IBladeburner {
const skill = Skills[skillName];
if (skill == null || !(skill instanceof Skill)) {
this.postToConsole("Invalid skill name (Note that it is case-sensitive): " + skillName);
break;
}
if (args[1].toLowerCase() === "list") {
let level = 0;
@ -515,7 +557,9 @@ export class Bladeburner implements IBladeburner {
currentLevel = this.skills[skillName];
}
const pointCost = skill.calculateCost(currentLevel);
if (this.skillPoints >= pointCost) {
if (skill.maxLvl !== 0 && currentLevel >= skill.maxLvl) {
this.postToConsole(`This skill ${skill.name} is already at max level (${currentLevel}/${skill.maxLvl}).`);
} else if (this.skillPoints >= pointCost) {
this.skillPoints -= pointCost;
this.upgradeSkill(skill);
this.log(skill.name + " upgraded to Level " + this.skills[skillName]);
@ -2032,44 +2076,9 @@ export class Bladeburner implements IBladeburner {
// Special logic for Black Ops
if (actionId.type === ActionTypes["BlackOp"]) {
// Can't start a BlackOp if you don't have the required rank
const action = this.getActionObject(actionId);
if (action == null) throw new Error(`Action not found ${actionId.type}, ${actionId.name}`);
if (!(action instanceof BlackOperation)) throw new Error(`Action should be BlackOperation but isn't`);
//const blackOp = (action as BlackOperation);
if (action.reqdRank > this.rank) {
workerScript.log("bladeburner.startAction", () => `Insufficient rank to start Black Op '${actionId.name}'.`);
return false;
}
// Can't start a BlackOp if its already been done
if (this.blackops[actionId.name] != null) {
workerScript.log("bladeburner.startAction", () => `Black Op ${actionId.name} has already been completed.`);
return false;
}
// Can't start a BlackOp if you haven't done the one before it
const blackops = [];
for (const nm in BlackOperations) {
if (BlackOperations.hasOwnProperty(nm)) {
blackops.push(nm);
}
}
blackops.sort(function (a, b) {
return BlackOperations[a].reqdRank - BlackOperations[b].reqdRank; // Sort black ops in intended order
});
const i = blackops.indexOf(actionId.name);
if (i === -1) {
workerScript.log("bladeburner.startAction", () => `Invalid Black Op: '${name}'`);
return false;
}
if (i > 0 && this.blackops[blackops[i - 1]] == null) {
workerScript.log(
"bladeburner.startAction",
() => `Preceding Black Op must be completed before starting '${actionId.name}'.`,
);
const canRunOp = this.canAttemptBlackOp(actionId);
if (!canRunOp.isAvailable) {
workerScript.log("bladeburner.startAction", () => canRunOp.error + "");
return false;
}
}

@ -54,7 +54,6 @@ interface IProps {
export function Console(props: IProps): React.ReactElement {
const classes = useStyles();
const scrollHook = useRef<HTMLDivElement>(null);
const [command, setCommand] = useState("");
const setRerender = useState(false)[1];
@ -64,22 +63,14 @@ export function Console(props: IProps): React.ReactElement {
const [consoleHistoryIndex, setConsoleHistoryIndex] = useState(props.bladeburner.consoleHistory.length);
// TODO: Figure out how to actually make the scrolling work correctly.
function scrollToBottom(): void {
if (!scrollHook.current) return;
scrollHook.current.scrollTop = scrollHook.current.scrollHeight;
}
function rerender(): void {
setRerender((old) => !old);
}
useEffect(() => {
const id = setInterval(rerender, 1000);
const id2 = setInterval(scrollToBottom, 100);
return () => {
clearInterval(id);
clearInterval(id2);
};
}, []);
@ -113,6 +104,7 @@ export function Console(props: IProps): React.ReactElement {
setConsoleHistoryIndex(i);
const prevCommand = consoleHistory[i];
event.currentTarget.value = prevCommand;
setCommand(prevCommand);
}
if (event.keyCode === 40) {
@ -134,39 +126,68 @@ export function Console(props: IProps): React.ReactElement {
setConsoleHistoryIndex(consoleHistoryIndex + 1);
const prevCommand = consoleHistory[consoleHistoryIndex + 1];
event.currentTarget.value = prevCommand;
setCommand(prevCommand);
}
}
}
return (
<Box height={"60vh"} display={"flex"} alignItems={"stretch"} component={Paper}>
<Box>
<List sx={{ height: "100%", overflow: "auto" }}>
{props.bladeburner.consoleLogs.map((log: any, i: number) => (
<Line key={i} content={log} />
))}
<TextField
classes={{ root: classes.textfield }}
autoFocus
tabIndex={1}
type="text"
value={command}
onChange={handleCommandChange}
onKeyDown={handleKeyDown}
InputProps={{
// for players to hook in
className: classes.input,
startAdornment: (
<>
<Typography>&gt;&nbsp;</Typography>
</>
),
spellCheck: false,
}}
/>
</List>
<div ref={scrollHook}></div>
<Paper>
<Box sx={{
height: '60vh',
paddingBottom: '8px',
display: 'flex',
alignItems: 'stretch',
}}>
<Box>
<Logs entries={[...props.bladeburner.consoleLogs]} />
</Box>
</Box>
</Box>
<TextField
classes={{ root: classes.textfield }}
autoFocus
tabIndex={1}
type="text"
value={command}
onChange={handleCommandChange}
onKeyDown={handleKeyDown}
InputProps={{
// for players to hook in
className: classes.input,
startAdornment: (
<>
<Typography>&gt;&nbsp;</Typography>
</>
),
spellCheck: false,
}}
/>
</Paper>
);
}
interface ILogProps {
entries: string[];
}
function Logs({entries}: ILogProps): React.ReactElement {
const scrollHook = useRef<HTMLUListElement>(null);
// TODO: Text gets shifted up as new entries appear, if the user scrolled up it should attempt to keep the text focused
function scrollToBottom(): void {
if (!scrollHook.current) return;
scrollHook.current.scrollTop = scrollHook.current.scrollHeight;
}
useEffect(() => {
scrollToBottom();
}, [entries]);
return (
<List sx={{ height: "100%", overflow: "auto", p: 1 }} ref={scrollHook}>
{entries && entries.map((log: any, i: number) => (
<Line key={i} content={log} />
))}
</List>
);
}

@ -5,6 +5,7 @@ import { Player } from "../Player";
import { Terminal } from "../Terminal";
import { SpecialServers } from "../Server/data/SpecialServers";
import { Money } from "../ui/React/Money";
import { DarkWebItem } from "./DarkWebItem";
//Posts a "help" message if connected to DarkWeb
export function checkIfConnectedToDarkweb(): void {
@ -21,9 +22,16 @@ export function checkIfConnectedToDarkweb(): void {
export function listAllDarkwebItems(): void {
for (const key in DarkWebItems) {
const item = DarkWebItems[key];
const cost = Player.getHomeComputer().programs.includes(item.program) ? (
<span style={{ color: `green` }}>[OWNED]</span>
) : (
<Money money={item.price} />
);
Terminal.printRaw(
<>
{item.program} - <Money money={item.price} /> - {item.description}
<span>{item.program}</span> - <span>{cost}</span> - <span>{item.description}</span>
</>,
);
}
@ -33,7 +41,8 @@ export function buyDarkwebItem(itemName: string): void {
itemName = itemName.toLowerCase();
// find the program that matches, if any
let item = null;
let item: DarkWebItem | null = null;
for (const key in DarkWebItems) {
const i = DarkWebItems[key];
if (i.program.toLowerCase() == itemName) {
@ -61,7 +70,19 @@ export function buyDarkwebItem(itemName: string): void {
// buy and push
Player.loseMoney(item.price, "other");
const programsRef = Player.getHomeComputer().programs;
// Remove partially created program if there is one
const existingPartialExeIndex = programsRef.findIndex(
(program) => item?.program && program.startsWith(item?.program),
);
// findIndex returns -1 if there is no match, we only want to splice on a match
if (existingPartialExeIndex > -1) {
programsRef.splice(existingPartialExeIndex, 1);
}
// Add the newly bought, full .exe
Player.getHomeComputer().programs.push(item.program);
Terminal.print(
"You have purchased the " + item.program + " program. The new program can be found on your home computer.",
);

@ -71,6 +71,20 @@ export function Servers(): React.ReactElement {
}
}
function minMoney(): void {
const s = GetServer(server);
if (s === null) return;
if (!(s instanceof Server)) return;
s.moneyAvailable = 0;
}
function minAllMoney(): void {
for (const s of GetAllServers()) {
if (!(s instanceof Server)) return;
s.moneyAvailable = 0;
}
}
return (
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
@ -119,6 +133,12 @@ export function Servers(): React.ReactElement {
<td>
<Typography>Money:</Typography>
</td>
<td>
<Button onClick={minMoney}>Min one</Button>
</td>
<td>
<Button onClick={minAllMoney}>Min all</Button>
</td>
<td>
<Button onClick={maxMoney}>Max one</Button>
</td>

@ -14,11 +14,14 @@ import { HacknetServer } from "./Hacknet/HacknetServer";
import { CityName } from "./Locations/data/CityNames";
import { Player } from "./Player";
import { Programs } from "./Programs/Programs";
import { isScriptFilename } from "./Script/isScriptFilename";
import { Script } from "./Script/Script";
import { GetAllServers, GetServer } from "./Server/AllServers";
import { SpecialServers } from "./Server/data/SpecialServers";
import { Server } from "./Server/Server";
import { Router } from "./ui/GameRoot";
import { Page } from "./ui/Router";
import { removeLeadingSlash } from "./Terminal/DirectoryHelpers";
interface Achievement {
ID: string;
@ -32,6 +35,7 @@ function bitNodeFinishedState(): boolean {
return Player.bladeburner !== null && Player.bladeburner.blackops.hasOwnProperty("Operation Daedalus");
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function sfAchievement(): Achievement[] {
const achs: Achievement[] = [];
for (let i = 0; i <= 11; i++) {
@ -232,7 +236,8 @@ const achievements: Achievement[] = [
{
ID: "BLADEBURNER_OVERCLOCK",
Condition: () =>
Player.bladeburner !== null && Player.bladeburner.skills[SkillNames.Overclock] === Skills[SkillNames.Overclock],
Player.bladeburner !== null &&
Player.bladeburner.skills[SkillNames.Overclock] === Skills[SkillNames.Overclock].maxLvl,
},
{
ID: "BLADEBURNER_UNSPENT_100000",
@ -249,12 +254,14 @@ const achievements: Achievement[] = [
Condition: () => {
if (!hasHacknetServers(Player)) return false;
for (const h of Player.hacknetNodes) {
if (!(h instanceof HacknetServer)) return false;
if (typeof h !== "string") return false;
const hs = GetServer(h);
if (!(hs instanceof HacknetServer)) return false;
if (
h.maxRam === HacknetServerConstants.MaxRam &&
h.cores === HacknetServerConstants.MaxCores &&
h.level === HacknetServerConstants.MaxLevel &&
h.cache === HacknetServerConstants.MaxCache
hs.maxRam === HacknetServerConstants.MaxRam &&
hs.cores === HacknetServerConstants.MaxCores &&
hs.level === HacknetServerConstants.MaxLevel &&
hs.cache === HacknetServerConstants.MaxCache
)
return true;
}
@ -293,7 +300,7 @@ const achievements: Achievement[] = [
Condition: () =>
Player.bitNodeN === 1 &&
bitNodeFinishedState() &&
Player.getHomeComputer().maxRam <= 32 &&
Player.getHomeComputer().maxRam <= 128 &&
Player.getHomeComputer().cpuCores === 1,
},
{
@ -408,5 +415,36 @@ function calculateAchievements(): void {
export function initElectron(): void {
setAchievements([]);
initWebserver();
setInterval(calculateAchievements, 5000);
}
function initWebserver(): void {
(document as any).saveFile = function (filename: string, code: string): string {
filename = removeLeadingSlash(filename);
console.log(code);
code = Buffer.from(code, "base64").toString();
console.log(code);
const home = GetServer("home");
if (home === null) return "'home' server not found.";
if (home === null) return "Server should not be null but it is.";
if (isScriptFilename(filename)) {
//If the current script already exists on the server, overwrite it
for (let i = 0; i < home.scripts.length; i++) {
console.log(`${filename} ${home.scripts[i].filename}`);
if (filename == home.scripts[i].filename) {
home.scripts[i].saveScript(filename, code, "home", home.scripts);
return "written";
}
}
//If the current script does NOT exist, create a new one
const script = new Script();
script.saveScript(filename, code, "home", home.scripts);
home.scripts.push(script);
return "written";
}
return "not a script file";
};
}

@ -174,7 +174,7 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
<>
<br />
<Typography variant="h4">Purchased Augmentations</Typography>
<Typography>This factions also offers these augmentations but you already own them.</Typography>
<Typography>This faction also offers these augmentations but you already own them.</Typography>
{owned.map((aug) => purchaseableAugmentation(aug, true))}
</>
);

@ -353,7 +353,7 @@ export class Gang {
const res = member.ascend();
this.respect = Math.max(1, this.respect - res.respect);
if (workerScript) {
workerScript.log("ascend", () => `Ascended Gang member ${member.name}`);
workerScript.log("gang.ascend", () => `Ascended Gang member ${member.name}`);
}
return res;
} catch (e: any) {

@ -1,6 +1,7 @@
import React from "react";
import Button from "@mui/material/Button";
import Tooltip from "@mui/material/Tooltip";
import Typography from "@mui/material/Typography";
import { IPlayer } from "../../PersonObjects/IPlayer";
@ -38,6 +39,11 @@ export function CoresButton(props: IProps): React.ReactElement {
}
>
<span>
<br />
<Typography>
<i>"Cores increase the effectiveness of grow() and weaken() on 'home'"</i>
</Typography>
<br />
<Button disabled={!props.p.canAfford(cost)} onClick={buy}>
Upgrade 'home' cores ({homeComputer.cpuCores} -&gt; {homeComputer.cpuCores + 1}) -&nbsp;
<Money money={cost} player={props.p} />

@ -1,6 +1,7 @@
import React from "react";
import Button from "@mui/material/Button";
import Tooltip from "@mui/material/Tooltip";
import Typography from "@mui/material/Typography";
import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer";
@ -37,6 +38,11 @@ export function RamButton(props: IProps): React.ReactElement {
}
>
<span>
<br />
<Typography>
<i>"More RAM means more scripts on 'home'"</i>
</Typography>
<br />
<Button disabled={!props.p.canAfford(cost)} onClick={buy}>
Upgrade 'home' RAM ({numeralWrapper.formatRAM(homeComputer.maxRam)} -&gt;&nbsp;
{numeralWrapper.formatRAM(homeComputer.maxRam * 2)}) -&nbsp;

@ -86,7 +86,7 @@ export function SpecialLocation(props: IProps): React.ReactElement {
function renderNoodleBar(): React.ReactElement {
function EatNoodles(): void {
SnackbarEvents.emit("You ate some delicious noodles and feel refreshed", "success");
SnackbarEvents.emit("You ate some delicious noodles and feel refreshed", "success", 2000);
N00dles(); // This is the true power of the noodles.
if (player.sourceFiles.length > 0) player.giveExploit(Exploit.N00dles);
if (player.sourceFileLvl(5) > 0 || player.bitNodeN === 5) {

@ -73,7 +73,7 @@ export function TechVendorLocation(props: IProps): React.ReactElement {
{purchaseServerButtons}
<br />
<Typography>
<i>"You can order bigger servers via scripts. We don't take custom order in person."</i>
<i>"You can order bigger servers via scripts. We don't take custom orders in person."</i>
</Typography>
<br />
<TorButton p={player} rerender={rerender} />

@ -348,6 +348,10 @@ export const RamCosts: IMap<any> = {
remove: RamCostConstants.ScriptStanekDeleteAt,
},
ui: {
getTheme: 0,
},
heart: {
// Easter egg function
break: 0,

@ -64,7 +64,7 @@ import { NetscriptSleeve } from "./NetscriptFunctions/Sleeve";
import { NetscriptExtra } from "./NetscriptFunctions/Extra";
import { NetscriptHacknet } from "./NetscriptFunctions/Hacknet";
import { NetscriptStanek } from "./NetscriptFunctions/Stanek";
import { NetscriptUserInterface } from './NetscriptFunctions/UserInterface';
import { NetscriptBladeburner } from "./NetscriptFunctions/Bladeburner";
import { NetscriptCodingContract } from "./NetscriptFunctions/CodingContract";
import { NetscriptCorporation } from "./NetscriptFunctions/Corporation";
@ -455,6 +455,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
const formulas = NetscriptFormulas(Player, workerScript, helper);
const singularity = NetscriptSingularity(Player, workerScript, helper);
const stockmarket = NetscriptStockMarket(Player, workerScript, helper);
const ui = NetscriptUserInterface(Player, workerScript, helper);
const base: INS = {
...singularity,
@ -465,7 +466,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
sleeve: sleeve,
corporation: corporation,
stanek: stanek,
ui: ui,
formulas: formulas,
stock: stockmarket,
args: workerScript.args,
@ -508,6 +509,8 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
if (hackAmount < 0 || hackAmount > server.moneyAvailable) {
return -1;
} else if (hackAmount === 0) {
return 0;
}
const percentHacked = calculatePercentMoneyHacked(server, Player);
@ -1594,7 +1597,9 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return cost;
},
purchaseServer: function (name: any, ram: any): any {
purchaseServer: function (aname: any, aram: any): any {
const name = helper.string("purchaseServer", "name", aname);
const ram = helper.number("purchaseServer", "ram", aram);
updateDynamicRam("purchaseServer", getRamCost("purchaseServer"));
let hostnameStr = String(name);
hostnameStr = hostnameStr.replace(/\s+/g, "");
@ -2131,15 +2136,15 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return Player.playtimeSinceLastAug;
},
alert: function (message: any): void {
message = toNative(message);
dialogBoxCreate(JSON.stringify(message));
message = argsToString([message]);
dialogBoxCreate(message);
},
toast: function (message: any, variant: any = "success"): void {
toast: function (message: any, variant: any = "success", duration: any = 2000): void {
if (!["success", "info", "warning", "error"].includes(variant))
throw new Error(`variant must be one of "success", "info", "warning", or "error"`);
message = toNative(message);
SnackbarEvents.emit(JSON.stringify(message), variant);
message = argsToString([message]);
SnackbarEvents.emit(message, variant, duration);
},
prompt: function (txt: any): any {
if (!isString(txt)) {

@ -819,7 +819,7 @@ export function NetscriptSingularity(
return player.getUpgradeHomeRamCost();
},
workForCompany: function (companyName: any): any {
workForCompany: function (companyName: any, focus = true): any {
helper.updateDynamicRam("workForCompany", getRamCost("workForCompany"));
helper.checkSingularityAccess("workForCompany", 2);
@ -848,7 +848,6 @@ export function NetscriptSingularity(
return false;
}
const wasWorking = player.isWorking;
const wasFocused = player.focus;
if (player.isWorking) {
const txt = player.singularityStopWork();
@ -861,8 +860,14 @@ export function NetscriptSingularity(
player.startWork(companyName);
}
if (!wasWorking || (wasWorking && !wasFocused)) player.stopFocusing();
else if (wasWorking && wasFocused) player.startFocusing();
if (focus) {
player.startFocusing();
Router.toWork();
}
else if (wasFocused) {
player.stopFocusing();
Router.toTerminal();
}
workerScript.log(
"workForCompany",
() => `Began working at '${player.companyName}' as a '${companyPositionName}'`,
@ -986,7 +991,7 @@ export function NetscriptSingularity(
workerScript.log("joinFaction", () => `Joined the '${name}' faction.`);
return true;
},
workForFaction: function (name: any, type: any): any {
workForFaction: function (name: any, type: any, focus = true): any {
helper.updateDynamicRam("workForFaction", getRamCost("workForFaction"));
helper.checkSingularityAccess("workForFaction", 2);
getFaction("workForFaction", name);
@ -1002,8 +1007,7 @@ export function NetscriptSingularity(
return false;
}
const wasWorking = player.isWorking;
const wasFocused = player.focus;
const wasFocusing = player.focus;
if (player.isWorking) {
const txt = player.singularityStopWork();
workerScript.log("workForFaction", () => txt);
@ -1102,8 +1106,14 @@ export function NetscriptSingularity(
return false;
}
player.startFactionHackWork(fac);
if (!wasWorking || (wasWorking && !wasFocused)) player.stopFocusing();
else if (wasWorking && wasFocused) player.startFocusing();
if (focus)
{
player.startFocusing();
Router.toWork();
} else if (wasFocusing) {
player.stopFocusing();
Router.toTerminal();
}
workerScript.log("workForFaction", () => `Started carrying out hacking contracts for '${fac.name}'`);
return true;
case "field":
@ -1114,8 +1124,14 @@ export function NetscriptSingularity(
return false;
}
player.startFactionFieldWork(fac);
if (!wasWorking || (wasWorking && !wasFocused)) player.stopFocusing();
else if (wasWorking && wasFocused) player.startFocusing();
if (focus)
{
player.startFocusing();
Router.toWork();
} else if (wasFocusing) {
player.stopFocusing();
Router.toTerminal();
}
workerScript.log("workForFaction", () => `Started carrying out field missions for '${fac.name}'`);
return true;
case "security":
@ -1126,8 +1142,14 @@ export function NetscriptSingularity(
return false;
}
player.startFactionSecurityWork(fac);
if (!wasWorking || (wasWorking && !wasFocused)) player.stopFocusing();
else if (wasWorking && wasFocused) player.startFocusing();
if (focus)
{
player.startFocusing();
Router.toWork();
} else if (wasFocusing) {
player.stopFocusing();
Router.toTerminal();
}
workerScript.log("workForFaction", () => `Started carrying out security work for '${fac.name}'`);
return true;
default:

@ -0,0 +1,20 @@
import { INetscriptHelper } from "./INetscriptHelper";
import { WorkerScript } from "../Netscript/WorkerScript";
import { IPlayer } from "../PersonObjects/IPlayer";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { UserInterface as IUserInterface, UserInterfaceTheme } from "../ScriptEditor/NetscriptDefinitions";
import { Settings } from "../Settings/Settings";
export function NetscriptUserInterface(
player: IPlayer,
workerScript: WorkerScript,
helper: INetscriptHelper,
): IUserInterface {
return {
getTheme: function (): UserInterfaceTheme {
helper.updateDynamicRam("getTheme", getRamCost("ui", "getTheme"));
return {...Settings.theme};
},
}
}

@ -4,6 +4,7 @@ const defaultInterpreter = new Interpreter("", () => undefined);
// the acorn interpreter has a bug where it doesn't convert arrays correctly.
// so we have to more or less copy it here.
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function toNative(pseudoObj: any): any {
if (pseudoObj == null) return null;
if (

@ -34,6 +34,7 @@ import { parse } from "acorn";
import { simple as walksimple } from "acorn-walk";
import { areFilesEqual } from "./Terminal/DirectoryHelpers";
import { Player } from "./Player";
import { Terminal } from "./Terminal";
// Netscript Ports are instantiated here
export const NetscriptPorts: IPort[] = [];
@ -126,6 +127,7 @@ function startNetscript2Script(workerScript: WorkerScript): Promise<WorkerScript
})
.catch((e) => reject(e));
}).catch((e) => {
console.log(e);
if (e instanceof Error) {
if (e instanceof SyntaxError) {
workerScript.errorMessage = makeRuntimeRejectMsg(workerScript, e.message + " (sorry we can't be more helpful)");
@ -608,6 +610,7 @@ export function updateOnlineScriptTimes(numCycles = 1): void {
export function loadAllRunningScripts(): void {
const skipScriptLoad = window.location.href.toLowerCase().indexOf("?noscripts") !== -1;
if (skipScriptLoad) {
Terminal.warn('Skipped loading player scripts during startup');
console.info("Skipping the load of any scripts during startup");
}
for (const server of GetAllServers()) {

@ -242,6 +242,7 @@ export interface IPlayer {
getIntelligenceBonus(weight: number): number;
getCasinoWinnings(): number;
quitJob(company: string): void;
hasJob(): boolean;
createHacknetServer(): HacknetServer;
startCreateProgramWork(router: IRouter, programName: string, time: number, reqLevel: number): void;
queueAugmentation(augmentationName: string): void;

@ -247,6 +247,7 @@ export class PlayerObject implements IPlayer {
getIntelligenceBonus: (weight: number) => number;
getCasinoWinnings: () => number;
quitJob: (company: string) => void;
hasJob: () => boolean;
process: (router: IRouter, numCycles?: number) => void;
createHacknetServer: () => HacknetServer;
startCreateProgramWork: (router: IRouter, programName: string, time: number, reqLevel: number) => void;
@ -531,6 +532,7 @@ export class PlayerObject implements IPlayer {
this.applyForJob = generalMethods.applyForJob;
this.getNextCompanyPosition = generalMethods.getNextCompanyPosition;
this.quitJob = generalMethods.quitJob;
this.hasJob = generalMethods.hasJob;
this.applyForSoftwareJob = generalMethods.applyForSoftwareJob;
this.applyForSoftwareConsultantJob = generalMethods.applyForSoftwareConsultantJob;
this.applyForItJob = generalMethods.applyForItJob;

@ -26,7 +26,11 @@ import { Locations } from "../../Locations/Locations";
import { CityName } from "../../Locations/data/CityNames";
import { LocationName } from "../../Locations/data/LocationNames";
import { Sleeve } from "../../PersonObjects/Sleeve/Sleeve";
import { calculateSkill as calculateSkillF, calculateSkillProgress as calculateSkillProgressF, getEmptySkillProgress, ISkillProgress } from "../formulas/skill";
import {
calculateSkill as calculateSkillF,
calculateSkillProgress as calculateSkillProgressF,
ISkillProgress,
} from "../formulas/skill";
import { calculateIntelligenceBonus } from "../formulas/intelligence";
import {
getHackingWorkRepGain,
@ -1648,7 +1652,7 @@ export function regenerateHp(this: IPlayer, amt: number): void {
export function hospitalize(this: IPlayer): number {
const cost = getHospitalizationCost(this);
SnackbarEvents.emit(`You've been Hospitalized for ${numeralWrapper.formatMoney(cost)}`, "warning");
SnackbarEvents.emit(`You've been Hospitalized for ${numeralWrapper.formatMoney(cost)}`, "warning", 2000);
this.loseMoney(cost, "hospitalization");
this.hp = this.max_hp;
@ -1782,6 +1786,15 @@ export function quitJob(this: IPlayer, company: string): void {
delete this.jobs[company];
}
/**
* Method to see if the player has at least one job assigned to them
* @param this The player instance
* @returns Whether the user has at least one job
*/
export function hasJob(this: IPlayer): boolean {
return Boolean(Object.keys(this.jobs).length);
}
export function applyForSoftwareJob(this: IPlayer, sing = false): boolean {
return this.applyForJob(CompanyPositions[posNames.SoftwareCompanyPositions[0]], sing);
}
@ -2582,7 +2595,7 @@ export function canAccessResleeving(this: IPlayer): boolean {
export function giveExploit(this: IPlayer, exploit: Exploit): void {
if (!this.exploits.includes(exploit)) {
this.exploits.push(exploit);
SnackbarEvents.emit("SF -1 acquired!", "success");
SnackbarEvents.emit("SF -1 acquired!", "success", 2000);
}
}

@ -9,9 +9,14 @@ export function calculateExp(skill: number, mult = 1): number {
export function calculateSkillProgress(exp: number, mult = 1): ISkillProgress {
const currentSkill = calculateSkill(exp, mult);
const nextSkill = currentSkill + 1;
let baseExperience = calculateExp(currentSkill, mult);
if (baseExperience < 0) baseExperience = 0;
const nextExperience = calculateExp(nextSkill, mult)
let nextExperience = calculateExp(nextSkill, mult)
if (nextExperience < 0) nextExperience = 0;
const normalize = (value: number): number => ((value - baseExperience) * 100) / (nextExperience - baseExperience);
return {
currentSkill,
@ -19,7 +24,7 @@ export function calculateSkillProgress(exp: number, mult = 1): ISkillProgress {
baseExperience,
experience: exp,
nextExperience,
progress: exp / nextExperience,
progress: (nextExperience - baseExperience !== 0) ? normalize(exp) : 99.99
}
}
@ -32,7 +37,7 @@ export interface ISkillProgress {
progress: number;
}
export function getEmptySkillProgress() {
export function getEmptySkillProgress(): ISkillProgress {
return {
currentSkill: 0, nextSkill: 0,
baseExperience: 0, experience: 0, nextExperience: 0,

@ -69,7 +69,7 @@ class BitburnerSaveObject {
save(saveString)
.then(() => {
if (!Settings.SuppressSavedGameToast) {
SnackbarEvents.emit("Game Saved!", "info")
SnackbarEvents.emit("Game Saved!", "info", 2000);
}
})
.catch((err) => console.error(err));

@ -194,6 +194,8 @@ async function parseOnlyRamCalculate(
func = workerScript.env.vars.sleeve[ref];
} else if (ref in workerScript.env.vars.stock) {
func = workerScript.env.vars.stock[ref];
} else if (ref in workerScript.env.vars.ui) {
func = workerScript.env.vars.ui[ref];
} else {
func = workerScript.env.vars[ref];
}

File diff suppressed because it is too large Load Diff

@ -2,4 +2,5 @@ export interface Options {
theme: string;
insertSpaces: boolean;
fontSize: number;
vim: boolean;
}

@ -21,12 +21,14 @@ export function OptionsModal(props: IProps): React.ReactElement {
const [theme, setTheme] = useState(props.options.theme);
const [insertSpaces, setInsertSpaces] = useState(props.options.insertSpaces);
const [fontSize, setFontSize] = useState(props.options.fontSize);
const [vim, setVim] = useState(props.options.vim);
function save(): void {
props.save({
theme: theme,
insertSpaces: insertSpaces,
fontSize: fontSize,
theme,
insertSpaces,
fontSize,
vim,
});
props.onClose();
}
@ -54,6 +56,12 @@ export function OptionsModal(props: IProps): React.ReactElement {
<Typography>Use whitespace over tabs: </Typography>
<Switch onChange={(event) => setInsertSpaces(event.target.checked)} checked={insertSpaces} />
</Box>
<Box display="flex" flexDirection="row" alignItems="center">
<Typography>Enable vim mode: </Typography>
<Switch onChange={(event) => setVim(event.target.checked)} checked={vim} />
</Box>
<Box display="flex" flexDirection="row" alignItems="center">
<TextField type="number" label="Font size" value={fontSize} onChange={onFontChange} />
</Box>

File diff suppressed because it is too large Load Diff

@ -246,6 +246,7 @@ export class BaseServer {
this.maxRam = ram;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
updateRamUsed(ram: number, player: IPlayer): void {
this.ramUsed = ram;
}

@ -16,22 +16,25 @@ import { isValidNumber } from "../utils/helpers/isValidNumber";
* does not have a duplicate hostname/ip.
*/
export function safetlyCreateUniqueServer(params: IConstructorParams): Server {
let hostname: string = params.hostname.replace(/ /g, `-`);
if (params.ip != null && ipExists(params.ip)) {
params.ip = createUniqueRandomIp();
}
if (GetServer(params.hostname) != null) {
if (GetServer(hostname) != null) {
hostname = `${hostname}-0`;
// Use a for loop to ensure that we don't get suck in an infinite loop somehow
let hostname: string = params.hostname;
for (let i = 0; i < 200; ++i) {
hostname = `${params.hostname}-${i}`;
hostname = hostname.replace(/-[0-9]+$/, `-${i}`);
if (GetServer(hostname) == null) {
break;
}
}
params.hostname = hostname;
}
params.hostname = hostname;
return new Server(params);
}

@ -96,7 +96,7 @@ export function purchaseServer(hostname: string, ram: number, cost: number, p: I
p.loseMoney(cost, "servers");
dialogBoxCreate("Server successfully purchased with hostname " + hostname);
dialogBoxCreate("Server successfully purchased with hostname " + newServ.hostname);
}
// Manually upgrade RAM on home computer (NOT through Netscript)

@ -1,5 +1,6 @@
import { ISelfInitializer, ISelfLoading } from "../types";
import { OwnedAugmentationsOrderSetting, PurchaseAugmentationsOrderSetting } from "./SettingEnums";
import { defaultTheme, ITheme } from "./Themes";
/**
* Represents the default settings the player could customize.
@ -111,42 +112,7 @@ interface IDefaultSettings {
/*
* Theme colors
*/
theme: {
[key: string]: string | undefined;
primarylight: string;
primary: string;
primarydark: string;
successlight: string;
success: string;
successdark: string;
errorlight: string;
error: string;
errordark: string;
secondarylight: string;
secondary: string;
secondarydark: string;
warninglight: string;
warning: string;
warningdark: string;
infolight: string;
info: string;
infodark: string;
welllight: string;
well: string;
white: string;
black: string;
hp: string;
money: string;
hack: string;
combat: string;
cha: string;
int: string;
rep: string;
disabled: string;
backgroundprimary: string;
backgroundsecondary: string;
button: string;
};
theme: ITheme;
}
/**
@ -168,6 +134,8 @@ interface ISettings extends IDefaultSettings {
MonacoInsertSpaces: boolean;
MonacoFontSize: number;
MonacoVim: boolean;
}
export const defaultSettings: IDefaultSettings = {
@ -193,41 +161,7 @@ export const defaultSettings: IDefaultSettings = {
SuppressTIXPopup: false,
SuppressSavedGameToast: false,
theme: {
primarylight: "#0f0",
primary: "#0c0",
primarydark: "#090",
successlight: "#0f0",
success: "#0c0",
successdark: "#090",
errorlight: "#f00",
error: "#c00",
errordark: "#900",
secondarylight: "#AAA",
secondary: "#888",
secondarydark: "#666",
warninglight: "#ff0",
warning: "#cc0",
warningdark: "#990",
infolight: "#69f",
info: "#36c",
infodark: "#039",
welllight: "#444",
well: "#222",
white: "#fff",
black: "#000",
hp: "#dd3434",
money: "#ffd700",
hack: "#adff2f",
combat: "#faffdf",
cha: "#a671d1",
int: "#6495ed",
rep: "#faffdf",
disabled: "#66cfbc",
backgroundprimary: "#000",
backgroundsecondary: "#000",
button: "#333",
},
theme: defaultTheme,
};
/**
@ -261,42 +195,9 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
MonacoTheme: "monokai",
MonacoInsertSpaces: false,
MonacoFontSize: 20,
MonacoVim: false,
theme: {
primarylight: defaultSettings.theme.primarylight,
primary: defaultSettings.theme.primary,
primarydark: defaultSettings.theme.primarydark,
successlight: defaultSettings.theme.successlight,
success: defaultSettings.theme.success,
successdark: defaultSettings.theme.successdark,
errorlight: defaultSettings.theme.errorlight,
error: defaultSettings.theme.error,
errordark: defaultSettings.theme.errordark,
secondarylight: defaultSettings.theme.secondarylight,
secondary: defaultSettings.theme.secondary,
secondarydark: defaultSettings.theme.secondarydark,
warninglight: defaultSettings.theme.warninglight,
warning: defaultSettings.theme.warning,
warningdark: defaultSettings.theme.warningdark,
infolight: defaultSettings.theme.infolight,
info: defaultSettings.theme.info,
infodark: defaultSettings.theme.infodark,
welllight: defaultSettings.theme.welllight,
well: defaultSettings.theme.well,
white: defaultSettings.theme.white,
black: defaultSettings.theme.black,
hp: defaultSettings.theme.hp,
money: defaultSettings.theme.money,
hack: defaultSettings.theme.hack,
combat: defaultSettings.theme.combat,
cha: defaultSettings.theme.cha,
int: defaultSettings.theme.int,
rep: defaultSettings.theme.rep,
disabled: defaultSettings.theme.disabled,
backgroundprimary: defaultSettings.theme.backgroundprimary,
backgroundsecondary: defaultSettings.theme.backgroundsecondary,
button: defaultSettings.theme.button,
},
theme: { ...defaultTheme },
init() {
Object.assign(Settings, defaultSettings);
},

411
src/Settings/Themes.ts Normal file

@ -0,0 +1,411 @@
import { IMap } from "../types";
export interface ITheme {
[key: string]: string | undefined;
primarylight: string;
primary: string;
primarydark: string;
successlight: string;
success: string;
successdark: string;
errorlight: string;
error: string;
errordark: string;
secondarylight: string;
secondary: string;
secondarydark: string;
warninglight: string;
warning: string;
warningdark: string;
infolight: string;
info: string;
infodark: string;
welllight: string;
well: string;
white: string;
black: string;
hp: string;
money: string;
hack: string;
combat: string;
cha: string;
int: string;
rep: string;
disabled: string;
backgroundprimary: string;
backgroundsecondary: string;
button: string;
}
export interface IPredefinedTheme {
colors: ITheme;
name?: string;
credit?: string;
description?: string;
reference?: string;
}
export const defaultTheme: ITheme = {
primarylight: "#0f0",
primary: "#0c0",
primarydark: "#090",
successlight: "#0f0",
success: "#0c0",
successdark: "#090",
errorlight: "#f00",
error: "#c00",
errordark: "#900",
secondarylight: "#AAA",
secondary: "#888",
secondarydark: "#666",
warninglight: "#ff0",
warning: "#cc0",
warningdark: "#990",
infolight: "#69f",
info: "#36c",
infodark: "#039",
welllight: "#444",
well: "#222",
white: "#fff",
black: "#000",
hp: "#dd3434",
money: "#ffd700",
hack: "#adff2f",
combat: "#faffdf",
cha: "#a671d1",
int: "#6495ed",
rep: "#faffdf",
disabled: "#66cfbc",
backgroundprimary: "#000",
backgroundsecondary: "#000",
button: "#333",
};
export const getPredefinedThemes = (): IMap<IPredefinedTheme> => ({
Default: {
colors: defaultTheme,
},
Monokai: {
description: "Monokai'ish",
colors: {
primarylight: "#FFF",
primary: "#F8F8F2",
primarydark: "#FAFAEB",
successlight: "#ADE146",
success: "#A6E22E",
successdark: "#98E104",
errorlight: "#FF69A0",
error: "#F92672",
errordark: "#D10F56",
secondarylight: "#AAA",
secondary: "#888",
secondarydark: "#666",
warninglight: "#E1D992",
warning: "#E6DB74",
warningdark: "#EDDD54",
infolight: "#92E1F1",
info: "#66D9EF",
infodark: "#31CDED",
welllight: "#444",
well: "#222",
white: "#fff",
black: "#000",
hp: "#F92672",
money: "#E6DB74",
hack: "#A6E22E",
combat: "#75715E",
cha: "#AE81FF",
int: "#66D9EF",
rep: "#E69F66",
disabled: "#66cfbc",
backgroundprimary: "#272822",
backgroundsecondary: "#1B1C18",
button: "#333",
},
},
Warmer: {
credit: "hexnaught",
description: "Warmer, softer theme",
reference: "https://discord.com/channels/415207508303544321/921991895230611466/921999581020028938",
colors: {
primarylight: "#EA9062",
primary: "#DD7B4A",
primarydark: "#D3591C",
successlight: "#6ACF6A",
success: "#43BF43",
successdark: "#3E913E",
errorlight: "#C15757",
error: "#B34141",
errordark: "#752525",
secondarylight: "#AAA",
secondary: "#888",
secondarydark: "#666",
warninglight: "#E6E69D",
warning: "#DADA56",
warningdark: "#A1A106",
infolight: "#69f",
info: "#36c",
infodark: "#039",
welllight: "#444",
well: "#222",
white: "#fff",
black: "#000",
hp: "#dd3434",
money: "#ffd700",
hack: "#adff2f",
combat: "#faffdf",
cha: "#AD84CF",
int: "#6495ed",
rep: "#faffdf",
disabled: "#76C6B7",
backgroundprimary: "#000",
backgroundsecondary: "#000",
button: "#333",
},
},
"Dark+": {
credit: "LoganMD",
description: "VSCode Dark+",
reference: "https://discord.com/channels/415207508303544321/921991895230611466/921999975867617310",
colors: {
primarylight: "#E0E0BC",
primary: "#CCCCAE",
primarydark: "#B8B89C",
successlight: "#00F000",
success: "#00D200",
successdark: "#00B400",
errorlight: "#F00000",
error: "#C80000",
errordark: "#A00000",
secondarylight: "#B4AEAE",
secondary: "#969090",
secondarydark: "#787272",
warninglight: "#F0F000",
warning: "#C8C800",
warningdark: "#A0A000",
infolight: "#69f",
info: "#36c",
infodark: "#039",
welllight: "#444",
well: "#222",
white: "#fff",
black: "#1E1E1E",
hp: "#dd3434",
money: "#ffd700",
hack: "#adff2f",
combat: "#faffdf",
cha: "#a671d1",
int: "#6495ed",
rep: "#faffdf",
disabled: "#66cfbc",
backgroundprimary: "#1E1E1E",
backgroundsecondary: "#252525",
button: "#333",
},
},
"Mayukai Dark": {
credit: "Festive Noire",
description: "Mayukai Dark-esque",
reference: "https://discord.com/channels/415207508303544321/921991895230611466/922037502334889994",
colors: {
primarylight: "#DDDFC5",
primary: "#CDCFB6",
primarydark: "#9D9F8C",
successlight: "#00EF00",
success: "#00A500",
successdark: "#007A00",
errorlight: "#F92672",
error: "#CA1C5C",
errordark: "#90274A",
secondarylight: "#AAA",
secondary: "#888",
secondarydark: "#666",
warninglight: "#D3D300",
warning: "#cc0",
warningdark: "#990",
infolight: "#69f",
info: "#36c",
infodark: "#039",
welllight: "#444",
well: "#00010A",
white: "#fff",
black: "#020509",
hp: "#dd3434",
money: "#ffd700",
hack: "#8CCF27",
combat: "#faffdf",
cha: "#a671d1",
int: "#6495ed",
rep: "#faffdf",
disabled: "#66cfbc",
backgroundprimary: "#080C11",
backgroundsecondary: "#03080F",
button: "#00010A",
},
},
Purple: {
credit: "zer0ney",
description: "Essentially all defaults except for purple replacing the main colors",
reference: "https://discord.com/channels/415207508303544321/921991895230611466/922091815849570395",
colors: {
primarylight: "#BA55D3",
primary: "#9370DB",
primarydark: "#8A2BE2",
successlight: "#BA55D3",
success: "#9370DB",
successdark: "#8A2BE2",
errorlight: "#f00",
error: "#c00",
errordark: "#900",
secondarylight: "#AAA",
secondary: "#888",
secondarydark: "#666",
warninglight: "#ff0",
warning: "#cc0",
warningdark: "#990",
infolight: "#69f",
info: "#36c",
infodark: "#039",
welllight: "#444",
well: "#222",
white: "#fff",
black: "#000",
hp: "#dd3434",
money: "#ffd700",
hack: "#adff2f",
combat: "#faffdf",
cha: "#a671d1",
int: "#6495ed",
rep: "#faffdf",
disabled: "#66cfbc",
backgroundprimary: "#000",
backgroundsecondary: "#000",
button: "#333",
},
},
"Smooth Green": {
credit: "Swidt",
description: "A nice green theme that doesn't hurt your eyes.",
reference: "https://discord.com/channels/415207508303544321/921991895230611466/922243957986033725",
colors: {
primarylight: "#E0E0BC",
primary: "#B0D9A3",
primarydark: "#B8B89C",
successlight: "#00F000",
success: "#6BC16B",
successdark: "#00B400",
errorlight: "#F00000",
error: "#3D713D",
errordark: "#A00000",
secondarylight: "#B4AEAE",
secondary: "#8FAF85",
secondarydark: "#787272",
warninglight: "#F0F000",
warning: "#38F100",
warningdark: "#A0A000",
infolight: "#69f",
info: "#36c",
infodark: "#039",
welllight: "#444",
well: "#2F3C2B",
white: "#fff",
black: "#1E1E1E",
hp: "#dd3434",
money: "#4AA52E",
hack: "#adff2f",
combat: "#faffdf",
cha: "#a671d1",
int: "#6495ed",
rep: "#35A135",
disabled: "#66cfbc",
backgroundprimary: "#1E1E1E",
backgroundsecondary: "#252525",
button: "#2F3C2B",
},
},
Dracula: {
credit: "H3draut3r",
reference: "https://discord.com/channels/415207508303544321/921991895230611466/922296307836678144",
colors: {
primarylight: "#7082B8",
primary: "#F8F8F2",
primarydark: "#FF79C6",
successlight: "#0f0",
success: "#0c0",
successdark: "#090",
errorlight: "#FD4545",
error: "#FF2D2D",
errordark: "#C62424",
secondarylight: "#AAA",
secondary: "#8BE9FD",
secondarydark: "#666",
warninglight: "#FFC281",
warning: "#FFB86C",
warningdark: "#E6A055",
infolight: "#A0A0FF",
info: "#7070FF",
infodark: "#4040FF",
welllight: "#44475A",
well: "#363948",
white: "#fff",
black: "#282A36",
hp: "#D34448",
money: "#50FA7B",
hack: "#F1FA8C",
combat: "#BD93F9",
cha: "#FF79C6",
int: "#6495ed",
rep: "#faffdf",
disabled: "#66cfbc",
backgroundprimary: "#282A36",
backgroundsecondary: "#21222C",
button: "#21222C",
},
},
"Dark Blue": {
credit: "Saynt_Garmo",
reference: "https://discord.com/channels/415207508303544321/921991895230611466/923084732718264340",
colors: {
primarylight: "#023DDE",
primary: "#4A41C8",
primarydark: "#005299",
successlight: "#00FF00",
success: "#D1DAD1",
successdark: "#BFCABF",
errorlight: "#f00",
error: "#c00",
errordark: "#900",
secondarylight: "#AAA",
secondary: "#888",
secondarydark: "#666",
warninglight: "#ff0",
warning: "#cc0",
warningdark: "#990",
infolight: "#69f",
info: "#36c",
infodark: "#039",
welllight: "#444",
well: "#040505",
white: "#fff",
black: "#000000",
hp: "#dd3434",
money: "#ffd700",
hack: "#adff2f",
combat: "#faffdf",
cha: "#a671d1",
int: "#6495ed",
rep: "#faffdf",
disabled: "#66cfbc",
backgroundprimary: "#091419",
backgroundsecondary: "#000000",
button: "#000000",
},
},
});

@ -292,7 +292,7 @@ export function SidebarRoot(props: IProps): React.ReactElement {
} else if (event.keyCode === KEY.W && event.altKey) {
event.preventDefault();
clickCity();
} else if (event.keyCode === KEY.J && event.altKey && !event.ctrlKey && !event.metaKey) {
} else if (event.keyCode === KEY.J && event.altKey && !event.ctrlKey && !event.metaKey && props.player.hasJob()) {
// ctrl/cmd + alt + j is shortcut to open Chrome dev tools
event.preventDefault();
clickJob();

@ -35,7 +35,7 @@ export function removeTrailingSlash(s: string): string {
* Checks whether a string is a valid filename. Only used for the filename itself,
* not the entire filepath
*/
function isValidFilename(filename: string): boolean {
export function isValidFilename(filename: string): boolean {
// Allows alphanumerics, hyphens, underscores, and percentage signs
// Must have a file extension
const regex = /^[.a-zA-Z0-9_-]+[.][a-zA-Z0-9]+(?:-\d+(?:\.\d*)?%-INC)?$/;
@ -48,7 +48,7 @@ function isValidFilename(filename: string): boolean {
* Checks whether a string is a valid directory name. Only used for the directory itself,
* not an entire path
*/
function isValidDirectoryName(name: string): boolean {
export function isValidDirectoryName(name: string): boolean {
// Allows alphanumerics, hyphens, underscores, and percentage signs.
// Name can begin with a single period, but otherwise cannot have any
const regex = /^.?[a-zA-Z0-9_-]+$/;
@ -178,14 +178,14 @@ export function getAllParentDirectories(path: string): string {
* @param cwd The current working directory
* @returns A file path which may be absolute or relative
*/
export function getDestinationFilepath(destination: string, source: string, cwd: string) {
export function getDestinationFilepath(destination: string, source: string, cwd: string): string {
const dstDir = evaluateDirectoryPath(destination, cwd);
// If evaluating the directory for this destination fails, we have a filename or full path.
if (dstDir === null) {
return destination;
} else {
// Append the filename to the directory provided.
let t_path = removeTrailingSlash(dstDir);
const t_path = removeTrailingSlash(dstDir);
const fileName = getFileName(source);
return t_path + "/" + fileName;
}

@ -14,7 +14,7 @@ export const TerminalHelpText: string[] = [
"clear Clear all text on the terminal ",
"cls See 'clear' command ",
"connect [hostname] Connects to a remote server",
"cp [src] [dst]: Copy a file",
"cp [src] [dst] Copy a file",
"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",
@ -29,7 +29,7 @@ export const TerminalHelpText: string[] = [
"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",
"nano [file ...] Text editor - Open up and edit one or more scripts or text files",
"ps Display all scripts that are currently running",
"rm [file] Delete a file from the server",
"run [name] [-t n] [--tail] [args...] Execute a program or script",
@ -40,7 +40,8 @@ export const TerminalHelpText: string[] = [
"tail [script] [args...] Displays dynamic logs for the specified script",
"top Displays all running scripts and their RAM usage",
"unalias [alias name] Deletes the specified alias",
"weaken [server] Reduce the security of a server",
"vim [file ...] Text editor - Open up and edit one or more scripts or text files in vim mode",
"weaken Reduce the security of the current machine",
"wget [url] [target file] Retrieves code/text from a web server",
];
@ -304,9 +305,9 @@ export const HelpTexts: IMap<string[]> = {
"mv myScript.js myOldScript.js",
],
nano: [
"nano [file name]",
"nano [file ...]",
" ",
"Opens up the specified file in the Text Editor. Only scripts (.script) or text files (.txt) can be ",
"Opens up the specified file(s) 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",
],
@ -322,7 +323,7 @@ export const HelpTexts: IMap<string[]> = {
run: [
"run [file name] [-t] [num threads] [args...]",
" ",
"Execute a program or a script.",
"Execute a program, script or coding contract.",
" ",
"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, ",
@ -402,6 +403,13 @@ export const HelpTexts: IMap<string[]> = {
" ",
"It is not necessary to differentiate between global and non-global aliases when using 'unalias'",
],
vim: [
"vim [file ...]",
" ",
"Opens up the specified file(s) in the Text Editor in vim mode. 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",
],
weaken: [
"weaken",
"",

@ -67,6 +67,7 @@ import { sudov } from "./commands/sudov";
import { tail } from "./commands/tail";
import { top } from "./commands/top";
import { unalias } from "./commands/unalias";
import { vim } from "./commands/vim";
import { weaken } from "./commands/weaken";
import { wget } from "./commands/wget";
import { hash } from "../hash/hash";
@ -212,13 +213,18 @@ export class Terminal implements ITerminal {
player.gainHackingExp(expGainedOnSuccess);
player.gainIntelligenceExp(expGainedOnSuccess / CONSTANTS.IntelligenceTerminalHackBaseExpGain);
const oldSec = server.hackDifficulty;
server.fortify(CONSTANTS.ServerFortifyAmount);
const newSec = server.hackDifficulty;
this.print(
`Hack successful! Gained ${numeralWrapper.formatMoney(moneyGained)} and ${numeralWrapper.formatExp(
expGainedOnSuccess,
)} hacking exp`,
);
this.print(
`Security increased from ${numeralWrapper.formatSecurity(oldSec)} to ${numeralWrapper.formatSecurity(newSec)}`,
);
} else {
// Failure
player.gainHackingExp(expGainedOnFailure);
@ -237,13 +243,18 @@ export class Terminal implements ITerminal {
}
if (!(server instanceof Server)) throw new Error("server should be normal server");
const expGain = calculateHackingExpGain(server, player);
const oldSec = server.hackDifficulty;
const growth = processSingleServerGrowth(server, 25, player, server.cpuCores) - 1;
const newSec = server.hackDifficulty;
this.print(
`Available money on '${server.hostname}' grown by ${numeralWrapper.formatPercentage(
growth,
6,
)}. Gained ${numeralWrapper.formatExp(expGain)} hacking exp.`,
);
this.print(
`Security increased from ${numeralWrapper.formatSecurity(oldSec)} to ${numeralWrapper.formatSecurity(newSec)}`,
);
}
finishWeaken(player: IPlayer, cancelled = false): void {
@ -255,10 +266,14 @@ export class Terminal implements ITerminal {
}
if (!(server instanceof Server)) throw new Error("server should be normal server");
const expGain = calculateHackingExpGain(server, player);
const oldSec = server.hackDifficulty;
server.weaken(CONSTANTS.ServerWeakenAmount);
const newSec = server.hackDifficulty;
this.print(
`'${server.hostname}' security level weakened to ${server.hackDifficulty.toFixed(3)} ` +
`and Gained ${numeralWrapper.formatExp(expGain)} hacking exp.`,
`Security decreased from ${numeralWrapper.formatSecurity(oldSec)} to ${numeralWrapper.formatSecurity(
newSec,
)} (min: ${numeralWrapper.formatSecurity(server.minDifficulty)})` +
` and Gained ${numeralWrapper.formatExp(expGain)} hacking exp.`,
);
}
@ -775,6 +790,7 @@ export class Terminal implements ITerminal {
tail: tail,
top: top,
unalias: unalias,
vim: vim,
weaken: weaken,
wget: wget,
};

@ -4,6 +4,7 @@ import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { showMessage } from "../../Message/MessageHelpers";
import { showLiterature } from "../../Literature/LiteratureHelpers";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
export function cat(
terminal: ITerminal,
@ -16,10 +17,11 @@ export function cat(
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")) {
const relative_filename = args[0] + "";
const filename = terminal.getFilepath(relative_filename);
if (!filename.endsWith(".msg") && !filename.endsWith(".lit") && !filename.endsWith(".txt") && !filename.endsWith(".script") && !filename.endsWith(".js") && !filename.endsWith(".ns")) {
terminal.error(
"Only .msg, .txt, and .lit files are viewable with cat (filename must end with .msg, .txt, or .lit)",
"Only .msg, .txt, .lit, .script, .js, and .ns files are viewable with cat (filename must end with .msg, .txt, .lit, .script, .js, or .ns)",
);
return;
}
@ -39,11 +41,17 @@ export function cat(
}
}
} else if (filename.endsWith(".txt")) {
const txt = terminal.getTextFile(player, filename);
const txt = terminal.getTextFile(player, relative_filename);
if (txt != null) {
txt.show();
return;
}
} else if (filename.endsWith(".script") || filename.endsWith(".js") || filename.endsWith(".ns")) {
const script = terminal.getScript(player, relative_filename);
if (script != null) {
dialogBoxCreate(`${script.filename}<br /><br />${script.code}`);
return;
}
}
terminal.error(`No such file ${filename}`);

@ -0,0 +1,68 @@
import { ITerminal } from "../../ITerminal";
import { IRouter, ScriptEditorRouteOptions } from "../../../ui/Router";
import { IPlayer } from "../../../PersonObjects/IPlayer";
import { BaseServer } from "../../../Server/BaseServer";
import { isScriptFilename } from "../../../Script/isScriptFilename";
import { CursorPositions } from "../../../ScriptEditor/CursorPositions";
interface EditorParameters {
terminal: ITerminal;
router: IRouter;
player: IPlayer;
server: BaseServer;
args: (string | number | boolean)[];
}
function isNs2(filename: string): boolean {
return filename.endsWith(".ns") || filename.endsWith(".js");
}
const newNs2Template = `/** @param {NS} ns **/
export async function main(ns) {
}`;
export function commonEditor(
command: string,
{ terminal, router, player, args }: EditorParameters,
scriptEditorRouteOptions?: ScriptEditorRouteOptions,
): void {
if (args.length < 1) {
terminal.error(`Incorrect usage of ${command} command. Usage: ${command} [scriptname]`);
return;
}
try {
const files = args.map((arg) => {
const filename = `${arg}`;
if (isScriptFilename(filename)) {
const filepath = terminal.getFilepath(filename);
const script = terminal.getScript(player, filename);
const fileIsNs2 = isNs2(filename);
const code = script !== null ? script.code : fileIsNs2 ? newNs2Template : "";
if (code === newNs2Template) {
CursorPositions.saveCursor(filename, {
row: 3,
column: 5,
});
}
return [filepath, code];
}
if (filename.endsWith(".txt")) {
const filepath = terminal.getFilepath(filename);
const txt = terminal.getTextFile(player, filename);
return [filepath, txt == null ? "" : txt.text];
}
throw new Error(`Invalid file. Only scripts (.script, .ns, .js), or text files (.txt) can be edited with ${command}`);
});
router.toScriptEditor(Object.fromEntries(files), scriptEditorRouteOptions);
} catch (e) {
terminal.error(`${e}`);
}
}

@ -3,6 +3,7 @@ import { IRouter } from "../../ui/Router";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { getServerOnNetwork } from "../../Server/ServerHelpers";
import { GetServer } from "../../Server/AllServers";
export function connect(
terminal: ITerminal,
@ -13,7 +14,7 @@ export function connect(
): 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]");
terminal.error("Incorrect usage of connect command. Usage: connect [hostname]");
return;
}
@ -28,5 +29,9 @@ export function connect(
}
}
terminal.error("Host not found");
if (GetServer(hostname) !== null) {
terminal.error(`Cannot directly connect to ${hostname}`);
} else {
terminal.error("Host not found");
}
}

@ -43,17 +43,16 @@ export function cp(
terminal.error("src and dst must have the same extension.");
return;
}
const filename = terminal.getFilepath(src);
if (!isScriptFilename(filename) && !filename.endsWith(".txt")) {
if (!isScriptFilename(src) && !src.endsWith(".txt")) {
terminal.error("cp only works for scripts and .txt files");
return;
}
// Scp for txt files
if (filename.endsWith(".txt")) {
if (src.endsWith(".txt")) {
let txtFile = null;
for (let i = 0; i < server.textFiles.length; ++i) {
if (server.textFiles[i].fn === filename) {
if (areFilesEqual(server.textFiles[i].fn, src)) {
txtFile = server.textFiles[i];
break;
}
@ -80,7 +79,7 @@ export function cp(
// Get the current script
let sourceScript = null;
for (let i = 0; i < server.scripts.length; ++i) {
if (filename == server.scripts[i].filename) {
if (areFilesEqual(server.scripts[i].filename, src)) {
sourceScript = server.scripts[i];
break;
}

@ -24,7 +24,7 @@ export function download(
const matchEnding = fn.length == 1 || fn === "*.*" ? null : fn.slice(1); // Treat *.* the same as *
const zip = new JSZip();
// Helper function to zip any file contents whose name matches the pattern
const zipFiles = (fileNames: string[], fileContents: string[]) => {
const zipFiles = (fileNames: string[], fileContents: string[]): void => {
for (let i = 0; i < fileContents.length; ++i) {
let name = fileNames[i];
if (name.startsWith("/")) name = name.slice(1);

@ -17,7 +17,7 @@ export function ls(
terminal.error("Incorrect usage of ls command. Usage: ls [dir] [| grep pattern]");
}
if (numArgs > 5 || numArgs === 3) {
if (numArgs > 4 || numArgs === 2) {
return incorrectUsage();
}
@ -30,12 +30,12 @@ export function ls(
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] !== "|") {
// If there are 3+ arguments, then the last 3 must be for grep
if (numArgs >= 3) {
if (args[numArgs - 2] !== "grep" || args[numArgs - 3] !== "|") {
return incorrectUsage();
}
filter = args[numArgs] + "";
filter = args[numArgs - 1] + "";
}
// If the second argument is not a pipe, then it must be for listing a directory

@ -2,8 +2,8 @@ import { ITerminal } from "../ITerminal";
import { IRouter } from "../../ui/Router";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { isScriptFilename } from "../../Script/isScriptFilename";
import { CursorPositions } from "../../ScriptEditor/CursorPositions";
import {commonEditor} from './common/editor';
export function nano(
terminal: ITerminal,
@ -12,45 +12,5 @@ export function nano(
server: BaseServer,
args: (string | number | boolean)[],
): void {
if (args.length !== 1) {
terminal.error("Incorrect usage of nano command. Usage: nano [scriptname]");
return;
}
try {
const filename = args[0] + "";
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 = `/** @param {NS} ns **/
export async function main(ns) {
}`;
}
CursorPositions.saveCursor(filename, {
row: 3,
column: 5,
});
router.toScriptEditor(filepath, code);
} else {
router.toScriptEditor(filepath, script.code);
}
} else if (filename.endsWith(".txt")) {
const filepath = terminal.getFilepath(filename);
const txt = terminal.getTextFile(player, filename);
if (txt == null) {
router.toScriptEditor(filepath);
} else {
router.toScriptEditor(filepath, txt.text);
}
} else {
terminal.error("Invalid file. Only scripts (.script, .ns, .js), or text files (.txt) can be edited with nano");
return;
}
} catch (e) {
terminal.error(e + "");
}
return commonEditor('nano', {terminal, router, player, server, args});
}

@ -0,0 +1,16 @@
import { ITerminal } from "../ITerminal";
import { IRouter } from "../../ui/Router";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import {commonEditor} from './common/editor';
export function vim(
terminal: ITerminal,
router: IRouter,
player: IPlayer,
server: BaseServer,
args: (string | number | boolean)[],
): void {
return commonEditor('vim', {terminal, router, player, server, args}, {vim: true});
}

@ -49,6 +49,7 @@ const commands = [
"tail",
"theme",
"top",
"vim",
"weaken",
];
@ -265,7 +266,7 @@ export async function determineAllPossibilitiesForTabCompletion(
return allPos;
}
if (isCommand("nano")) {
if (isCommand("nano") || isCommand("vim")) {
addAllScripts();
addAllTextFiles();
addAllDirectories();
@ -353,6 +354,7 @@ export async function determineAllPossibilitiesForTabCompletion(
addAllLitFiles();
addAllTextFiles();
addAllDirectories();
addAllScripts();
return allPos;
}

@ -1,6 +1,7 @@
import { dialogBoxCreate } from "./ui/React/DialogBox";
import { BaseServer } from "./Server/BaseServer";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "./utils/JSONReviver";
import { removeLeadingSlash, isInRootDirectory } from "./Terminal/DirectoryHelpers"
/**
* Represents a plain text file that is typically stored on a server.
@ -101,7 +102,11 @@ Reviver.constructors.TextFile = TextFile;
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function getTextFile(fn: string, server: BaseServer): TextFile | null {
const filename: string = !fn.endsWith(".txt") ? `${fn}.txt` : fn;
let filename: string = !fn.endsWith(".txt") ? `${fn}.txt` : fn;
if (isInRootDirectory(filename)) {
filename = removeLeadingSlash(filename);
}
for (const file of server.textFiles as TextFile[]) {
if (file.fn === filename) {

@ -110,6 +110,23 @@ export function TutorialRoot(props: IProps): React.ReactElement {
>
<Typography>NS1 vs NS2 (or .script vs .js)</Typography>
</Link>
<br />
<Link
color="primary"
target="_blank"
href="https://bitburner.readthedocs.io/en/latest/netscript/netscriptfunctions.html"
>
<Typography>Simplified list of functions</Typography>
</Link>
<br />
<Link
color="primary"
target="_blank"
href="https://github.com/danielyxie/bitburner/blob/dev/markdown/bitburner.ns.md"
>
<Typography>Complete list of functions</Typography>
</Link>
</Box>
</>
);

@ -0,0 +1,24 @@
import { WorkerScript } from "./Netscript/WorkerScript";
import { isScriptErrorMessage } from "./NetscriptEvaluator";
import { dialogBoxCreate } from "./ui/React/DialogBox";
export function setupUncaughtPromiseHandler(): void {
window.addEventListener("unhandledrejection", function (e) {
if (isScriptErrorMessage(e.reason)) {
const errorTextArray = e.reason.split("|DELIMITER|");
const hostname = errorTextArray[1];
const scriptName = errorTextArray[2];
const errorMsg = errorTextArray[3];
let msg = `UNCAUGHT PROMISE ERROR<br>You forgot to await a promise<br>${scriptName}@${hostname}<br>`;
msg += "<br>";
msg += errorMsg;
dialogBoxCreate(msg);
} else if (e.reason instanceof WorkerScript) {
const msg =
`UNCAUGHT PROMISE ERROR<br>You forgot to await a promise<br>${e.reason.name}@${e.reason.hostname}<br>` +
`Maybe hack / grow / weaken ?`;
dialogBoxCreate(msg);
}
});
}

@ -46,6 +46,7 @@ import { exceptionAlert } from "./utils/helpers/exceptionAlert";
import { startExploits } from "./Exploits/loops";
import React from "react";
import { setupUncaughtPromiseHandler } from "./UncaughtPromiseHandler";
const Engine: {
_lastUpdate: number;
@ -237,6 +238,7 @@ const Engine: {
load: function (saveString) {
startExploits();
setupUncaughtPromiseHandler();
// Load game from save or create new game
if (loadGame(saveString)) {
ThemeEvents.emit();

@ -21,12 +21,13 @@
/>
<script>
var require = { paths: { vs: "dist/ext/monaco-editor/min/vs" } };
var require = { paths: { vs: "dist/ext/monaco-editor/min/vs", "monaco-vim": "dist/ext/monaco-vim" } };
</script>
<script src="dist/ext/monaco-editor/min/vs/loader.js"></script>
<script src="dist/ext/monaco-editor/min/vs/editor/editor.main.nls.js"></script>
<script src="dist/ext/monaco-editor/min/vs/editor/editor.main.js"></script>
<script src="dist/ext/monaco-vim.js"></script>
<!-- Google Analytics -->
<script>

@ -33,7 +33,7 @@ import createStyles from "@mui/styles/createStyles";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import { Page, IRouter } from "./Router";
import { Page, IRouter, ScriptEditorRouteOptions } from "./Router";
import { Overview } from "./React/Overview";
import { SidebarRoot } from "../Sidebar/ui/SidebarRoot";
import { AugmentationsRoot } from "../Augmentation/ui/AugmentationsRoot";
@ -95,9 +95,6 @@ const useStyles = makeStyles((theme: Theme) =>
}),
);
let filename = "";
let code = "";
export let Router: IRouter = {
page: () => {
throw new Error("Router called before initialization");
@ -196,6 +193,7 @@ function determineStartPage(player: IPlayer): Page {
export function GameRoot({ player, engine, terminal }: IProps): React.ReactElement {
const classes = useStyles();
const [{files, vim}, setEditorOptions] = useState({files: {}, vim: false})
const [page, setPage] = useState(determineStartPage(player));
const setRerender = useState(0)[1];
const [faction, setFaction] = useState<Faction>(
@ -246,9 +244,11 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
toHacknetNodes: () => setPage(Page.Hacknet),
toMilestones: () => setPage(Page.Milestones),
toResleeves: () => setPage(Page.Resleeves),
toScriptEditor: (fn: string, c: string) => {
filename = fn;
code = c;
toScriptEditor: (files: Record<string, string>, options?: ScriptEditorRouteOptions) => {
setEditorOptions({
files,
vim: !!options?.vim,
});
setPage(Page.ScriptEditor);
},
toSleeves: () => setPage(Page.Sleeves),
@ -290,8 +290,6 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
};
useEffect(() => {
filename = "";
code = "";
if (page !== Page.Terminal) window.scrollTo(0, 0);
});
@ -319,7 +317,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
) : (
<Box display="flex" flexDirection="row" width="100%">
<SidebarRoot player={player} router={Router} page={page} />
<Box className={classes.root} flexGrow={1} display="block" px={1} height="100vh">
<Box className={classes.root} flexGrow={1} display="block" px={1} min-height="100vh">
{page === Page.Terminal ? (
<TerminalRoot terminal={terminal} router={Router} player={player} />
) : page === Page.Sleeves ? (
@ -330,11 +328,11 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
<CharacterStats />
) : page === Page.ScriptEditor ? (
<ScriptEditorRoot
filename={filename}
code={code}
files={files}
hostname={player.getCurrentServer().hostname}
player={player}
router={Router}
vim={vim}
/>
) : page === Page.ActiveScripts ? (
<ActiveScriptsRoot workerScripts={workerScripts} />

@ -32,6 +32,16 @@ export function AlertManager(): React.ReactElement {
[],
);
useEffect(() => {
function handle(this: Document, event: KeyboardEvent): void {
if (event.code === "Escape") {
setAlerts([]);
}
}
document.addEventListener("keydown", handle);
return () => document.removeEventListener("keydown", handle);
}, []);
function close(): void {
setAlerts((old) => {
return old.slice(1, 1e99);

@ -15,6 +15,7 @@ import { workerScripts } from "../../Netscript/WorkerScripts";
import { startWorkerScript } from "../../NetscriptWorker";
import { GetServer } from "../../Server/AllServers";
import { Theme } from "@mui/material";
import { findRunningScript } from "../../Script/ScriptHelpers";
let layerCounter = 0;
@ -102,6 +103,7 @@ const useStyles = makeStyles((theme: Theme) =>
);
function LogWindow(props: IProps): React.ReactElement {
const [script, setScript] = useState(props.script);
const classes = useStyles();
const container = useRef<HTMLDivElement>(null);
const setRerender = useState(false)[1];
@ -116,13 +118,18 @@ function LogWindow(props: IProps): React.ReactElement {
}, []);
function kill(): void {
killWorkerScript(props.script, props.script.server, true);
killWorkerScript(script, script.server, true);
}
function run(): void {
const server = GetServer(props.script.server);
const server = GetServer(script.server);
if (server === null) return;
startWorkerScript(props.script, server);
const s = findRunningScript(script.filename, script.args, server);
if (s === null) {
startWorkerScript(script, server);
} else {
setScript(s);
}
}
function updateLayer(): void {
@ -135,7 +142,7 @@ function LogWindow(props: IProps): React.ReactElement {
function title(): string {
const maxLength = 30;
const t = `${props.script.filename} ${props.script.args.map((x: any): string => `${x}`).join(" ")}`;
const t = `${script.filename} ${script.args.map((x: any): string => `${x}`).join(" ")}`;
if (t.length <= maxLength) {
return t;
}
@ -183,8 +190,8 @@ function LogWindow(props: IProps): React.ReactElement {
</Typography>
<Box position="absolute" right={0}>
{!workerScripts.has(props.script.pid) && <Button onClick={run}>Run</Button>}
{workerScripts.has(props.script.pid) && <Button onClick={kill}>Kill</Button>}
{!workerScripts.has(script.pid) && <Button onClick={run}>Run</Button>}
{workerScripts.has(script.pid) && <Button onClick={kill}>Kill</Button>}
<Button onClick={props.onClose}>Close</Button>
</Box>
</Box>
@ -201,7 +208,7 @@ function LogWindow(props: IProps): React.ReactElement {
}
>
<Box>
{props.script.logs.map(
{script.logs.map(
(line: string, i: number): JSX.Element => (
<Typography key={i} className={lineClass(line)}>
{line}

@ -43,8 +43,10 @@ export function PromptManager(): React.ReactElement {
{prompt != null && (
<Modal open={true} onClose={close}>
<Typography>{prompt.txt}</Typography>
<Button onClick={yes}>Yes</Button>
<Button onClick={no}>No</Button>
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', paddingTop: '10px' }}>
<Button style={{ marginRight: 'auto' }} onClick={yes}>Yes</Button>
<Button onClick={no}>No</Button>
</div>
</Modal>
)}
</>

@ -16,16 +16,17 @@ export function SnackbarProvider(props: IProps): React.ReactElement {
);
}
export const SnackbarEvents = new EventEmitter<[string, "success" | "warning" | "error" | "info"]>();
export const SnackbarEvents = new EventEmitter<[string, "success" | "warning" | "error" | "info", number]>();
export function Snackbar(): React.ReactElement {
const { enqueueSnackbar } = useSnackbar();
useEffect(() =>
SnackbarEvents.subscribe((s, variant) =>
SnackbarEvents.subscribe((s, variant, duration) =>
enqueueSnackbar(<Alert severity={variant}>{s}</Alert>, {
content: (k, m) => <Paper key={k}>{m}</Paper>,
variant: variant,
autoHideDuration: duration,
}),
),
);

@ -1,6 +1,6 @@
import * as React from "react";
import LinearProgress from "@mui/material/LinearProgress";
import { TableCell, Tooltip } from "@mui/material";
import { TableCell, Tooltip, Typography } from "@mui/material";
import { characterOverviewStyles } from "./CharacterOverview";
import { ISkillProgress } from "src/PersonObjects/formulas/skill";
import { numeralWrapper } from "../numeralFormat";
@ -9,6 +9,7 @@ interface IProgressProps {
min: number;
max: number;
current: number;
progress: number;
color?: React.CSSProperties["color"];
}
@ -17,21 +18,22 @@ interface IStatsOverviewCellProps {
color?: React.CSSProperties["color"];
}
export function StatsProgressBar({ min, max, current, color }: IProgressProps): React.ReactElement {
const normalise = (value: number): number => ((value - min) * 100) / (max - min);
const tooltipText = (
<>
Experience: {numeralWrapper.formatExp(current)}/{numeralWrapper.formatExp(max)}
export function StatsProgressBar({ min, max, current, progress, color }: IProgressProps): React.ReactElement {
const tooltip = (
<Typography sx={{ textAlign: 'right' }}>
<strong>Progress:</strong>&nbsp;
{numeralWrapper.formatExp(current - min)} / {numeralWrapper.formatExp(max - min)}
<br />
{normalise(current).toFixed(2)}%
</>
<strong>Remaining:</strong>&nbsp;
{numeralWrapper.formatExp(max - current)} ({progress.toFixed(2)}%)
</Typography>
);
return (
<Tooltip title={tooltipText}>
<Tooltip title={tooltip}>
<LinearProgress
variant="determinate"
value={normalise(current)}
value={progress}
sx={{
backgroundColor: "#111111",
"& .MuiLinearProgress-bar1Determinate": {
@ -43,7 +45,7 @@ export function StatsProgressBar({ min, max, current, color }: IProgressProps):
);
}
export function StatsProgressOverviewCell({ progress, color }: IStatsOverviewCellProps): React.ReactElement {
export function StatsProgressOverviewCell({ progress: skill, color }: IStatsOverviewCellProps): React.ReactElement {
const classes = characterOverviewStyles();
return (
<TableCell
@ -54,9 +56,10 @@ export function StatsProgressOverviewCell({ progress, color }: IStatsOverviewCel
style={{ paddingBottom: "2px", position: "relative", top: "-3px" }}
>
<StatsProgressBar
min={progress.baseExperience}
max={progress.nextExperience}
current={progress.experience}
min={skill.baseExperience}
max={skill.nextExperience}
current={skill.experience}
progress={skill.progress}
color={color}
/>
</TableCell>

@ -7,9 +7,12 @@ import Paper from "@mui/material/Paper";
import TextField from "@mui/material/TextField";
import IconButton from "@mui/material/IconButton";
import ReplyIcon from "@mui/icons-material/Reply";
import PaletteSharpIcon from "@mui/icons-material/PaletteSharp";
import { Color, ColorPicker } from "material-ui-color";
import { ThemeEvents } from "./Theme";
import { Settings, defaultSettings } from "../../Settings/Settings";
import { getPredefinedThemes } from "../../Settings/Themes";
import { UserInterfaceTheme } from "../../ScriptEditor/NetscriptDefinitions";
interface IProps {
open: boolean;
@ -64,11 +67,31 @@ export function ThemeEditorModal(props: IProps): React.ReactElement {
...Settings.theme,
});
function resetTheme(): void {
setCustomTheme({
...defaultSettings.theme,
});
Object.assign(Settings.theme, defaultSettings.theme);
const predefinedThemes = getPredefinedThemes();
const themes = predefinedThemes && Object.entries(predefinedThemes)
.map(([key, templateTheme]) => {
const name = templateTheme.name || key;
let inner = <Typography>{name}</Typography>;
let toolTipTitle;
if (templateTheme.credit) {
toolTipTitle = <Typography>{templateTheme.description || name} <em>by {templateTheme.credit}</em></Typography>;
} else if (templateTheme.description) {
toolTipTitle = <Typography>{templateTheme.description}</Typography>;
}
if (toolTipTitle) {
inner = <Tooltip title={toolTipTitle}>{inner}</Tooltip>
}
return (
<Button onClick={() => setTemplateTheme(templateTheme.colors)}
startIcon={<PaletteSharpIcon />} key={key} sx={{ mr: 1, mb: 1 }}>
{inner}
</Button>
);
}) || <></>;
function setTheme(theme: UserInterfaceTheme): void {
setCustomTheme(theme);
Object.assign(Settings.theme, theme);
ThemeEvents.emit();
}
@ -96,250 +119,262 @@ export function ThemeEditorModal(props: IProps): React.ReactElement {
ThemeEvents.emit();
}
function setTemplateTheme(theme: UserInterfaceTheme): void {
setTheme(theme);
}
return (
<Modal open={props.open} onClose={props.onClose}>
<Paper>
<Tooltip open={true} placement={"top"} title={<Typography>Example tooltip</Typography>}>
<Button color="primary">primary button</Button>
</Tooltip>
<Button color="secondary">secondary button</Button>
<Button color="warning">warning button</Button>
<Button color="info">info button</Button>
<Button color="error">error button</Button>
<Button disabled>disabled button</Button>
<Typography color="primary">text with primary color</Typography>
<Typography color="secondary">text with secondary color</Typography>
<Typography color="error">text with error color</Typography>
<TextField value={"Text field"} />
<Paper sx={{ px: 1, py: 1, my: 1 }}>
<Tooltip open={true} placement={"top"} title={<Typography>Example tooltip</Typography>}>
<Button color="primary" size="small">primary button</Button>
</Tooltip>
<Button color="secondary" size="small">secondary button</Button>
<Button color="warning" size="small">warning button</Button>
<Button color="info" size="small">info button</Button>
<Button color="error" size="small">error button</Button>
<Button disabled size="small">disabled button</Button>
<br />
<Typography color="primary" variant="caption">text with primary color</Typography>&nbsp;
<Typography color="secondary" variant="caption">text with secondary color</Typography>&nbsp;
<Typography color="error" variant="caption">text with error color</Typography>
<br />
<TextField value={"Text field"} size="small" />
</Paper>
<br />
<ColorEditor
name="primarylight"
onColorChange={onColorChange}
color={customTheme["primarylight"]}
defaultColor={defaultSettings.theme["primarylight"]}
/>
<ColorEditor
name="primary"
onColorChange={onColorChange}
color={customTheme["primary"]}
defaultColor={defaultSettings.theme["primary"]}
/>
<ColorEditor
name="primarydark"
onColorChange={onColorChange}
color={customTheme["primarydark"]}
defaultColor={defaultSettings.theme["primarydark"]}
/>
<br />
<Paper sx={{ py: 1, my: 1 }}>
<ColorEditor
name="primarylight"
onColorChange={onColorChange}
color={customTheme["primarylight"]}
defaultColor={defaultSettings.theme["primarylight"]}
/>
<ColorEditor
name="primary"
onColorChange={onColorChange}
color={customTheme["primary"]}
defaultColor={defaultSettings.theme["primary"]}
/>
<ColorEditor
name="primarydark"
onColorChange={onColorChange}
color={customTheme["primarydark"]}
defaultColor={defaultSettings.theme["primarydark"]}
/>
<ColorEditor
name="successlight"
onColorChange={onColorChange}
color={customTheme["successlight"]}
defaultColor={defaultSettings.theme["successlight"]}
/>
<ColorEditor
name="success"
onColorChange={onColorChange}
color={customTheme["success"]}
defaultColor={defaultSettings.theme["success"]}
/>
<ColorEditor
name="successdark"
onColorChange={onColorChange}
color={customTheme["successdark"]}
defaultColor={defaultSettings.theme["successdark"]}
/>
<br />
<ColorEditor
name="successlight"
onColorChange={onColorChange}
color={customTheme["successlight"]}
defaultColor={defaultSettings.theme["successlight"]}
/>
<ColorEditor
name="success"
onColorChange={onColorChange}
color={customTheme["success"]}
defaultColor={defaultSettings.theme["success"]}
/>
<ColorEditor
name="successdark"
onColorChange={onColorChange}
color={customTheme["successdark"]}
defaultColor={defaultSettings.theme["successdark"]}
/>
<br />
<ColorEditor
name="errorlight"
onColorChange={onColorChange}
color={customTheme["errorlight"]}
defaultColor={defaultSettings.theme["errorlight"]}
/>
<ColorEditor
name="error"
onColorChange={onColorChange}
color={customTheme["error"]}
defaultColor={defaultSettings.theme["error"]}
/>
<ColorEditor
name="errordark"
onColorChange={onColorChange}
color={customTheme["errordark"]}
defaultColor={defaultSettings.theme["errordark"]}
/>
<br />
<ColorEditor
name="errorlight"
onColorChange={onColorChange}
color={customTheme["errorlight"]}
defaultColor={defaultSettings.theme["errorlight"]}
/>
<ColorEditor
name="error"
onColorChange={onColorChange}
color={customTheme["error"]}
defaultColor={defaultSettings.theme["error"]}
/>
<ColorEditor
name="errordark"
onColorChange={onColorChange}
color={customTheme["errordark"]}
defaultColor={defaultSettings.theme["errordark"]}
/>
<br />
<ColorEditor
name="secondarylight"
onColorChange={onColorChange}
color={customTheme["secondarylight"]}
defaultColor={defaultSettings.theme["secondarylight"]}
/>
<ColorEditor
name="secondary"
onColorChange={onColorChange}
color={customTheme["secondary"]}
defaultColor={defaultSettings.theme["secondary"]}
/>
<ColorEditor
name="secondarydark"
onColorChange={onColorChange}
color={customTheme["secondarydark"]}
defaultColor={defaultSettings.theme["secondarydark"]}
/>
<br />
<ColorEditor
name="secondarylight"
onColorChange={onColorChange}
color={customTheme["secondarylight"]}
defaultColor={defaultSettings.theme["secondarylight"]}
/>
<ColorEditor
name="secondary"
onColorChange={onColorChange}
color={customTheme["secondary"]}
defaultColor={defaultSettings.theme["secondary"]}
/>
<ColorEditor
name="secondarydark"
onColorChange={onColorChange}
color={customTheme["secondarydark"]}
defaultColor={defaultSettings.theme["secondarydark"]}
/>
<br />
<ColorEditor
name="warninglight"
onColorChange={onColorChange}
color={customTheme["warninglight"]}
defaultColor={defaultSettings.theme["warninglight"]}
/>
<ColorEditor
name="warning"
onColorChange={onColorChange}
color={customTheme["warning"]}
defaultColor={defaultSettings.theme["warning"]}
/>
<ColorEditor
name="warningdark"
onColorChange={onColorChange}
color={customTheme["warningdark"]}
defaultColor={defaultSettings.theme["warningdark"]}
/>
<br />
<ColorEditor
name="warninglight"
onColorChange={onColorChange}
color={customTheme["warninglight"]}
defaultColor={defaultSettings.theme["warninglight"]}
/>
<ColorEditor
name="warning"
onColorChange={onColorChange}
color={customTheme["warning"]}
defaultColor={defaultSettings.theme["warning"]}
/>
<ColorEditor
name="warningdark"
onColorChange={onColorChange}
color={customTheme["warningdark"]}
defaultColor={defaultSettings.theme["warningdark"]}
/>
<br />
<ColorEditor
name="infolight"
onColorChange={onColorChange}
color={customTheme["infolight"]}
defaultColor={defaultSettings.theme["infolight"]}
/>
<ColorEditor
name="info"
onColorChange={onColorChange}
color={customTheme["info"]}
defaultColor={defaultSettings.theme["info"]}
/>
<ColorEditor
name="infodark"
onColorChange={onColorChange}
color={customTheme["infodark"]}
defaultColor={defaultSettings.theme["infodark"]}
/>
<br />
<ColorEditor
name="infolight"
onColorChange={onColorChange}
color={customTheme["infolight"]}
defaultColor={defaultSettings.theme["infolight"]}
/>
<ColorEditor
name="info"
onColorChange={onColorChange}
color={customTheme["info"]}
defaultColor={defaultSettings.theme["info"]}
/>
<ColorEditor
name="infodark"
onColorChange={onColorChange}
color={customTheme["infodark"]}
defaultColor={defaultSettings.theme["infodark"]}
/>
<br />
<ColorEditor
name="welllight"
onColorChange={onColorChange}
color={customTheme["welllight"]}
defaultColor={defaultSettings.theme["welllight"]}
/>
<ColorEditor
name="well"
onColorChange={onColorChange}
color={customTheme["well"]}
defaultColor={defaultSettings.theme["well"]}
/>
<ColorEditor
name="white"
onColorChange={onColorChange}
color={customTheme["white"]}
defaultColor={defaultSettings.theme["white"]}
/>
<ColorEditor
name="black"
onColorChange={onColorChange}
color={customTheme["black"]}
defaultColor={defaultSettings.theme["black"]}
/>
<ColorEditor
name="backgroundprimary"
onColorChange={onColorChange}
color={customTheme["backgroundprimary"]}
defaultColor={defaultSettings.theme["backgroundprimary"]}
/>
<ColorEditor
name="backgroundsecondary"
onColorChange={onColorChange}
color={customTheme["backgroundsecondary"]}
defaultColor={defaultSettings.theme["backgroundsecondary"]}
/>
<ColorEditor
name="button"
onColorChange={onColorChange}
color={customTheme["button"]}
defaultColor={defaultSettings.theme["button"]}
/>
<br />
<ColorEditor
name="welllight"
onColorChange={onColorChange}
color={customTheme["welllight"]}
defaultColor={defaultSettings.theme["welllight"]}
/>
<ColorEditor
name="well"
onColorChange={onColorChange}
color={customTheme["well"]}
defaultColor={defaultSettings.theme["well"]}
/>
<ColorEditor
name="white"
onColorChange={onColorChange}
color={customTheme["white"]}
defaultColor={defaultSettings.theme["white"]}
/>
<ColorEditor
name="black"
onColorChange={onColorChange}
color={customTheme["black"]}
defaultColor={defaultSettings.theme["black"]}
/>
<ColorEditor
name="backgroundprimary"
onColorChange={onColorChange}
color={customTheme["backgroundprimary"]}
defaultColor={defaultSettings.theme["backgroundprimary"]}
/>
<ColorEditor
name="backgroundsecondary"
onColorChange={onColorChange}
color={customTheme["backgroundsecondary"]}
defaultColor={defaultSettings.theme["backgroundsecondary"]}
/>
<ColorEditor
name="button"
onColorChange={onColorChange}
color={customTheme["button"]}
defaultColor={defaultSettings.theme["button"]}
/>
<br />
<ColorEditor
name="hp"
onColorChange={onColorChange}
color={customTheme["hp"]}
defaultColor={defaultSettings.theme["hp"]}
/>
<ColorEditor
name="money"
onColorChange={onColorChange}
color={customTheme["money"]}
defaultColor={defaultSettings.theme["money"]}
/>
<ColorEditor
name="hack"
onColorChange={onColorChange}
color={customTheme["hack"]}
defaultColor={defaultSettings.theme["hack"]}
/>
<ColorEditor
name="combat"
onColorChange={onColorChange}
color={customTheme["combat"]}
defaultColor={defaultSettings.theme["combat"]}
/>
<ColorEditor
name="cha"
onColorChange={onColorChange}
color={customTheme["cha"]}
defaultColor={defaultSettings.theme["cha"]}
/>
<ColorEditor
name="int"
onColorChange={onColorChange}
color={customTheme["int"]}
defaultColor={defaultSettings.theme["int"]}
/>
<ColorEditor
name="rep"
onColorChange={onColorChange}
color={customTheme["rep"]}
defaultColor={defaultSettings.theme["rep"]}
/>
<ColorEditor
name="disabled"
onColorChange={onColorChange}
color={customTheme["disabled"]}
defaultColor={defaultSettings.theme["disabled"]}
/>
<br />
<br />
<TextField
label={"import / export theme"}
value={JSON.stringify(customTheme)}
onChange={onThemeChange}
InputProps={{
endAdornment: (
<IconButton onClick={resetTheme} size="large">
<ReplyIcon />
</IconButton>
),
}}
/>
<br />
<ColorEditor
name="hp"
onColorChange={onColorChange}
color={customTheme["hp"]}
defaultColor={defaultSettings.theme["hp"]}
/>
<ColorEditor
name="money"
onColorChange={onColorChange}
color={customTheme["money"]}
defaultColor={defaultSettings.theme["money"]}
/>
<ColorEditor
name="hack"
onColorChange={onColorChange}
color={customTheme["hack"]}
defaultColor={defaultSettings.theme["hack"]}
/>
<ColorEditor
name="combat"
onColorChange={onColorChange}
color={customTheme["combat"]}
defaultColor={defaultSettings.theme["combat"]}
/>
<ColorEditor
name="cha"
onColorChange={onColorChange}
color={customTheme["cha"]}
defaultColor={defaultSettings.theme["cha"]}
/>
<ColorEditor
name="int"
onColorChange={onColorChange}
color={customTheme["int"]}
defaultColor={defaultSettings.theme["int"]}
/>
<ColorEditor
name="rep"
onColorChange={onColorChange}
color={customTheme["rep"]}
defaultColor={defaultSettings.theme["rep"]}
/>
<ColorEditor
name="disabled"
onColorChange={onColorChange}
color={customTheme["disabled"]}
defaultColor={defaultSettings.theme["disabled"]}
/>
</Paper>
<Paper sx={{ px: 1, py: 1, my: 1 }}>
<TextField
sx={{ mb: 1 }}
multiline
fullWidth
maxRows={3}
label={"import / export theme"}
value={JSON.stringify(customTheme)}
onChange={onThemeChange}
/>
<>
<Typography sx={{ my: 1 }}>Backup your theme or share it with others by copying the string above.</Typography>
<Typography sx={{ my: 1 }}>Replace the current theme with a pre-built template using the buttons below.</Typography>
{themes}
</>
</Paper>
</Modal>
);
}

@ -38,6 +38,10 @@ export enum Page {
Recovery,
}
export interface ScriptEditorRouteOptions {
vim: boolean;
}
/**
* This class keeps track of player navigation/routing within the game.
*/
@ -66,7 +70,7 @@ export interface IRouter {
toJob(): void;
toMilestones(): void;
toResleeves(): void;
toScriptEditor(filename?: string, code?: string): void;
toScriptEditor(files?: Record<string, string>, options?: ScriptEditorRouteOptions): void;
toSleeves(): void;
toStockMarket(): void;
toTerminal(): void;

@ -105,6 +105,10 @@ class NumeralFormatter {
return this.format(n, "0,0");
}
formatSecurity(n: number): string {
return n.toFixed(3);
}
formatRAM(n: number): string {
if (n < 1e3) return this.format(n, "0.00") + "GB";
if (n < 1e6) return this.format(n / 1e3, "0.00") + "TB";

@ -1,3 +1,6 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { jest, describe, expect, test } from '@jest/globals'
import { NetscriptFunctions } from "../../src/NetscriptFunctions";
import { getRamCost, RamCostConstants } from "../../src/Netscript/RamCostGenerator";
import { Environment } from "../../src/Netscript/Environment";
@ -5,6 +8,10 @@ import { RunningScript } from "../../src/Script/RunningScript";
import { Script } from "../../src/Script/Script";
import { SourceFileFlags } from "../../src/SourceFile/SourceFileFlags";
jest.mock(`!!raw-loader!../NetscriptDefinitions.d.ts`, () => '', {
virtual: true,
});
const ScriptBaseCost = RamCostConstants.ScriptBaseRamCost;
describe("Netscript Dynamic RAM Calculation/Generation Tests", function () {
@ -26,8 +33,8 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function () {
}
// Runs a Netscript function and properly catches it if it returns promise
function runPotentiallyAsyncFunction(fn) {
const res = fn();
function runPotentiallyAsyncFunction(fn, ...args) {
const res = fn(...args);
if (res instanceof Promise) {
res.catch(() => undefined);
}
@ -41,7 +48,7 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function () {
* @param {string[]} fnDesc - describes the name of the function being tested,
* including the namespace(s). e.g. ["gang", "getMemberNames"]
*/
async function testNonzeroDynamicRamCost(fnDesc) {
async function testNonzeroDynamicRamCost(fnDesc, ...args) {
if (!Array.isArray(fnDesc)) {
throw new Error("Non-array passed to testNonzeroDynamicRamCost()");
}
@ -52,6 +59,72 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function () {
const runningScript = await createRunningScript(code);
// We don't need a real WorkerScript
const workerScript = {
args: args,
code: code,
dynamicLoadedFns: {},
dynamicRamUsage: RamCostConstants.ScriptBaseRamCost,
env: new Environment(null),
ramUsage: runningScript.ramUsage,
scriptRef: runningScript,
};
workerScript.env.vars = NetscriptFunctions(workerScript);
// Run the function through the workerscript's args
const scope = workerScript.env.vars;
let curr = scope[fnDesc[0]];
for (let i = 1; i < fnDesc.length; ++i) {
if (curr == null) {
throw new Error(`Invalid function specified: [${fnDesc}]`);
}
if (typeof curr === "function") {
break;
}
curr = curr[fnDesc[i]];
}
if (typeof curr === "function") {
// We use a try/catch because the function will probably fail since the game isn't
// actually initialized. Call the fn multiple times to test that repeated calls
// do not incur extra RAM costs.
try {
runPotentiallyAsyncFunction(curr, ...args);
runPotentiallyAsyncFunction(curr, ...args);
runPotentiallyAsyncFunction(curr, ...args);
} catch (e) {}
} else {
throw new Error(`Invalid function specified: [${fnDesc}]`);
}
const fnName = fnDesc[fnDesc.length - 1];
testEquality(workerScript.dynamicRamUsage - ScriptBaseCost, expected);
testEquality(workerScript.dynamicRamUsage, runningScript.ramUsage);
expect(workerScript.dynamicLoadedFns).toHaveProperty(fnName);
}
/**
* Tests that:
* 1. A function has zero RAM cost
* 2. Running the function does NOT update the MockWorkerScript's dynamic RAM calculation
* 3. Running multiple calls of the function does not result in dynamic RAM calculation
* @param {string[]} fnDesc - describes the name of the function being tested,
* including the namespace(s). e.g. ["gang", "getMemberNames"]
*/
async function testZeroDynamicRamCost(fnDesc, skipRun = false) {
if (!Array.isArray(fnDesc)) {
throw new Error("Non-array passed to testZeroDynamicRamCost()");
}
const expected = getRamCost(...fnDesc);
expect(expected).toEqual(0);
if (skipRun) return;
const code = `${fnDesc.join(".")}();`;
const runningScript = await createRunningScript(code);
// We don't need a real WorkerScript
const workerScript = {
args: [],
@ -92,71 +165,6 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function () {
throw new Error(`Invalid function specified: [${fnDesc}]`);
}
const fnName = fnDesc[fnDesc.length - 1];
testEquality(workerScript.dynamicRamUsage - ScriptBaseCost, expected);
testEquality(workerScript.dynamicRamUsage, runningScript.ramUsage);
expect(workerScript.dynamicLoadedFns).toHaveProperty(fnName);
}
/**
* Tests that:
* 1. A function has zero RAM cost
* 2. Running the function does NOT update the MockWorkerScript's dynamic RAM calculation
* 3. Running multiple calls of the function does not result in dynamic RAM calculation
* @param {string[]} fnDesc - describes the name of the function being tested,
* including the namespace(s). e.g. ["gang", "getMemberNames"]
*/
async function testZeroDynamicRamCost(fnDesc) {
if (!Array.isArray(fnDesc)) {
throw new Error("Non-array passed to testZeroDynamicRamCost()");
}
const expected = getRamCost(...fnDesc);
expect(expected).toEqual(0);
const code = `${fnDesc.join(".")}();`;
const runningScript = await createRunningScript(code);
// We don't need a real WorkerScript
const workerScript = {
args: [],
code: code,
dynamicLoadedFns: {},
dynamicRamUsage: RamCostConstants.ScriptBaseRamCost,
env: new Environment(null),
ramUsage: runningScript.ramUsage,
scriptRef: runningScript,
};
workerScript.env.vars = NetscriptFunctions(workerScript);
// Run the function through the workerscript's args
const scope = workerScript.env.vars;
let curr = scope[fnDesc[0]];
for (let i = 1; i < fnDesc.length; ++i) {
if (curr == null) {
throw new Error(`Invalid function specified: [${fnDesc}]`);
}
if (typeof curr === "function") {
break;
}
curr = curr[fnDesc[i]];
}
if (typeof curr === "function") {
// We use a try/catch because the function will probably fail since the game isn't
// actually initialized. Call the fn multiple times to test that repeated calls
// do not incur extra RAM costs.
try {
runPotentiallyAsyncFunction(curr);
runPotentiallyAsyncFunction(curr);
runPotentiallyAsyncFunction(curr);
} catch (e) {}
} else {
throw new Error(`Invalid function specified: [${fndesc}]`);
}
testEquality(workerScript.dynamicRamUsage, ScriptBaseCost);
testEquality(workerScript.dynamicRamUsage, runningScript.ramUsage);
}
@ -188,13 +196,13 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function () {
await testNonzeroDynamicRamCost(f);
});
it("hackAnalyzePercent()", async function () {
const f = ["hackAnalyzePercent"];
it("hackAnalyze()", async function () {
const f = ["hackAnalyze"];
await testNonzeroDynamicRamCost(f);
});
it("hackChance()", async function () {
const f = ["hackChance"];
it("hackAnalyzeChance()", async function () {
const f = ["hackAnalyzeChance"];
await testNonzeroDynamicRamCost(f);
});
@ -285,6 +293,7 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function () {
it("exec()", async function () {
const f = ["exec"];
jest.spyOn(console, 'log').mockImplementation(() => {}); // eslint-disable-line
await testNonzeroDynamicRamCost(f);
});
@ -305,7 +314,7 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function () {
it("exit()", async function () {
const f = ["exit"];
await testZeroDynamicRamCost(f);
await testZeroDynamicRamCost(f, true);
});
it("scp()", async function () {
@ -425,7 +434,7 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function () {
it("purchaseServer()", async function () {
const f = ["purchaseServer"];
await testNonzeroDynamicRamCost(f);
await testNonzeroDynamicRamCost(f, "abc", '64');
});
it("deleteServer()", async function () {
@ -450,32 +459,32 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function () {
it("write()", async function () {
const f = ["write"];
await testNonzeroDynamicRamCost(f);
await testZeroDynamicRamCost(f);
});
it("tryWrite()", async function () {
const f = ["tryWrite"];
await testNonzeroDynamicRamCost(f);
it("tryWritePort()", async function () {
const f = ["tryWritePort"];
await testZeroDynamicRamCost(f);
});
it("read()", async function () {
const f = ["read"];
await testNonzeroDynamicRamCost(f);
await testZeroDynamicRamCost(f);
});
it("peek()", async function () {
const f = ["peek"];
await testNonzeroDynamicRamCost(f);
await testZeroDynamicRamCost(f);
});
it("clear()", async function () {
const f = ["clear"];
await testNonzeroDynamicRamCost(f);
await testZeroDynamicRamCost(f);
});
it("getPortHandle()", async function () {
const f = ["getPortHandle"];
await testNonzeroDynamicRamCost(f);
await testZeroDynamicRamCost(f);
});
it("rm()", async function () {
@ -577,88 +586,88 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function () {
});
describe("TIX API", function () {
it("getStockSymbols()", async function () {
const f = ["getStockSymbols"];
it("stock.getSymbols()", async function () {
const f = ["stock", "getSymbols"];
await testNonzeroDynamicRamCost(f);
});
it("getStockPrice()", async function () {
const f = ["getStockPrice"];
it("stock.getPrice()", async function () {
const f = ["stock", "getPrice"];
await testNonzeroDynamicRamCost(f);
});
it("getStockAskPrice()", async function () {
const f = ["getStockAskPrice"];
it("stock.getBidPrice()", async function () {
const f = ["stock", "getBidPrice"];
await testNonzeroDynamicRamCost(f);
});
it("getStockBidPrice()", async function () {
const f = ["getStockBidPrice"];
it("stock.getBidPrice()", async function () {
const f = ["stock", "getBidPrice"];
await testNonzeroDynamicRamCost(f);
});
it("getStockPosition()", async function () {
const f = ["getStockPosition"];
it("stock.getPosition()", async function () {
const f = ["stock", "getPosition"];
await testNonzeroDynamicRamCost(f);
});
it("getStockMaxShares()", async function () {
const f = ["getStockMaxShares"];
it("stock.getMaxShares()", async function () {
const f = ["stock", "getMaxShares"];
await testNonzeroDynamicRamCost(f);
});
it("buyStock()", async function () {
const f = ["buyStock"];
it("stock.buy()", async function () {
const f = ["stock", "buy"];
await testNonzeroDynamicRamCost(f);
});
it("sellStock()", async function () {
const f = ["sellStock"];
it("stock.sell()", async function () {
const f = ["stock", "sell"];
await testNonzeroDynamicRamCost(f);
});
it("shortStock()", async function () {
const f = ["shortStock"];
it("stock.short()", async function () {
const f = ["stock", "short"];
await testNonzeroDynamicRamCost(f);
});
it("sellShort()", async function () {
const f = ["sellShort"];
it("stock.sellShort()", async function () {
const f = ["stock", "sellShort"];
await testNonzeroDynamicRamCost(f);
});
it("placeOrder()", async function () {
const f = ["placeOrder"];
it("stock.placeOrder()", async function () {
const f = ["stock", "placeOrder"];
await testNonzeroDynamicRamCost(f);
});
it("cancelOrder()", async function () {
const f = ["cancelOrder"];
it("stock.cancelOrder()", async function () {
const f = ["stock", "cancelOrder"];
await testNonzeroDynamicRamCost(f);
});
it("getOrders()", async function () {
const f = ["getOrders"];
it("stock.getOrders()", async function () {
const f = ["stock", "getOrders"];
await testNonzeroDynamicRamCost(f);
});
it("getStockVolatility()", async function () {
const f = ["getStockVolatility"];
it("stock.getVolatility()", async function () {
const f = ["stock", "getVolatility"];
await testNonzeroDynamicRamCost(f);
});
it("getStockForecast()", async function () {
const f = ["getStockForecast"];
it("stock.getForecast()", async function () {
const f = ["stock", "getForecast"];
await testNonzeroDynamicRamCost(f);
});
it("purchase4SMarketData()", async function () {
const f = ["purchase4SMarketData"];
it("stock.purchase4SMarketData()", async function () {
const f = ["stock", "purchase4SMarketData"];
await testNonzeroDynamicRamCost(f);
});
it("purchase4SMarketDataTixApi()", async function () {
const f = ["purchase4SMarketDataTixApi"];
it("stock.purchase4SMarketDataTixApi()", async function () {
const f = ["stock", "purchase4SMarketDataTixApi"];
await testNonzeroDynamicRamCost(f);
});
});
@ -971,11 +980,6 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function () {
await testNonzeroDynamicRamCost(f);
});
it("getCityEstimatedCommunities()", async function () {
const f = ["bladeburner", "getCityEstimatedCommunities"];
await testNonzeroDynamicRamCost(f);
});
it("getCityChaos()", async function () {
const f = ["bladeburner", "getCityChaos"];
await testNonzeroDynamicRamCost(f);

@ -1,3 +1,6 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { jest, describe, expect, test } from '@jest/globals'
import { getRamCost, RamCostConstants } from "../../src/Netscript/RamCostGenerator";
import { calculateRamUsage } from "../../src/Script/RamCalculations";
@ -81,13 +84,13 @@ describe("Netscript Static RAM Calculation/Generation Tests", function () {
await expectNonZeroRamCost(f);
});
it("hackAnalyzePercent()", async function () {
const f = ["hackAnalyzePercent"];
it("hackAnalyze()", async function () {
const f = ["hackAnalyze"];
await expectNonZeroRamCost(f);
});
it("hackChance()", async function () {
const f = ["hackChance"];
it("hackAnalyzeChance()", async function () {
const f = ["hackAnalyzeChance"];
await expectNonZeroRamCost(f);
});
@ -343,32 +346,32 @@ describe("Netscript Static RAM Calculation/Generation Tests", function () {
it("write()", async function () {
const f = ["write"];
await expectNonZeroRamCost(f);
await expectZeroRamCost(f);
});
it("tryWrite()", async function () {
const f = ["tryWrite"];
await expectNonZeroRamCost(f);
it("tryWritePort()", async function () {
const f = ["tryWritePort"];
await expectZeroRamCost(f);
});
it("read()", async function () {
const f = ["read"];
await expectNonZeroRamCost(f);
await expectZeroRamCost(f);
});
it("peek()", async function () {
const f = ["peek"];
await expectNonZeroRamCost(f);
await expectZeroRamCost(f);
});
it("clear()", async function () {
const f = ["clear"];
await expectNonZeroRamCost(f);
await expectZeroRamCost(f);
});
it("getPortHandle()", async function () {
const f = ["getPortHandle"];
await expectNonZeroRamCost(f);
await expectZeroRamCost(f);
});
it("rm()", async function () {
@ -508,98 +511,98 @@ describe("Netscript Static RAM Calculation/Generation Tests", function () {
});
describe("TIX API", function () {
it("getStockSymbols()", async function () {
const f = ["getStockSymbols"];
it("stock.getSymbols()", async function () {
const f = ["stock", "getSymbols"];
await expectNonZeroRamCost(f);
});
it("getStockPrice()", async function () {
const f = ["getStockPrice"];
it("stock.getPrice()", async function () {
const f = ["stock", "getPrice"];
await expectNonZeroRamCost(f);
});
it("getStockAskPrice()", async function () {
const f = ["getStockAskPrice"];
it("stock.getAskPrice()", async function () {
const f = ["stock", "getAskPrice"];
await expectNonZeroRamCost(f);
});
it("getStockBidPrice()", async function () {
const f = ["getStockBidPrice"];
it("stock.getBidPrice()", async function () {
const f = ["stock", "getBidPrice"];
await expectNonZeroRamCost(f);
});
it("getStockPosition()", async function () {
const f = ["getStockPosition"];
it("stock.getPosition()", async function () {
const f = ["stock", "getPosition"];
await expectNonZeroRamCost(f);
});
it("getStockMaxShares()", async function () {
const f = ["getStockMaxShares"];
it("stock.getMaxShares()", async function () {
const f = ["stock", "getMaxShares"];
await expectNonZeroRamCost(f);
});
it("getStockPurchaseCost()", async function () {
const f = ["getStockPurchaseCost"];
it("stock.getPurchaseCost()", async function () {
const f = ["stock", "getPurchaseCost"];
await expectNonZeroRamCost(f);
});
it("getStockSaleGain()", async function () {
const f = ["getStockSaleGain"];
it("stock.getSaleGain()", async function () {
const f = ["stock", "getSaleGain"];
await expectNonZeroRamCost(f);
});
it("buyStock()", async function () {
const f = ["buyStock"];
it("stock.buy()", async function () {
const f = ["stock", "buy"];
await expectNonZeroRamCost(f);
});
it("sellStock()", async function () {
const f = ["sellStock"];
it("stock.sell()", async function () {
const f = ["stock", "sell"];
await expectNonZeroRamCost(f);
});
it("shortStock()", async function () {
const f = ["shortStock"];
it("stock.short()", async function () {
const f = ["stock", "short"];
await expectNonZeroRamCost(f);
});
it("sellShort()", async function () {
const f = ["sellShort"];
it("stock.sellShort()", async function () {
const f = ["stock", "sell"];
await expectNonZeroRamCost(f);
});
it("placeOrder()", async function () {
const f = ["placeOrder"];
it("stock.placeOrder()", async function () {
const f = ["stock", "placeOrder"];
await expectNonZeroRamCost(f);
});
it("cancelOrder()", async function () {
const f = ["cancelOrder"];
it("stock.cancelOrder()", async function () {
const f = ["stock", "cancelOrder"];
await expectNonZeroRamCost(f);
});
it("getOrders()", async function () {
const f = ["getOrders"];
it("stock.getOrders()", async function () {
const f = ["stock", "getOrders"];
await expectNonZeroRamCost(f);
});
it("getStockVolatility()", async function () {
const f = ["getStockVolatility"];
it("stock.getVolatility()", async function () {
const f = ["stock", "getVolatility"];
await expectNonZeroRamCost(f);
});
it("getStockForecast()", async function () {
const f = ["getStockForecast"];
it("stock.getForecast()", async function () {
const f = ["stock", "getForecast"];
await expectNonZeroRamCost(f);
});
it("purchase4SMarketData()", async function () {
const f = ["purchase4SMarketData"];
it("stock.purchase4SMarketData()", async function () {
const f = ["stock", "purchase4SMarketData"];
await expectNonZeroRamCost(f);
});
it("purchase4SMarketDataTixApi()", async function () {
const f = ["purchase4SMarketDataTixApi"];
it("stock.purchase4SMarketDataTixApi()", async function () {
const f = ["stock", "purchase4SMarketDataTixApi"];
await expectNonZeroRamCost(f);
});
});
@ -912,11 +915,6 @@ describe("Netscript Static RAM Calculation/Generation Tests", function () {
await expectNonZeroRamCost(f);
});
it("getCityEstimatedCommunities()", async function () {
const f = ["bladeburner", "getCityEstimatedCommunities"];
await expectNonZeroRamCost(f);
});
it("getCityChaos()", async function () {
const f = ["bladeburner", "getCityChaos"];
await expectNonZeroRamCost(f);

File diff suppressed because it is too large Load Diff

@ -1,19 +1,21 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { jest, describe, expect, test } from '@jest/globals'
import { convertTimeMsToTimeElapsedString } from "../src/utils/StringHelperFunctions";
describe("StringHelperFunctions Tests", function () {
it("transforms strings", () => {
expect(convertTimeMsToTimeElapsedString(1000)).equal("1 seconds");
expect(convertTimeMsToTimeElapsedString(5 * 60 * 1000 + 34 * 1000)).equal("5 minutes 34 seconds");
expect(convertTimeMsToTimeElapsedString(2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000)).equal(
expect(convertTimeMsToTimeElapsedString(1000)).toEqual("1 seconds");
expect(convertTimeMsToTimeElapsedString(5 * 60 * 1000 + 34 * 1000)).toEqual("5 minutes 34 seconds");
expect(convertTimeMsToTimeElapsedString(2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000)).toEqual(
"2 days 5 minutes 34 seconds",
);
expect(convertTimeMsToTimeElapsedString(2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000, true)).equal(
expect(convertTimeMsToTimeElapsedString(2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000, true)).toEqual(
"2 days 5 minutes 34.000 seconds",
);
expect(convertTimeMsToTimeElapsedString(2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000 + 123, true)).equal(
expect(convertTimeMsToTimeElapsedString(2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000 + 123, true)).toEqual(
"2 days 5 minutes 34.123 seconds",
);
expect(convertTimeMsToTimeElapsedString(2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000 + 123.888, true)).equal(
expect(convertTimeMsToTimeElapsedString(2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000 + 123.888, true)).toEqual(
"2 days 5 minutes 34.123 seconds",
);
});

@ -1,3 +1,5 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { jest, describe, expect, test } from '@jest/globals'
import * as dirHelpers from "../../src/Terminal/DirectoryHelpers";
describe("Terminal Directory Tests", function () {

@ -1,3 +1,7 @@
/* eslint-disable no-await-in-loop */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { jest, describe, expect, test } from '@jest/globals'
import { Player } from "../../src/Player";
import { determineAllPossibilitiesForTabCompletion } from "../../src/Terminal/determineAllPossibilitiesForTabCompletion";
import { Server } from "../../src/Server/Server";
@ -5,6 +9,10 @@ import { AddToAllServers, prestigeAllServers } from "../../src/Server/AllServers
import { LocationName } from "../../src/Locations/data/LocationNames";
import { CodingContract } from "../../src/CodingContracts";
jest.mock(`!!raw-loader!../NetscriptDefinitions.d.ts`, () => '', {
virtual: true,
});
describe("determineAllPossibilitiesForTabCompletion", function () {
let closeServer: Server;
let farServer: Server;
@ -41,14 +49,14 @@ describe("determineAllPossibilitiesForTabCompletion", function () {
AddToAllServers(farServer);
});
it("completes the connect command", () => {
const options = determineAllPossibilitiesForTabCompletion(Player, "connect ", 0);
expect(options).equal(["8.8.8.8", "near"]);
it("completes the connect command", async () => {
const options = await determineAllPossibilitiesForTabCompletion(Player, "connect ", 0);
expect(options).toEqual(["near"]);
});
it("completes the buy command", () => {
const options = determineAllPossibilitiesForTabCompletion(Player, "buy ", 0);
expect(options).equal([
it("completes the buy command", async () => {
const options = await determineAllPossibilitiesForTabCompletion(Player, "buy ", 0);
expect(options.sort()).toEqual([
"BruteSSH.exe",
"FTPCrack.exe",
"relaySMTP.exe",
@ -58,45 +66,48 @@ describe("determineAllPossibilitiesForTabCompletion", function () {
"DeepscanV2.exe",
"AutoLink.exe",
"ServerProfiler.exe",
]);
"Formulas.exe",
].sort());
});
it("completes the scp command", () => {
it("completes the scp command", async () => {
Player.getHomeComputer().writeToTextFile("note.txt", "oh hai mark");
Player.getHomeComputer().messages.push("af.lit");
Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark");
const options1 = determineAllPossibilitiesForTabCompletion(Player, "scp ", 0);
expect(options1).equal(["/www/script.js", "af.lit", "note.txt", "www/"]);
const options1 = await determineAllPossibilitiesForTabCompletion(Player, "scp ", 0);
expect(options1).toEqual(["/www/script.js", "af.lit", "note.txt", "www/"]);
const options2 = determineAllPossibilitiesForTabCompletion(Player, "scp note.txt ", 1);
expect(options2).equal([Player.getHomeComputer().ip, "home", "8.8.8.8", "near", "4.4.4.4", "far"]);
const options2 = await determineAllPossibilitiesForTabCompletion(Player, "scp note.txt ", 1);
expect(options2).toEqual(["home", "near", "far"]);
});
it("completes the kill, tail, mem, and check commands", () => {
it("completes the kill, tail, mem, and check commands", async () => {
Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark");
for (const command of ["kill", "tail", "mem", "check"]) {
expect(determineAllPossibilitiesForTabCompletion(Player, `${command} `, 0)).equal(["/www/script.js", "www/"]);
const options = await determineAllPossibilitiesForTabCompletion(Player, `${command} `, 0);
expect(options).toEqual(["/www/script.js", "www/"]);
}
});
it("completes the nano commands", () => {
it("completes the nano commands", async () => {
Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark");
Player.getHomeComputer().writeToTextFile("note.txt", "oh hai mark");
expect(determineAllPossibilitiesForTabCompletion(Player, "nano ", 0)).equal([
const options = await determineAllPossibilitiesForTabCompletion(Player, "nano ", 0);
expect(options).toEqual([
"/www/script.js",
"note.txt",
".fconf",
"www/",
]);
});
it("completes the rm command", () => {
it("completes the rm command", async () => {
Player.getHomeComputer().writeToTextFile("note.txt", "oh hai mark");
Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark");
Player.getHomeComputer().contracts.push(new CodingContract("linklist.cct"));
Player.getHomeComputer().messages.push("asl.msg");
Player.getHomeComputer().messages.push("af.lit");
expect(determineAllPossibilitiesForTabCompletion(Player, "rm ", 0)).equal([
const options = await determineAllPossibilitiesForTabCompletion(Player, "rm ", 0);
expect(options).toEqual([
"/www/script.js",
"NUKE.exe",
"af.lit",
@ -106,10 +117,11 @@ describe("determineAllPossibilitiesForTabCompletion", function () {
]);
});
it("completes the run command", () => {
it("completes the run command", async () => {
Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark");
Player.getHomeComputer().contracts.push(new CodingContract("linklist.cct"));
expect(determineAllPossibilitiesForTabCompletion(Player, "run ", 0)).equal([
const options = await determineAllPossibilitiesForTabCompletion(Player, "run ", 0);
expect(options).toEqual([
"/www/script.js",
"NUKE.exe",
"linklist.cct",
@ -117,11 +129,12 @@ describe("determineAllPossibilitiesForTabCompletion", function () {
]);
});
it("completes the cat command", () => {
it("completes the cat command", async () => {
Player.getHomeComputer().writeToTextFile("/www/note.txt", "oh hai mark");
Player.getHomeComputer().messages.push("asl.msg");
Player.getHomeComputer().messages.push("af.lit");
expect(determineAllPossibilitiesForTabCompletion(Player, "cat ", 0)).equal([
const options = await determineAllPossibilitiesForTabCompletion(Player, "cat ", 0);
expect(options).toEqual([
"asl.msg",
"af.lit",
"/www/note.txt",
@ -129,11 +142,13 @@ describe("determineAllPossibilitiesForTabCompletion", function () {
]);
});
it("completes the download and mv commands", () => {
it("completes the download and mv commands", async () => {
Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark");
Player.getHomeComputer().writeToTextFile("note.txt", "oh hai mark");
for (const command of ["download", "mv"]) {
expect(determineAllPossibilitiesForTabCompletion(Player, `${command} `, 0)).equal([
const options = await determineAllPossibilitiesForTabCompletion(Player, `${command} `, 0);
expect(options).toEqual([
"/www/script.js",
"note.txt",
"www/",
@ -141,21 +156,24 @@ describe("determineAllPossibilitiesForTabCompletion", function () {
}
});
it("completes the cd command", () => {
it("completes the cd command", async () => {
Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark");
expect(determineAllPossibilitiesForTabCompletion(Player, "cd ", 0)).equal(["www/"]);
const options = await determineAllPossibilitiesForTabCompletion(Player, "cd ", 0);
expect(options).toEqual(["www/"]);
});
it("completes the ls and cd commands", () => {
it("completes the ls and cd commands", async () => {
Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark");
for (const command of ["ls", "cd"]) {
expect(determineAllPossibilitiesForTabCompletion(Player, `${command} `, 0)).equal(["www/"]);
const options = await determineAllPossibilitiesForTabCompletion(Player, `${command} `, 0);
expect(options).toEqual(["www/"]);
}
});
it("completes commands starting with ./", () => {
it("completes commands starting with ./", async () => {
Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark");
expect(determineAllPossibilitiesForTabCompletion(Player, "run ./", 0)).equal([
const options = await determineAllPossibilitiesForTabCompletion(Player, "run ./", 0);
expect(options).toEqual([
".//www/script.js",
"NUKE.exe",
"./www/",