Merge pull request #1466 from danielyxie/dev

v0.56.0
This commit is contained in:
hydroflame 2021-10-12 00:29:45 -04:00 committed by GitHub
commit 4c0d96f572
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 4770 additions and 2775 deletions

@ -99,7 +99,6 @@ module.exports = {
"no-catch-shadow": ["error"], "no-catch-shadow": ["error"],
"no-class-assign": ["error"], "no-class-assign": ["error"],
"no-compare-neg-zero": ["error"], "no-compare-neg-zero": ["error"],
"no-cond-assign": ["off", "except-parens"],
"no-confusing-arrow": ["error"], "no-confusing-arrow": ["error"],
"no-console": ["off"], "no-console": ["off"],
"no-const-assign": ["error"], "no-const-assign": ["error"],

@ -84,6 +84,28 @@ changes are okay to contribute:
- Changes that directly affect the game's balance - Changes that directly affect the game's balance
- New gameplay mechanics - New gameplay mechanics
### How to setup fork properly
Fork and clone the repo
```
# This will add the game original code as a repo in your local copy
$ git remote add danielyxie git@github.com:danielyxie/bitburner.git
# You can verify you did this right by doing the following command
$ git remote show
danielyxie
origin
# Then download all the branches from the game. (there might be more branches)
$ git fetch danielyxie
From github.com:danielyxie/bitburner
* [new branch] dev -> danielyxie/dev
* [new branch] master -> danielyxie/master
# Makes sure you always start from `danielyxie/dev` to avoid merge conflicts.
```
#### Submitting a Pull Request #### Submitting a Pull Request
When submitting a pull request with your code contributions, please abide by When submitting a pull request with your code contributions, please abide by

40
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

@ -3,6 +3,109 @@
Changelog Changelog
========= =========
v0.56.0 - 2021-10-11 Trimming the backlog (hydroflame & community)
-------------------------------------------
** BREAKING **
* The 'write' function is now async. This helps when making scripts that write scripts.
** Terminal **
* 'grow' and 'weaken' have been added as terminal command. This should help player transition
from commands to scripts. The tutorial also talks about it.
* 'cp' command added
* Improved performance by rate limiting refresh.
** IP vs Hostname **
* The game now uses hostname as primary key for it's servers (yeah believe it or not IPs were
used until then). This has caused some issues with purchased servers (they couldn't be sold).
You might need to soft reset for the game to fully convert itself.
** Sleeve **
* Fixed bug where they couldn't train at Volhaven.
* No longer consume all bonus time at once, making it look buggy.
** SF9 **
* Now boosts hacknet production by 8/12/14%
** Hacknet Servers **
* production nerfed by 10%
* Max money increase gets weaker above 10t max money
** Corporation **
* Warehouse tooltip now also displays the amount of space taken by products.
* Changed research box completely to avoid dependency on Treant (Treant is a pita)
* All textbox should accept MAX/MP case insensitive.
* Fixed export popup not refreshing dropdowns correctly.
* Fixed product mku becoming zero
* Increased scaling of Wilson to avoid feedback loop.
* Can no longer get in debt by buying real estate
* Bonus time is consumed faster.
** Netscript **
* isBusy takes bitverse and infiltration into account
* hospitalize can't be called when in infiltration.
* setToCommitCrime now accepts crime rough name instead of perfect name.
* disableLog All now works for bladeburner functions.
* Fixed netscript port for ns1.
** Augmentation **
* Added augmentation to Ti Di Hui that removes penalty for being unfocused.
* Neuroflux no longer appears in special factions.
** Script Editor **
* Ram check is debounced instead of refreshed every second.
* Added the vscode extension documentation to the game (it doesn't work well, thought)
* Fixed issue where autocomplete list would grow forever
* Added semi-monokai as theme.
* Fixed issue where modifying filename would mess it up.
* Font size can be changed now.
** Infiltration **
* Fixed issue where game controls would become unfocused.
** Misc. **
* Fixed loader incorrectly assuming some null values are incorrect.
* installBackdoor trigger Bitverse sequence
* Some improvements to the theme editor
* Improved documentation about where to learn javascript.
* Added some instructions for contributors.
* Fixed typo in corporation sell shares modal (@Saynt_Garmo)
* Fixed pagination being black on black in Active Scripts
* Create Script tab renamed to Script Editor
* Fixed an issue where corp some textbox wouldn't update when changing city.
* Fixed an issue where hacknet online time was always 0.
* Netscript function prompt fixed.
* Fixed miscalculation in growth.
* Script with syntax errors will try to be a tad more helpful.
* Corporations can no longer bribe bladeburners.
* Augmentation Graphene Branchiblade renamed to Brachi, like the rest of them.
* All ram is displayed in GB/TB/PB now.
* Game now saves when saving a file, this can be turned off.
* Several improvement to log window.
* Bladeburner current action returns General type instead of the name of the action.
* Bladeburner travel and Sleeve travel respect disable ASCII.
* Tutorial fits on small screens.
* Import is much slower but more consistent now.
* Fix intelligence not updating properly.
* Added SF -1: Time Compression
* ReadTheDoc theme now matches the game.
* Logbox should wrap text better
* Logbox behavior should feel better.
* Fix font for AutoLink.exe
* nerf noodle bar
v0.55.0 - 2021-09-20 Material UI (hydroflame & community) v0.55.0 - 2021-09-20 Material UI (hydroflame & community)
------------------------------------------- -------------------------------------------

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4391
main.css

File diff suppressed because it is too large Load Diff

@ -1,7 +1,7 @@
{ {
"name": "bitburner", "name": "bitburner",
"license": "SEE LICENSE IN license.txt", "license": "SEE LICENSE IN license.txt",
"version": "0.53.0", "version": "0.56.0",
"main": "electron-main.js", "main": "electron-main.js",
"author": { "author": {
"name": "Daniel Xie" "name": "Daniel Xie"

@ -2,25 +2,25 @@ const numSpaces = 4;
const maxLineLength = 160; const maxLineLength = 160;
module.exports = { module.exports = {
"env": { env: {
"es6": true, es6: true,
"node": true node: true,
}, },
"extends": "eslint:recommended", extends: "eslint:recommended",
"parserOptions": { parserOptions: {
"ecmaFeatures": { ecmaFeatures: {
"experimentalObjectRestSpread": true experimentalObjectRestSpread: true,
}, },
"ecmaVersion": 8, ecmaVersion: 8,
"sourceType": "module" sourceType: "module",
}, },
"rules": { rules: {
"accessor-pairs": [ "accessor-pairs": [
"error", "error",
{ {
"getWithoutSet": false, getWithoutSet: false,
"setWithoutGet": true setWithoutGet: true,
} },
], ],
"array-bracket-newline": ["error"], "array-bracket-newline": ["error"],
"array-bracket-spacing": ["error"], "array-bracket-spacing": ["error"],
@ -33,50 +33,35 @@ module.exports = {
"block-spacing": ["error"], "block-spacing": ["error"],
"brace-style": ["error"], "brace-style": ["error"],
"callback-return": ["error"], "callback-return": ["error"],
"camelcase": ["error"], camelcase: ["error"],
"capitalized-comments": ["error"], "capitalized-comments": ["error"],
"class-methods-use-this": ["error"], "class-methods-use-this": ["error"],
"comma-dangle": ["error"], "comma-dangle": ["error"],
"comma-spacing": ["error"], "comma-spacing": ["error"],
"comma-style": [ "comma-style": ["error", "last"],
"error", complexity: ["error"],
"last" "computed-property-spacing": ["error", "never"],
],
"complexity": ["error"],
"computed-property-spacing": [
"error",
"never"
],
"consistent-return": ["error"], "consistent-return": ["error"],
"consistent-this": ["error"], "consistent-this": ["error"],
"constructor-super": ["error"], "constructor-super": ["error"],
"curly": ["error"], curly: ["error"],
"default-case": ["error"], "default-case": ["error"],
"dot-location": [ "dot-location": ["error", "property"],
"error",
"property"
],
"dot-notation": ["error"], "dot-notation": ["error"],
"eol-last": ["error"], "eol-last": ["error"],
"eqeqeq": ["error"], eqeqeq: ["error"],
"for-direction": ["error"], "for-direction": ["error"],
"func-call-spacing": ["error"], "func-call-spacing": ["error"],
"func-name-matching": ["error"], "func-name-matching": ["error"],
"func-names": [ "func-names": ["error", "never"],
"error",
"never"
],
"func-style": ["error"], "func-style": ["error"],
"function-paren-newline": ["error"], "function-paren-newline": ["error"],
"generator-star-spacing": [ "generator-star-spacing": ["error", "before"],
"error",
"before"
],
"getter-return": [ "getter-return": [
"error", "error",
{ {
"allowImplicit": false allowImplicit: false,
} },
], ],
"global-require": ["error"], "global-require": ["error"],
"guard-for-in": ["error"], "guard-for-in": ["error"],
@ -84,52 +69,37 @@ module.exports = {
"id-blacklist": ["error"], "id-blacklist": ["error"],
"id-length": ["error"], "id-length": ["error"],
"id-match": ["error"], "id-match": ["error"],
"implicit-arrow-linebreak": [ "implicit-arrow-linebreak": ["error", "beside"],
"error", indent: [
"beside"
],
"indent": [
"error", "error",
numSpaces, numSpaces,
{ {
"SwitchCase": 1 SwitchCase: 1,
} },
], ],
"init-declarations": ["error"], "init-declarations": ["error"],
"jsx-quotes": ["error"], "jsx-quotes": ["error"],
"key-spacing": ["error"], "key-spacing": ["error"],
"keyword-spacing": ["error"], "keyword-spacing": ["error"],
"line-comment-position": ["error"], "line-comment-position": ["error"],
"linebreak-style": [ "linebreak-style": ["error", "windows"],
"error",
"windows"
],
"lines-around-comment": ["error"], "lines-around-comment": ["error"],
"lines-between-class-members": ["error"], "lines-between-class-members": ["error"],
"max-depth": ["error"], "max-depth": ["error"],
"max-len": [ "max-len": ["error", maxLineLength],
"error",
maxLineLength
],
"max-lines": [ "max-lines": [
"error", "error",
{ {
"skipBlankLines": true, skipBlankLines: true,
"skipComments": true skipComments: true,
} },
], ],
"max-nested-callbacks": ["error"], "max-nested-callbacks": ["error"],
"max-params": ["error"], "max-params": ["error"],
"max-statements": ["error"], "max-statements": ["error"],
"max-statements-per-line": ["error"], "max-statements-per-line": ["error"],
"multiline-comment-style": [ "multiline-comment-style": ["off", "starred-block"],
"off", "multiline-ternary": ["error", "never"],
"starred-block"
],
"multiline-ternary": [
"error",
"never"
],
"new-cap": ["error"], "new-cap": ["error"],
"new-parens": ["error"], "new-parens": ["error"],
// TODO: configure this... // TODO: configure this...
@ -145,18 +115,15 @@ module.exports = {
"no-catch-shadow": ["error"], "no-catch-shadow": ["error"],
"no-class-assign": ["error"], "no-class-assign": ["error"],
"no-compare-neg-zero": ["error"], "no-compare-neg-zero": ["error"],
"no-cond-assign": [ "no-cond-assign": ["error", "except-parens"],
"error",
"except-parens"
],
"no-confusing-arrow": ["error"], "no-confusing-arrow": ["error"],
"no-console": ["error"], "no-console": ["error"],
"no-const-assign": ["error"], "no-const-assign": ["error"],
"no-constant-condition": [ "no-constant-condition": [
"error", "error",
{ {
"checkLoops": false checkLoops: false,
} },
], ],
"no-continue": ["off"], "no-continue": ["off"],
"no-control-regex": ["error"], "no-control-regex": ["error"],
@ -170,15 +137,15 @@ module.exports = {
"no-duplicate-imports": [ "no-duplicate-imports": [
"error", "error",
{ {
"includeExports": true includeExports: true,
} },
], ],
"no-else-return": ["error"], "no-else-return": ["error"],
"no-empty": [ "no-empty": [
"error", "error",
{ {
"allowEmptyCatch": false allowEmptyCatch: false,
} },
], ],
"no-empty-character-class": ["error"], "no-empty-character-class": ["error"],
"no-empty-function": ["error"], "no-empty-function": ["error"],
@ -194,8 +161,8 @@ module.exports = {
"error", "error",
"all", "all",
{ {
"conditionalAssign": false conditionalAssign: false,
} },
], ],
"no-extra-semi": ["error"], "no-extra-semi": ["error"],
"no-fallthrough": ["error"], "no-fallthrough": ["error"],
@ -206,20 +173,17 @@ module.exports = {
"no-implicit-globals": ["error"], "no-implicit-globals": ["error"],
"no-implied-eval": ["error"], "no-implied-eval": ["error"],
"no-inline-comments": ["error"], "no-inline-comments": ["error"],
"no-inner-declarations": [ "no-inner-declarations": ["error", "both"],
"error",
"both"
],
"no-invalid-regexp": ["error"], "no-invalid-regexp": ["error"],
"no-invalid-this": ["error"], "no-invalid-this": ["error"],
"no-irregular-whitespace": [ "no-irregular-whitespace": [
"error", "error",
{ {
"skipComments": false, skipComments: false,
"skipRegExps": false, skipRegExps: false,
"skipStrings": false, skipStrings: false,
"skipTemplates": false skipTemplates: false,
} },
], ],
"no-iterator": ["error"], "no-iterator": ["error"],
"no-label-var": ["error"], "no-label-var": ["error"],
@ -230,13 +194,9 @@ module.exports = {
"no-magic-numbers": [ "no-magic-numbers": [
"error", "error",
{ {
"ignore": [ ignore: [-1, 0, 1],
-1, ignoreArrayIndexes: true,
0, },
1
],
"ignoreArrayIndexes": true
}
], ],
"no-mixed-operators": ["error"], "no-mixed-operators": ["error"],
"no-mixed-requires": ["error"], "no-mixed-requires": ["error"],
@ -247,8 +207,8 @@ module.exports = {
"no-multiple-empty-lines": [ "no-multiple-empty-lines": [
"error", "error",
{ {
"max": 1 max: 1,
} },
], ],
"no-native-reassign": ["error"], "no-native-reassign": ["error"],
"no-negated-condition": ["error"], "no-negated-condition": ["error"],
@ -268,8 +228,8 @@ module.exports = {
"no-plusplus": [ "no-plusplus": [
"error", "error",
{ {
"allowForLoopAfterthoughts": true allowForLoopAfterthoughts: true,
} },
], ],
"no-process-env": ["error"], "no-process-env": ["error"],
"no-process-exit": ["error"], "no-process-exit": ["error"],
@ -283,10 +243,10 @@ module.exports = {
"no-restricted-properties": [ "no-restricted-properties": [
"error", "error",
{ {
"message": "'log' is too general, use an appropriate level when logging.", message: "'log' is too general, use an appropriate level when logging.",
"object": "console", object: "console",
"property": "log" property: "log",
} },
], ],
"no-restricted-syntax": ["error"], "no-restricted-syntax": ["error"],
"no-return-assign": ["error"], "no-return-assign": ["error"],
@ -295,8 +255,8 @@ module.exports = {
"no-self-assign": [ "no-self-assign": [
"error", "error",
{ {
"props": false props: false,
} },
], ],
"no-self-compare": ["error"], "no-self-compare": ["error"],
"no-sequences": ["error"], "no-sequences": ["error"],
@ -333,10 +293,10 @@ module.exports = {
"no-useless-rename": [ "no-useless-rename": [
"error", "error",
{ {
"ignoreDestructuring": false, ignoreDestructuring: false,
"ignoreExport": false, ignoreExport: false,
"ignoreImport": false ignoreImport: false,
} },
], ],
"no-useless-return": ["error"], "no-useless-return": ["error"],
"no-var": ["error"], "no-var": ["error"],
@ -344,10 +304,7 @@ module.exports = {
"no-warning-comments": ["error"], "no-warning-comments": ["error"],
"no-whitespace-before-property": ["error"], "no-whitespace-before-property": ["error"],
"no-with": ["error"], "no-with": ["error"],
"nonblock-statement-body-position": [ "nonblock-statement-body-position": ["error", "below"],
"error",
"below"
],
"object-curly-newline": ["error"], "object-curly-newline": ["error"],
"object-curly-spacing": ["error"], "object-curly-spacing": ["error"],
"object-property-newline": ["error"], "object-property-newline": ["error"],
@ -355,10 +312,7 @@ module.exports = {
"one-var": ["off"], "one-var": ["off"],
"one-var-declaration-per-line": ["error"], "one-var-declaration-per-line": ["error"],
"operator-assignment": ["error"], "operator-assignment": ["error"],
"operator-linebreak": [ "operator-linebreak": ["error", "none"],
"error",
"none"
],
"padded-blocks": ["off"], "padded-blocks": ["off"],
"padding-line-between-statements": ["error"], "padding-line-between-statements": ["error"],
"prefer-arrow-callback": ["error"], "prefer-arrow-callback": ["error"],
@ -371,24 +325,15 @@ module.exports = {
"prefer-spread": ["error"], "prefer-spread": ["error"],
"prefer-template": ["error"], "prefer-template": ["error"],
"quote-props": ["error"], "quote-props": ["error"],
"quotes": ["error"], quotes: ["error"],
"radix": [ radix: ["error", "as-needed"],
"error",
"as-needed"
],
"require-await": ["error"], "require-await": ["error"],
"require-jsdoc": ["off"], "require-jsdoc": ["off"],
"require-yield": ["error"], "require-yield": ["error"],
"rest-spread-spacing": [ "rest-spread-spacing": ["error", "never"],
"error", semi: ["error"],
"never"
],
"semi": ["error"],
"semi-spacing": ["error"], "semi-spacing": ["error"],
"semi-style": [ "semi-style": ["error", "last"],
"error",
"last"
],
"sort-imports": ["error"], "sort-imports": ["error"],
"sort-keys": ["error"], "sort-keys": ["error"],
"sort-vars": ["error"], "sort-vars": ["error"],
@ -398,37 +343,25 @@ module.exports = {
"space-infix-ops": ["error"], "space-infix-ops": ["error"],
"space-unary-ops": ["error"], "space-unary-ops": ["error"],
"spaced-comment": ["error"], "spaced-comment": ["error"],
"strict": ["error"], strict: ["error"],
"switch-colon-spacing": [ "switch-colon-spacing": [
"error", "error",
{ {
"after": true, after: true,
"before": false before: false,
} },
], ],
"symbol-description": ["error"], "symbol-description": ["error"],
"template-curly-spacing": ["error"], "template-curly-spacing": ["error"],
"template-tag-spacing": ["error"], "template-tag-spacing": ["error"],
"unicode-bom": [ "unicode-bom": ["error", "never"],
"error",
"never"
],
"use-isnan": ["error"], "use-isnan": ["error"],
"valid-jsdoc": ["error"], "valid-jsdoc": ["error"],
"valid-typeof": ["error"], "valid-typeof": ["error"],
"vars-on-top": ["error"], "vars-on-top": ["error"],
"wrap-iife": [ "wrap-iife": ["error", "any"],
"error",
"any"
],
"wrap-regex": ["error"], "wrap-regex": ["error"],
"yield-star-spacing": [ "yield-star-spacing": ["error", "before"],
"error", yoda: ["error", "never"],
"before" },
],
"yoda": [
"error",
"never"
]
}
}; };

@ -8,16 +8,18 @@ const path = require("path");
const exec = require("child_process").exec; const exec = require("child_process").exec;
const semver = require("./semver"); const semver = require("./semver");
const getPackageJson = () => new Promise((resolve, reject) => { const getPackageJson = () =>
new Promise((resolve, reject) => {
try { try {
/* eslint-disable-next-line global-require */ /* eslint-disable-next-line global-require */
resolve(require(path.resolve(process.cwd(), "package.json"))); resolve(require(path.resolve(process.cwd(), "package.json")));
} catch (error) { } catch (error) {
reject(error); reject(error);
} }
}); });
const getEngines = (data) => new Promise((resolve, reject) => { const getEngines = (data) =>
new Promise((resolve, reject) => {
let versions = null; let versions = null;
if (data.engines) { if (data.engines) {
@ -29,9 +31,10 @@ const getEngines = (data) => new Promise((resolve, reject) => {
} else { } else {
reject("Missing or improper 'engines' property in 'package.json'"); reject("Missing or improper 'engines' property in 'package.json'");
} }
}); });
const checkNpmVersion = (engines) => new Promise((resolve, reject) => { const checkNpmVersion = (engines) =>
new Promise((resolve, reject) => {
exec("npm -v", (error, stdout, stderr) => { exec("npm -v", (error, stdout, stderr) => {
if (error) { if (error) {
reject(`Unable to find NPM version\n${stderr}`); reject(`Unable to find NPM version\n${stderr}`);
@ -43,20 +46,25 @@ const checkNpmVersion = (engines) => new Promise((resolve, reject) => {
if (semver.satisfies(npmVersion, engineVersion)) { if (semver.satisfies(npmVersion, engineVersion)) {
resolve(); resolve();
} else { } else {
reject(`Incorrect npm version\n'package.json' specifies "${engineVersion}", you are currently running "${npmVersion}".`); reject(
`Incorrect npm version\n'package.json' specifies "${engineVersion}", you are currently running "${npmVersion}".`,
);
} }
}); });
}); });
const checkNodeVersion = (engines) => new Promise((resolve, reject) => { const checkNodeVersion = (engines) =>
new Promise((resolve, reject) => {
const nodeVersion = process.version.substring(1); const nodeVersion = process.version.substring(1);
if (semver.satisfies(nodeVersion, engines.node)) { if (semver.satisfies(nodeVersion, engines.node)) {
resolve(engines); resolve(engines);
} else { } else {
reject(`Incorrect node version\n'package.json' specifies "${engines.node}", you are currently running "${process.version}".`); reject(
`Incorrect node version\n'package.json' specifies "${engines.node}", you are currently running "${process.version}".`,
);
} }
}); });
getPackageJson() getPackageJson()
.then(getEngines) .then(getEngines)
@ -69,5 +77,5 @@ getPackageJson()
/* eslint-disable no-console, no-process-exit */ /* eslint-disable no-console, no-process-exit */
console.error(error); console.error(error);
process.exit(1); process.exit(1);
} },
); );

@ -444,7 +444,6 @@ function parseComparator(comp, loose) {
} }
class SemVer { class SemVer {
/** /**
* A semantic version. * A semantic version.
* @param {string} version The version. * @param {string} version The version.
@ -488,7 +487,7 @@ class SemVer {
// Numberify any prerelease numeric ids // Numberify any prerelease numeric ids
if (matches[4]) { if (matches[4]) {
this.prerelease = matches[4].split(".").map((id) => { this.prerelease = matches[4].split(".").map((id) => {
if ((/^[0-9]+$/).test(id)) { if (/^[0-9]+$/.test(id)) {
const num = Number(id); const num = Number(id);
if (num >= 0 && num < MAX_SAFE_INTEGER) { if (num >= 0 && num < MAX_SAFE_INTEGER) {
return num; return num;
@ -532,7 +531,9 @@ class SemVer {
} }
return ( return (
compareIdentifiers(this.major, other.major) || compareIdentifiers(this.minor, other.minor) || compareIdentifiers(this.patch, other.patch) compareIdentifiers(this.major, other.major) ||
compareIdentifiers(this.minor, other.minor) ||
compareIdentifiers(this.patch, other.patch)
); );
} }
@ -572,7 +573,8 @@ class SemVer {
} }
} }
const compare = (leftVersion, rightVersion, loose) => new SemVer(leftVersion, loose).compare(new SemVer(rightVersion, loose)); const compare = (leftVersion, rightVersion, loose) =>
new SemVer(leftVersion, loose).compare(new SemVer(rightVersion, loose));
const gt = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) > 0; const gt = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) > 0;
const lt = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) < 0; const lt = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) < 0;
const eq = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) === 0; const eq = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) === 0;

@ -533,6 +533,7 @@ export class Augmentation {
console.warn(`Invalid Faction object in addToAllFactions(). Key value: ${fac}`); console.warn(`Invalid Faction object in addToAllFactions(). Key value: ${fac}`);
continue; continue;
} }
if (facObj.getInfo().special) continue;
facObj.augmentations.push(this.name); facObj.augmentations.push(this.name);
} }
} }

@ -1,6 +1,114 @@
import { IMap } from "../../types"; export const AugmentationNames: {
Targeting1: string;
export const AugmentationNames: IMap<string> = { Targeting2: string;
Targeting3: string;
SyntheticHeart: string;
SynfibrilMuscle: string;
CombatRib1: string;
CombatRib2: string;
CombatRib3: string;
NanofiberWeave: string;
SubdermalArmor: string;
WiredReflexes: string;
GrapheneBoneLacings: string;
BionicSpine: string;
GrapheneBionicSpine: string;
BionicLegs: string;
GrapheneBionicLegs: string;
SpeechProcessor: string;
TITN41Injection: string;
EnhancedSocialInteractionImplant: string;
BitWire: string;
ArtificialBioNeuralNetwork: string;
ArtificialSynapticPotentiation: string;
EnhancedMyelinSheathing: string;
SynapticEnhancement: string;
NeuralRetentionEnhancement: string;
DataJack: string;
ENM: string;
ENMCore: string;
ENMCoreV2: string;
ENMCoreV3: string;
ENMAnalyzeEngine: string;
ENMDMA: string;
Neuralstimulator: string;
NeuralAccelerator: string;
CranialSignalProcessorsG1: string;
CranialSignalProcessorsG2: string;
CranialSignalProcessorsG3: string;
CranialSignalProcessorsG4: string;
CranialSignalProcessorsG5: string;
NeuronalDensification: string;
NeuroreceptorManager: string;
NuoptimalInjectorImplant: string;
SpeechEnhancement: string;
FocusWire: string;
PCDNI: string;
PCDNIOptimizer: string;
PCDNINeuralNetwork: string;
PCMatrix: string;
ADRPheromone1: string;
ADRPheromone2: string;
ShadowsSimulacrum: string;
HacknetNodeCPUUpload: string;
HacknetNodeCacheUpload: string;
HacknetNodeNICUpload: string;
HacknetNodeKernelDNI: string;
HacknetNodeCoreDNI: string;
NeuroFluxGovernor: string;
Neurotrainer1: string;
Neurotrainer2: string;
Neurotrainer3: string;
Hypersight: string;
LuminCloaking1: string;
LuminCloaking2: string;
HemoRecirculator: string;
SmartSonar: string;
PowerRecirculator: string;
QLink: string;
TheRedPill: string;
SPTN97: string;
HiveMind: string;
CordiARCReactor: string;
SmartJaw: string;
Neotra: string;
Xanipher: string;
nextSENS: string;
OmniTekInfoLoad: string;
PhotosyntheticCells: string;
Neurolink: string;
TheBlackHand: string;
UnstableCircadianModulator: string;
CRTX42AA: string;
Neuregen: string;
CashRoot: string;
NutriGen: string;
INFRARet: string;
DermaForce: string;
GrapheneBrachiBlades: string;
GrapheneBionicArms: string;
BrachiBlades: string;
BionicArms: string;
SNA: string;
HydroflameLeftArm: string;
EsperEyewear: string;
EMS4Recombination: string;
OrionShoulder: string;
HyperionV1: string;
HyperionV2: string;
GolemSerum: string;
VangelisVirus: string;
VangelisVirus3: string;
INTERLINKED: string;
BladeRunner: string;
BladeArmor: string;
BladeArmorPowerCells: string;
BladeArmorEnergyShielding: string;
BladeArmorUnibeam: string;
BladeArmorOmnibeam: string;
BladeArmorIPU: string;
BladesSimulacrum: string;
} = {
Targeting1: "Augmented Targeting I", Targeting1: "Augmented Targeting I",
Targeting2: "Augmented Targeting II", Targeting2: "Augmented Targeting II",
Targeting3: "Augmented Targeting III", Targeting3: "Augmented Targeting III",
@ -87,7 +195,7 @@ export const AugmentationNames: IMap<string> = {
NutriGen: "NutriGen Implant", NutriGen: "NutriGen Implant",
INFRARet: "INFRARET Enhancement", INFRARet: "INFRARET Enhancement",
DermaForce: "DermaForce Particle Barrier", DermaForce: "DermaForce Particle Barrier",
GrapheneBrachiBlades: "Graphene BranchiBlades Upgrade", GrapheneBrachiBlades: "Graphene BrachiBlades Upgrade",
GrapheneBionicArms: "Graphene Bionic Arms Upgrade", GrapheneBionicArms: "Graphene Bionic Arms Upgrade",
BrachiBlades: "BrachiBlades", BrachiBlades: "BrachiBlades",
BionicArms: "Bionic Arms", BionicArms: "Bionic Arms",

@ -114,7 +114,7 @@ export const CONSTANTS: {
TotalNumBitNodes: number; TotalNumBitNodes: number;
LatestUpdate: string; LatestUpdate: string;
} = { } = {
Version: "0.55.0", Version: "0.56.0",
// Speed (in ms) at which the main loop is updated // Speed (in ms) at which the main loop is updated
_idleSpeed: 200, _idleSpeed: 200,
@ -281,31 +281,107 @@ export const CONSTANTS: {
TotalNumBitNodes: 24, TotalNumBitNodes: 24,
LatestUpdate: ` LatestUpdate: `
v0.55.0 - 2021-09-20 Material UI (hydroflame & community) v0.56.0 - 2021-10-11 Trimming the backlog (hydroflame & community)
------------------------------------------- -------------------------------------------
** Global ** ** BREAKING **
* The game is now 100% in typescript, react, and Material-UI * The 'write' function is now async. This helps when making scripts that write scripts.
** Terminal **
* 'grow' and 'weaken' have been added as terminal command. This should help player transition
from commands to scripts. The tutorial also talks about it.
* 'cp' command added
* Improved performance by rate limiting refresh.
** IP vs Hostname **
* The game now uses hostname as primary key for it's servers (yeah believe it or not IPs were
used until then). This has caused some issues with purchased servers (they couldn't be sold).
You might need to soft reset for the game to fully convert itself.
** Sleeve **
* Fixed bug where they couldn't train at Volhaven.
* No longer consume all bonus time at once, making it look buggy.
** SF9 **
* Now boosts hacknet production by 8/12/14%
** Hacknet Servers **
* production nerfed by 10%
* Max money increase gets weaker above 10t max money
** Corporation **
* Warehouse tooltip now also displays the amount of space taken by products.
* Changed research box completely to avoid dependency on Treant (Treant is a pita)
* All textbox should accept MAX/MP case insensitive.
* Fixed export popup not refreshing dropdowns correctly.
* Fixed product mku becoming zero
* Increased scaling of Wilson to avoid feedback loop.
* Can no longer get in debt by buying real estate
* Bonus time is consumed faster.
** Netscript **
* isBusy takes bitverse and infiltration into account
* hospitalize can't be called when in infiltration.
* setToCommitCrime now accepts crime rough name instead of perfect name.
* disableLog All now works for bladeburner functions.
* Fixed netscript port for ns1.
** Augmentation **
* Added augmentation to Ti Di Hui that removes penalty for being unfocused.
* Neuroflux no longer appears in special factions.
** Script Editor **
* Ram check is debounced instead of refreshed every second.
* Added the vscode extension documentation to the game (it doesn't work well, thought)
* Fixed issue where autocomplete list would grow forever
* Added semi-monokai as theme.
* Fixed issue where modifying filename would mess it up.
* Font size can be changed now.
** Infiltration **
* Fixed issue where game controls would become unfocused.
** Misc. ** ** Misc. **
* Corporations can no longer bribe special factions * Fixed loader incorrectly assuming some null values are incorrect.
* Infiltration can no longer lose focus of the keyboard. * installBackdoor trigger Bitverse sequence
* Fix terminal line limit * Some improvements to the theme editor
* Added theme editor * Improved documentation about where to learn javascript.
* Theme applies on game load (@Nolshine) * Added some instructions for contributors.
* Sleeves no longer consume all bonus time for some actions * Fixed typo in corporation sell shares modal (@Saynt_Garmo)
* Fix a bug where the autocomlete list would get duplicates * Fixed pagination being black on black in Active Scripts
* Fix tutorial not scaling properly on small screens * Create Script tab renamed to Script Editor
* Import should be more consistent * Fixed an issue where corp some textbox wouldn't update when changing city.
* Typo with 'help' command * Fixed an issue where hacknet online time was always 0.
* Fix infinite loop in casino * Netscript function prompt fixed.
* Fixed miscalculation in growth.
* Script with syntax errors will try to be a tad more helpful.
* Corporations can no longer bribe bladeburners.
* Augmentation Graphene Branchiblade renamed to Brachi, like the rest of them.
* All ram is displayed in GB/TB/PB now.
* Game now saves when saving a file, this can be turned off.
* Several improvement to log window.
* Bladeburner current action returns General type instead of the name of the action.
* Bladeburner travel and Sleeve travel respect disable ASCII.
* Tutorial fits on small screens.
* Import is much slower but more consistent now.
* Fix intelligence not updating properly.
* Added SF -1: Time Compression
* ReadTheDoc theme now matches the game.
* Logbox should wrap text better
* Logbox behavior should feel better.
* Fix font for AutoLink.exe
* nerf noodle bar * nerf noodle bar
`, `,
/*
*/
}; };

@ -105,6 +105,7 @@ export function SellMaterial(mat: Material, amt: string, price: string): void {
} }
//Parse quantity //Parse quantity
amt = amt.toUpperCase();
if (amt.includes("MAX") || amt.includes("PROD")) { if (amt.includes("MAX") || amt.includes("PROD")) {
let q = amt.replace(/\s+/g, ""); let q = amt.replace(/\s+/g, "");
q = q.replace(/[^-()\d/*+.MAXPROD]/g, ""); q = q.replace(/[^-()\d/*+.MAXPROD]/g, "");
@ -168,6 +169,7 @@ export function SellProduct(product: Product, city: string, amt: string, price:
const cities = Object.keys(Cities); const cities = Object.keys(Cities);
// Parse quantity // Parse quantity
amt = amt.toUpperCase();
if (amt.includes("MAX") || amt.includes("PROD")) { if (amt.includes("MAX") || amt.includes("PROD")) {
//Dynamically evaluated quantity. First test to make sure its valid //Dynamically evaluated quantity. First test to make sure its valid
let qty = amt.replace(/\s+/g, ""); let qty = amt.replace(/\s+/g, "");
@ -372,7 +374,7 @@ export function Research(division: IIndustry, researchName: string): void {
export function ExportMaterial(divisionName: string, cityName: string, material: Material, amt: string): void { export function ExportMaterial(divisionName: string, cityName: string, material: Material, amt: string): void {
// Sanitize amt // Sanitize amt
let sanitizedAmt = amt.replace(/\s+/g, ""); let sanitizedAmt = amt.replace(/\s+/g, "").toUpperCase();
sanitizedAmt = sanitizedAmt.replace(/[^-()\d/*+.MAX]/g, ""); sanitizedAmt = sanitizedAmt.replace(/[^-()\d/*+.MAX]/g, "");
let temp = sanitizedAmt.replace(/MAX/g, "1"); let temp = sanitizedAmt.replace(/MAX/g, "1");
try { try {

@ -564,7 +564,7 @@ export class Industry implements IIndustry {
buyAmt = mat.buy * CorporationConstants.SecsPerMarketCycle * marketCycles; buyAmt = mat.buy * CorporationConstants.SecsPerMarketCycle * marketCycles;
if (matName == "RealEstate") { if (matName == "RealEstate") {
maxAmt = buyAmt; maxAmt = corporation.funds.toNumber() / mat.bCost;
} else { } else {
maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / MaterialSizes[matName]); maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / MaterialSizes[matName]);
} }
@ -840,7 +840,7 @@ export class Industry implements IIndustry {
let sellAmt; let sellAmt;
if (isString(mat.sllman[1])) { if (isString(mat.sllman[1])) {
//Dynamically evaluated //Dynamically evaluated
let tmp = (mat.sllman[1] as string).replace(/MAX/g, maxSell + ""); let tmp = (mat.sllman[1] as string).replace(/MAX/g, (maxSell + "").toUpperCase());
tmp = tmp.replace(/PROD/g, mat.prd + ""); tmp = tmp.replace(/PROD/g, mat.prd + "");
try { try {
sellAmt = eval(tmp); sellAmt = eval(tmp);
@ -893,7 +893,7 @@ export class Industry implements IIndustry {
const exp = mat.exp[expI]; const exp = mat.exp[expI];
const amtStr = exp.amt.replace( const amtStr = exp.amt.replace(
/MAX/g, /MAX/g,
mat.qty / (CorporationConstants.SecsPerMarketCycle * marketCycles) + "", (mat.qty / (CorporationConstants.SecsPerMarketCycle * marketCycles) + "").toUpperCase(),
); );
let amt = 0; let amt = 0;
try { try {
@ -1201,7 +1201,7 @@ export class Industry implements IIndustry {
let sellAmt; let sellAmt;
if (product.sllman[city][0] && isString(product.sllman[city][1])) { if (product.sllman[city][0] && isString(product.sllman[city][1])) {
//Sell amount is dynamically evaluated //Sell amount is dynamically evaluated
let tmp = product.sllman[city][1].replace(/MAX/g, maxSell); let tmp = product.sllman[city][1].replace(/MAX/g, (maxSell + "").toUpperCase());
tmp = tmp.replace(/PROD/g, product.data[city][1]); tmp = tmp.replace(/PROD/g, product.data[city][1]);
try { try {
tmp = eval(tmp); tmp = eval(tmp);

@ -80,10 +80,20 @@ export function ExpandIndustryTab(props: IProps): React.ReactElement {
<Typography>Division name:</Typography> <Typography>Division name:</Typography>
<Box display="flex" alignItems="center"> <Box display="flex" alignItems="center">
<TextField autoFocus={true} value={name} onChange={onNameChange} onKeyDown={onKeyDown} type="text" /> <TextField
autoFocus={true}
value={name}
onChange={onNameChange}
onKeyDown={onKeyDown}
type="text"
InputProps={{
endAdornment: (
<Button disabled={disabled} sx={{ mx: 1 }} onClick={newIndustry}> <Button disabled={disabled} sx={{ mx: 1 }} onClick={newIndustry}>
Create Division Expand
</Button> </Button>
),
}}
/>
</Box> </Box>
</> </>
); );

@ -43,16 +43,21 @@ export function ExpandNewCity(props: IProps): React.ReactElement {
Would you like to expand into a new city by opening an office? This would cost{" "} Would you like to expand into a new city by opening an office? This would cost{" "}
<MoneyCost money={CorporationConstants.OfficeInitialCost} corp={corp} /> <MoneyCost money={CorporationConstants.OfficeInitialCost} corp={corp} />
</Typography> </Typography>
<Select value={city} onChange={onCityChange}> <Select
endAdornment={
<Button onClick={expand} disabled={disabled}>
Confirm
</Button>
}
value={city}
onChange={onCityChange}
>
{possibleCities.map((cityName: string) => ( {possibleCities.map((cityName: string) => (
<MenuItem key={cityName} value={cityName}> <MenuItem key={cityName} value={cityName}>
{cityName} {cityName}
</MenuItem> </MenuItem>
))} ))}
</Select> </Select>
<Button onClick={expand} disabled={disabled}>
Confirm
</Button>
</> </>
); );
} }

@ -39,7 +39,9 @@ export function ExportModal(props: IProps): React.ReactElement {
} }
function onIndustryChange(event: SelectChangeEvent<string>): void { function onIndustryChange(event: SelectChangeEvent<string>): void {
setIndustry(event.target.value); const div = event.target.value;
setIndustry(div);
setCity(Object.keys(corp.divisions[0].warehouses)[0]);
} }
function onAmtChange(event: React.ChangeEvent<HTMLInputElement>): void { function onAmtChange(event: React.ChangeEvent<HTMLInputElement>): void {

@ -30,8 +30,7 @@ export function Augmentations(props: IProps): React.ReactElement {
} }
function queueAllAugs(): void { function queueAllAugs(): void {
for (const i in AugmentationNames) { for (const augName of Object.keys(AugmentationNames)) {
const augName = AugmentationNames[i];
props.player.queueAugmentation(augName); props.player.queueAugmentation(augName);
} }
} }

@ -2,13 +2,14 @@ import React from "react";
import { use } from "../ui/Context"; import { use } from "../ui/Context";
import { Exploit } from "./Exploit"; import { Exploit } from "./Exploit";
const getComputedStyle = window.getComputedStyle;
export function Unclickable(): React.ReactElement { export function Unclickable(): React.ReactElement {
const player = use.Player(); const player = use.Player();
function unclickable(event: React.MouseEvent<HTMLDivElement>): void { function unclickable(event: React.MouseEvent<HTMLDivElement>): void {
if (!event.target || !(event.target instanceof Element)) return; if (!event.target || !(event.target instanceof Element)) return;
const display = window.getComputedStyle(event.target as Element).display; const display = getComputedStyle(event.target as Element).display;
const visibility = window.getComputedStyle(event.target as Element).visibility; const visibility = getComputedStyle(event.target as Element).visibility;
if (display === "none" && visibility === "hidden" && event.isTrusted) player.giveExploit(Exploit.Unclickable); if (display === "none" && visibility === "hidden" && event.isTrusted) player.giveExploit(Exploit.Unclickable);
} }

@ -50,6 +50,11 @@ export class FactionInfo {
*/ */
keep: boolean; keep: boolean;
/**
* Special faction
*/
special: boolean;
constructor( constructor(
infoText: JSX.Element, infoText: JSX.Element,
enemies: string[], enemies: string[],
@ -57,6 +62,7 @@ export class FactionInfo {
offerHackingWork: boolean, offerHackingWork: boolean,
offerFieldWork: boolean, offerFieldWork: boolean,
offerSecurityWork: boolean, offerSecurityWork: boolean,
special: boolean,
keep: boolean, keep: boolean,
) { ) {
this.infoText = infoText; this.infoText = infoText;
@ -70,6 +76,7 @@ export class FactionInfo {
this.augmentationPriceMult = 1; this.augmentationPriceMult = 1;
this.augmentationRepRequirementMult = 1; this.augmentationRepRequirementMult = 1;
this.keep = keep; this.keep = keep;
this.special = special;
} }
offersWork(): boolean { offersWork(): boolean {
@ -96,6 +103,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true, true,
false, false,
false, false,
false,
), ),
Daedalus: new FactionInfo( Daedalus: new FactionInfo(
@ -106,6 +114,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true, true,
false, false,
false, false,
false,
), ),
"The Covenant": new FactionInfo( "The Covenant": new FactionInfo(
@ -124,6 +133,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true, true,
false, false,
false, false,
false,
), ),
// Megacorporations, each forms its own faction // Megacorporations, each forms its own faction
@ -139,6 +149,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true, true,
true, true,
true, true,
false,
true, true,
), ),
@ -158,6 +169,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true, true,
true, true,
true, true,
false,
true, true,
), ),
@ -175,10 +187,11 @@ export const FactionInfos: IMap<FactionInfo> = {
true, true,
true, true,
true, true,
false,
true, true,
), ),
"Blade Industries": new FactionInfo(<>Augmentation is Salvation.</>, [], true, true, true, true, true), "Blade Industries": new FactionInfo(<>Augmentation is Salvation.</>, [], true, true, true, true, false, true),
NWO: new FactionInfo( NWO: new FactionInfo(
( (
@ -193,10 +206,20 @@ export const FactionInfos: IMap<FactionInfo> = {
true, true,
true, true,
true, true,
false,
true, true,
), ),
"Clarke Incorporated": new FactionInfo(<>The Power of the Genome - Unlocked.</>, [], true, true, true, true, true), "Clarke Incorporated": new FactionInfo(
<>The Power of the Genome - Unlocked.</>,
[],
true,
true,
true,
true,
false,
true,
),
"OmniTek Incorporated": new FactionInfo( "OmniTek Incorporated": new FactionInfo(
<>Simply put, our mission is to design and build robots that make a difference.</>, <>Simply put, our mission is to design and build robots that make a difference.</>,
@ -205,6 +228,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true, true,
true, true,
true, true,
false,
true, true,
), ),
@ -220,10 +244,20 @@ export const FactionInfos: IMap<FactionInfo> = {
true, true,
true, true,
true, true,
false,
true, true,
), ),
"KuaiGong International": new FactionInfo(<>Dream big. Work hard. Make history.</>, [], true, true, true, true, true), "KuaiGong International": new FactionInfo(
<>Dream big. Work hard. Make history.</>,
[],
true,
true,
true,
true,
false,
true,
),
// Other Corporations // Other Corporations
"Fulcrum Secret Technologies": new FactionInfo( "Fulcrum Secret Technologies": new FactionInfo(
@ -238,6 +272,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true, true,
false, false,
true, true,
false,
true, true,
), ),
@ -261,6 +296,7 @@ export const FactionInfos: IMap<FactionInfo> = {
false, false,
false, false,
false, false,
false,
), ),
"The Black Hand": new FactionInfo( "The Black Hand": new FactionInfo(
@ -280,6 +316,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true, true,
false, false,
false, false,
false,
), ),
// prettier-ignore // prettier-ignore
@ -325,6 +362,7 @@ export const FactionInfos: IMap<FactionInfo> = {
false, false,
false, false,
false, false,
false,
), ),
// City factions, essentially governments // City factions, essentially governments
@ -336,8 +374,18 @@ export const FactionInfos: IMap<FactionInfo> = {
true, true,
true, true,
false, false,
false,
),
Chongqing: new FactionInfo(
<>Serve the People.</>,
["Sector-12", "Aevum", "Volhaven"],
true,
true,
true,
true,
false,
false,
), ),
Chongqing: new FactionInfo(<>Serve the People.</>, ["Sector-12", "Aevum", "Volhaven"], true, true, true, true, false),
Ishima: new FactionInfo( Ishima: new FactionInfo(
<>The East Asian Order of the Future.</>, <>The East Asian Order of the Future.</>,
["Sector-12", "Aevum", "Volhaven"], ["Sector-12", "Aevum", "Volhaven"],
@ -346,6 +394,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true, true,
true, true,
false, false,
false,
), ),
"New Tokyo": new FactionInfo( "New Tokyo": new FactionInfo(
<>Asia's World City.</>, <>Asia's World City.</>,
@ -355,6 +404,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true, true,
true, true,
false, false,
false,
), ),
"Sector-12": new FactionInfo( "Sector-12": new FactionInfo(
<>The City of the Future.</>, <>The City of the Future.</>,
@ -364,6 +414,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true, true,
true, true,
false, false,
false,
), ),
Volhaven: new FactionInfo( Volhaven: new FactionInfo(
<>Benefit, Honor, and Glory.</>, <>Benefit, Honor, and Glory.</>,
@ -373,6 +424,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true, true,
true, true,
false, false,
false,
), ),
// Criminal Organizations/Gangs // Criminal Organizations/Gangs
@ -384,6 +436,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true, true,
true, true,
false, false,
false,
), ),
"The Dark Army": new FactionInfo( "The Dark Army": new FactionInfo(
@ -394,9 +447,10 @@ export const FactionInfos: IMap<FactionInfo> = {
true, true,
false, false,
false, false,
false,
), ),
"The Syndicate": new FactionInfo(<>Honor holds you back.</>, [], true, true, true, true, false), "The Syndicate": new FactionInfo(<>Honor holds you back.</>, [], true, true, true, true, false, false),
Silhouette: new FactionInfo( Silhouette: new FactionInfo(
( (
@ -415,6 +469,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true, true,
false, false,
false, false,
false,
), ),
Tetrads: new FactionInfo( Tetrads: new FactionInfo(
@ -425,14 +480,15 @@ export const FactionInfos: IMap<FactionInfo> = {
true, true,
true, true,
false, false,
false,
), ),
"Slum Snakes": new FactionInfo(<>Slum Snakes rule!</>, [], false, false, true, true, false), "Slum Snakes": new FactionInfo(<>Slum Snakes rule!</>, [], false, false, true, true, false, false),
// Earlygame factions - factions the player will prestige with early on that don't belong in other categories. // Earlygame factions - factions the player will prestige with early on that don't belong in other categories.
Netburners: new FactionInfo(<>{"~~//*>H4CK||3T 8URN3R5**>?>\\~~"}</>, [], true, true, false, false, false), Netburners: new FactionInfo(<>{"~~//*>H4CK||3T 8URN3R5**>?>\\~~"}</>, [], true, true, false, false, false, false),
"Tian Di Hui": new FactionInfo(<>Obey Heaven and work righteously.</>, [], true, true, false, true, false), "Tian Di Hui": new FactionInfo(<>Obey Heaven and work righteously.</>, [], true, true, false, true, false, false),
CyberSec: new FactionInfo( CyberSec: new FactionInfo(
( (
@ -448,6 +504,7 @@ export const FactionInfos: IMap<FactionInfo> = {
false, false,
false, false,
false, false,
false,
), ),
// Special Factions // Special Factions
@ -466,6 +523,7 @@ export const FactionInfos: IMap<FactionInfo> = {
false, false,
false, false,
false, false,
true,
false, false,
), ),
}; };

@ -41,6 +41,7 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
if (isPlayersGang) { if (isPlayersGang) {
const augs: string[] = []; const augs: string[] = [];
for (const augName in Augmentations) { for (const augName in Augmentations) {
if (augName === AugmentationNames.NeuroFluxGovernor) continue;
const aug = Augmentations[augName]; const aug = Augmentations[augName];
if (!aug.isSpecial) { if (!aug.isSpecial) {
augs.push(augName); augs.push(augName);

@ -510,7 +510,9 @@ export function purchaseHashUpgrade(player: IPlayer, upgName: string, upgTarget:
} }
if (!(target instanceof Server)) throw new Error(`'${upgTarget}' is not a normal server.`); if (!(target instanceof Server)) throw new Error(`'${upgTarget}' is not a normal server.`);
target.changeMaximumMoney(upg.value, true); const old = target.moneyMax;
target.changeMaximumMoney(upg.value);
console.log(target.moneyMax / old);
} catch (e) { } catch (e) {
player.hashManager.refundUpgrade(upgName); player.hashManager.refundUpgrade(upgName);
return false; return false;

@ -28,6 +28,7 @@ import { TableCell } from "../../ui/React/Table";
import TableBody from "@mui/material/TableBody"; import TableBody from "@mui/material/TableBody";
import Table from "@mui/material/Table"; import Table from "@mui/material/Table";
import TableRow from "@mui/material/TableRow"; import TableRow from "@mui/material/TableRow";
import { numeralWrapper } from "../../ui/numeralFormat";
interface IProps { interface IProps {
node: HacknetNode; node: HacknetNode;
@ -163,7 +164,7 @@ export function HacknetNodeElem(props: IProps): React.ReactElement {
<Typography>RAM:</Typography> <Typography>RAM:</Typography>
</TableCell> </TableCell>
<TableCell> <TableCell>
<Typography>{node.ram}GB</Typography> <Typography>{numeralWrapper.formatRAM(node.ram)}</Typography>
</TableCell> </TableCell>
<TableCell> <TableCell>
<Button onClick={upgradeRamOnClick}>{upgradeRamContent}</Button> <Button onClick={upgradeRamOnClick}>{upgradeRamContent}</Button>

@ -31,6 +31,7 @@ import { TableCell } from "../../ui/React/Table";
import TableBody from "@mui/material/TableBody"; import TableBody from "@mui/material/TableBody";
import Table from "@mui/material/Table"; import Table from "@mui/material/Table";
import TableRow from "@mui/material/TableRow"; import TableRow from "@mui/material/TableRow";
import { numeralWrapper } from "../../ui/numeralFormat";
interface IProps { interface IProps {
node: HacknetServer; node: HacknetServer;
@ -213,7 +214,7 @@ export function HacknetServerElem(props: IProps): React.ReactElement {
<Typography>RAM:</Typography> <Typography>RAM:</Typography>
</TableCell> </TableCell>
<TableCell> <TableCell>
<Typography>{node.maxRam}GB</Typography> <Typography>{numeralWrapper.formatRAM(node.maxRam)}</Typography>
</TableCell> </TableCell>
<TableCell> <TableCell>
<Button onClick={upgradeRamOnClick}>{upgradeRamContent}</Button> <Button onClick={upgradeRamOnClick}>{upgradeRamContent}</Button>

@ -64,13 +64,21 @@ export function HacknetUpgradeElem(props: IProps): React.ReactElement {
</Typography> </Typography>
<Typography>{upg.desc}</Typography> <Typography>{upg.desc}</Typography>
{!upg.hasTargetServer && (
<Button onClick={purchase} disabled={!canPurchase}> <Button onClick={purchase} disabled={!canPurchase}>
Purchase Buy
</Button> </Button>
{level > 0 && effect && <Typography>{effect}</Typography>}
{upg.hasTargetServer && (
<ServerDropdown value={selectedServer} serverType={ServerType.Foreign} onChange={changeTargetServer} />
)} )}
{upg.hasTargetServer && (
<ServerDropdown
purchase={purchase}
canPurchase={canPurchase}
value={selectedServer}
serverType={ServerType.Foreign}
onChange={changeTargetServer}
/>
)}
{level > 0 && effect && <Typography>{effect}</Typography>}
</Paper> </Paper>
); );
} }

@ -8,6 +8,7 @@ import { purchaseRamForHomeComputer } from "../../Server/ServerPurchases";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { MathComponent } from "mathjax-react"; import { MathComponent } from "mathjax-react";
import { numeralWrapper } from "../../ui/numeralFormat";
type IProps = { type IProps = {
p: IPlayer; p: IPlayer;
@ -31,7 +32,8 @@ export function RamButton(props: IProps): React.ReactElement {
<Tooltip title={<MathComponent tex={String.raw`\large{cost = 3.2 \times 10^3 \times 1.58^{log_2{(ram)}}}`} />}> <Tooltip title={<MathComponent tex={String.raw`\large{cost = 3.2 \times 10^3 \times 1.58^{log_2{(ram)}}}`} />}>
<span> <span>
<Button disabled={!props.p.canAfford(cost)} onClick={buy}> <Button disabled={!props.p.canAfford(cost)} onClick={buy}>
Upgrade 'home' RAM ({homeComputer.maxRam}GB -&gt;&nbsp;{homeComputer.maxRam * 2}GB) -&nbsp; Upgrade 'home' RAM ({numeralWrapper.formatRAM(homeComputer.maxRam)} -&gt;&nbsp;
{numeralWrapper.formatRAM(homeComputer.maxRam * 2)}) -&nbsp;
<Money money={cost} player={props.p} /> <Money money={cost} player={props.p} />
</Button> </Button>
</span> </span>

@ -17,6 +17,7 @@ import { getPurchaseServerCost } from "../../Server/ServerPurchases";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
import { PurchaseServerModal } from "./PurchaseServerModal"; import { PurchaseServerModal } from "./PurchaseServerModal";
import { numeralWrapper } from "../../ui/numeralFormat";
interface IServerProps { interface IServerProps {
ram: number; ram: number;
@ -30,7 +31,7 @@ function ServerButton(props: IServerProps): React.ReactElement {
return ( return (
<> <>
<Button onClick={() => setOpen(true)} disabled={!player.canAfford(cost)}> <Button onClick={() => setOpen(true)} disabled={!player.canAfford(cost)}>
Purchase {props.ram}GB Server&nbsp;-&nbsp; Purchase {numeralWrapper.formatRAM(props.ram)} Server&nbsp;-&nbsp;
<Money money={cost} player={player} /> <Money money={cost} player={player} />
</Button> </Button>
<PurchaseServerModal <PurchaseServerModal

@ -10,7 +10,6 @@ import { RunningScript } from "../Script/RunningScript";
import { GetServer } from "../Server/AllServers"; import { GetServer } from "../Server/AllServers";
import { compareArrays } from "../utils/helpers/compareArrays"; import { compareArrays } from "../utils/helpers/compareArrays";
import { roundToTwo } from "../utils/helpers/roundToTwo";
export function killWorkerScript(runningScriptObj: RunningScript, hostname: string, rerenderUi?: boolean): boolean; export function killWorkerScript(runningScriptObj: RunningScript, hostname: string, rerenderUi?: boolean): boolean;
export function killWorkerScript(workerScript: WorkerScript): boolean; export function killWorkerScript(workerScript: WorkerScript): boolean;

@ -13,13 +13,12 @@ export function netscriptDelay(time: number, workerScript: WorkerScript): Promis
} }
export function makeRuntimeRejectMsg(workerScript: WorkerScript, msg: string): string { export function makeRuntimeRejectMsg(workerScript: WorkerScript, msg: string): string {
const lineNum = "";
const server = GetServer(workerScript.hostname); const server = GetServer(workerScript.hostname);
if (server == null) { if (server == null) {
throw new Error(`WorkerScript constructed with invalid server ip: ${workerScript.hostname}`); throw new Error(`WorkerScript constructed with invalid server ip: ${workerScript.hostname}`);
} }
return "|" + server.hostname + "|" + workerScript.name + "|" + msg + lineNum; return "|" + server.hostname + "|" + workerScript.name + "|" + msg;
} }
export function resolveNetscriptRequestedThreads( export function resolveNetscriptRequestedThreads(

@ -122,6 +122,7 @@ import { Router } from "./ui/GameRoot";
import { numeralWrapper } from "./ui/numeralFormat"; import { numeralWrapper } from "./ui/numeralFormat";
import { is2DArray } from "./utils/helpers/is2DArray"; import { is2DArray } from "./utils/helpers/is2DArray";
import { convertTimeMsToTimeElapsedString } from "./utils/StringHelperFunctions"; import { convertTimeMsToTimeElapsedString } from "./utils/StringHelperFunctions";
import { SpecialServers } from "./Server/data/SpecialServers";
import { LogBoxEvents } from "./ui/React/LogBoxManager"; import { LogBoxEvents } from "./ui/React/LogBoxManager";
import { arrayToString } from "./utils/helpers/arrayToString"; import { arrayToString } from "./utils/helpers/arrayToString";
@ -136,6 +137,7 @@ import { IIndustry } from "./Corporation/IIndustry";
import { Faction } from "./Faction/Faction"; import { Faction } from "./Faction/Faction";
import { Augmentation } from "./Augmentation/Augmentation"; import { Augmentation } from "./Augmentation/Augmentation";
import { Page } from "./ui/Router";
import { CodingContract } from "./CodingContracts"; import { CodingContract } from "./CodingContracts";
import { Stock } from "./StockMarket/Stock"; import { Stock } from "./StockMarket/Stock";
@ -713,7 +715,7 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
const gang = NetscriptGang(Player, workerScript, helper); const gang = NetscriptGang(Player, workerScript, helper);
const sleeve = NetscriptSleeve(Player, workerScript, helper); const sleeve = NetscriptSleeve(Player, workerScript, helper);
const extra = NetscriptExtra(Player, workerScript, helper); const extra = NetscriptExtra(Player, workerScript);
const hacknet = NetscriptHacknet(Player, workerScript, helper); const hacknet = NetscriptHacknet(Player, workerScript, helper);
const functions = { const functions = {
@ -3076,6 +3078,10 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
workerScript.log("installBackdoor", `Successfully installed backdoor on '${server.hostname}'`); workerScript.log("installBackdoor", `Successfully installed backdoor on '${server.hostname}'`);
server.backdoorInstalled = true; server.backdoorInstalled = true;
if (SpecialServers.WorldDaemon === server.hostname) {
Router.toBitVerse(false, false);
}
return Promise.resolve(); return Promise.resolve();
}); });
}, },
@ -3239,12 +3245,16 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
hospitalize: function (): any { hospitalize: function (): any {
updateDynamicRam("hospitalize", getRamCost("hospitalize")); updateDynamicRam("hospitalize", getRamCost("hospitalize"));
checkSingularityAccess("hospitalize", 1); checkSingularityAccess("hospitalize", 1);
if (Player.isWorking || Router.page() === Page.Infiltration || Router.page() === Page.BitVerse) {
workerScript.log("hospitalize", "Cannot go to the hospital because the player is busy.");
return;
}
return Player.hospitalize(); return Player.hospitalize();
}, },
isBusy: function (): any { isBusy: function (): any {
updateDynamicRam("isBusy", getRamCost("isBusy")); updateDynamicRam("isBusy", getRamCost("isBusy"));
checkSingularityAccess("isBusy", 1); checkSingularityAccess("isBusy", 1);
return Player.isWorking; return Player.isWorking || Router.page() === Page.Infiltration || Router.page() === Page.BitVerse;
}, },
stopAction: function (): any { stopAction: function (): any {
updateDynamicRam("stopAction", getRamCost("stopAction")); updateDynamicRam("stopAction", getRamCost("stopAction"));
@ -3280,7 +3290,9 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain); Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain);
workerScript.log( workerScript.log(
"upgradeHomeRam", "upgradeHomeRam",
`Purchased additional RAM for home computer! It now has ${homeComputer.maxRam}GB of RAM.`, `Purchased additional RAM for home computer! It now has ${numeralWrapper.formatRAM(
homeComputer.maxRam,
)} of RAM.`,
); );
return true; return true;
}, },
@ -4170,8 +4182,6 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
}, },
joinBladeburnerDivision: function (): any { joinBladeburnerDivision: function (): any {
updateDynamicRam("joinBladeburnerDivision", getRamCost("bladeburner", "joinBladeburnerDivision")); updateDynamicRam("joinBladeburnerDivision", getRamCost("bladeburner", "joinBladeburnerDivision"));
const bladeburner = Player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
if (Player.bitNodeN === 7 || SourceFileFlags[7] > 0) { if (Player.bitNodeN === 7 || SourceFileFlags[7] > 0) {
if (Player.bitNodeN === 8) { if (Player.bitNodeN === 8) {
return false; return false;

@ -1,4 +1,3 @@
import { INetscriptHelper } from "./INetscriptHelper";
import { WorkerScript } from "../Netscript/WorkerScript"; import { WorkerScript } from "../Netscript/WorkerScript";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { Exploit } from "../Exploits/Exploit"; import { Exploit } from "../Exploits/Exploit";
@ -11,7 +10,7 @@ export interface INetscriptExtra {
bypass(doc: Document): void; bypass(doc: Document): void;
} }
export function NetscriptExtra(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): INetscriptExtra { export function NetscriptExtra(player: IPlayer, workerScript: WorkerScript): INetscriptExtra {
return { return {
heart: { heart: {
// Easter egg function // Easter egg function

@ -8,6 +8,7 @@ import { WorkerScript } from "../Netscript/WorkerScript";
import { findSleevePurchasableAugs } from "../PersonObjects/Sleeve/SleeveHelpers"; import { findSleevePurchasableAugs } from "../PersonObjects/Sleeve/SleeveHelpers";
import { Augmentations } from "../Augmentation/Augmentations"; import { Augmentations } from "../Augmentation/Augmentations";
import { CityName } from "../Locations/data/CityNames"; import { CityName } from "../Locations/data/CityNames";
import { findCrime } from "../Crime/CrimeHelpers";
export interface INetscriptSleeve { export interface INetscriptSleeve {
getNumSleeves(): number; getNumSleeves(): number;
@ -87,13 +88,17 @@ export function NetscriptSleeve(
checkSleeveNumber("setToSynchronize", sleeveNumber); checkSleeveNumber("setToSynchronize", sleeveNumber);
return player.sleeves[sleeveNumber].synchronize(player); return player.sleeves[sleeveNumber].synchronize(player);
}, },
setToCommitCrime: function (asleeveNumber: any = 0, acrimeName: any = ""): boolean { setToCommitCrime: function (asleeveNumber: any = 0, aCrimeRoughName: any = ""): boolean {
const sleeveNumber = helper.number("setToCommitCrime", "sleeveNumber", asleeveNumber); const sleeveNumber = helper.number("setToCommitCrime", "sleeveNumber", asleeveNumber);
const crimeName = helper.string("setToUniversityCourse", "crimeName", acrimeName); const crimeRoughName = helper.string("setToCommitCrime", "crimeName", aCrimeRoughName);
helper.updateDynamicRam("setToCommitCrime", getRamCost("sleeve", "setToCommitCrime")); helper.updateDynamicRam("setToCommitCrime", getRamCost("sleeve", "setToCommitCrime"));
checkSleeveAPIAccess("setToCommitCrime"); checkSleeveAPIAccess("setToCommitCrime");
checkSleeveNumber("setToCommitCrime", sleeveNumber); checkSleeveNumber("setToCommitCrime", sleeveNumber);
return player.sleeves[sleeveNumber].commitCrime(player, crimeName); const crime = findCrime(crimeRoughName);
if (crime === null) {
return false;
}
return player.sleeves[sleeveNumber].commitCrime(player, crime.name);
}, },
setToUniversityCourse: function (asleeveNumber: any = 0, auniversityName: any = "", aclassName: any = ""): boolean { setToUniversityCourse: function (asleeveNumber: any = 0, auniversityName: any = "", aclassName: any = ""): boolean {
const sleeveNumber = helper.number("setToUniversityCourse", "sleeveNumber", asleeveNumber); const sleeveNumber = helper.number("setToUniversityCourse", "sleeveNumber", asleeveNumber);

@ -124,10 +124,14 @@ function startNetscript2Script(workerScript: WorkerScript): Promise<WorkerScript
.catch((e) => reject(e)); .catch((e) => reject(e));
}).catch((e) => { }).catch((e) => {
if (e instanceof Error) { if (e instanceof Error) {
if (e instanceof SyntaxError) {
workerScript.errorMessage = makeRuntimeRejectMsg(workerScript, e.message + " (sorry we can't be more helpful)");
} else {
workerScript.errorMessage = makeRuntimeRejectMsg( workerScript.errorMessage = makeRuntimeRejectMsg(
workerScript, workerScript,
e.message + ((e.stack && "\nstack:\n" + e.stack.toString()) || ""), e.message + ((e.stack && "\nstack:\n" + e.stack.toString()) || ""),
); );
}
throw workerScript; throw workerScript;
} else if (isScriptErrorMessage(e)) { } else if (isScriptErrorMessage(e)) {
workerScript.errorMessage = e; workerScript.errorMessage = e;

@ -201,6 +201,7 @@ export interface IPlayer {
inGang(): boolean; inGang(): boolean;
isQualified(company: Company, position: CompanyPosition): boolean; isQualified(company: Company, position: CompanyPosition): boolean;
loseMoney(money: number): void; loseMoney(money: number): void;
process(router: IRouter, numCycles?: number): void;
reapplyAllAugmentations(resetMultipliers?: boolean): void; reapplyAllAugmentations(resetMultipliers?: boolean): void;
reapplyAllSourceFiles(): void; reapplyAllSourceFiles(): void;
regenerateHp(amt: number): void; regenerateHp(amt: number): void;

@ -245,6 +245,7 @@ export class PlayerObject implements IPlayer {
getIntelligenceBonus: (weight: number) => number; getIntelligenceBonus: (weight: number) => number;
getCasinoWinnings: () => number; getCasinoWinnings: () => number;
quitJob: (company: string) => void; quitJob: (company: string) => void;
process: (router: IRouter, numCycles?: number) => void;
createHacknetServer: () => HacknetServer; createHacknetServer: () => HacknetServer;
startCreateProgramWork: (router: IRouter, programName: string, time: number, reqLevel: number) => void; startCreateProgramWork: (router: IRouter, programName: string, time: number, reqLevel: number) => void;
queueAugmentation: (augmentationName: string) => void; queueAugmentation: (augmentationName: string) => void;
@ -505,6 +506,7 @@ export class PlayerObject implements IPlayer {
this.getWorkAgiExpGain = generalMethods.getWorkAgiExpGain; this.getWorkAgiExpGain = generalMethods.getWorkAgiExpGain;
this.getWorkChaExpGain = generalMethods.getWorkChaExpGain; this.getWorkChaExpGain = generalMethods.getWorkChaExpGain;
this.getWorkRepGain = generalMethods.getWorkRepGain; this.getWorkRepGain = generalMethods.getWorkRepGain;
this.process = generalMethods.process;
this.startCreateProgramWork = generalMethods.startCreateProgramWork; this.startCreateProgramWork = generalMethods.startCreateProgramWork;
this.createProgramWork = generalMethods.createProgramWork; this.createProgramWork = generalMethods.createProgramWork;
this.finishCreateProgramWork = generalMethods.finishCreateProgramWork; this.finishCreateProgramWork = generalMethods.finishCreateProgramWork;

@ -77,7 +77,6 @@ export function init(this: IPlayer): void {
} }
export function prestigeAugmentation(this: IPlayer): void { export function prestigeAugmentation(this: IPlayer): void {
const homeComp = this.getHomeComputer();
this.currentServer = SpecialServers.Home; this.currentServer = SpecialServers.Home;
this.numPeopleKilled = 0; this.numPeopleKilled = 0;
@ -576,6 +575,37 @@ export function startWork(this: IPlayer, router: IRouter, companyName: string):
router.toWork(); router.toWork();
} }
export function process(this: IPlayer, router: IRouter, numCycles = 1): void {
// Working
if (this.isWorking) {
if (this.workType == CONSTANTS.WorkTypeFaction) {
if (this.workForFaction(numCycles)) {
router.toFaction();
}
} else if (this.workType == CONSTANTS.WorkTypeCreateProgram) {
if (this.createProgramWork(numCycles)) {
router.toTerminal();
}
} else if (this.workType == CONSTANTS.WorkTypeStudyClass) {
if (this.takeClass(numCycles)) {
router.toCity();
}
} else if (this.workType == CONSTANTS.WorkTypeCrime) {
if (this.commitCrime(numCycles)) {
router.toLocation(Locations[LocationName.Slums]);
}
} else if (this.workType == CONSTANTS.WorkTypeCompanyPartTime) {
if (this.workPartTime(numCycles)) {
router.toCity();
}
} else {
if (this.work(numCycles)) {
router.toCity();
}
}
}
}
export function cancelationPenalty(this: IPlayer): number { export function cancelationPenalty(this: IPlayer): number {
const server = GetServer(this.companyName); const server = GetServer(this.companyName);
if (server instanceof Server) { if (server instanceof Server) {

@ -563,7 +563,6 @@ export class Sleeve extends Person {
* Resets all parameters used to keep information about the current task * Resets all parameters used to keep information about the current task
*/ */
resetTaskStatus(): void { resetTaskStatus(): void {
console.error("");
this.earningsForTask = createTaskTracker(); this.earningsForTask = createTaskTracker();
this.gainRatesForTask = createTaskTracker(); this.gainRatesForTask = createTaskTracker();
this.currentTask = SleeveTaskType.Idle; this.currentTask = SleeveTaskType.Idle;

@ -29,4 +29,5 @@ export function loadPlayer(saveString: string): void {
} }
Player.exploits = sanitizeExploits(Player.exploits); Player.exploits = sanitizeExploits(Player.exploits);
console.log(Player.bladeburner);
} }

@ -141,6 +141,18 @@ function evaluateVersionCompatibility(ver: string): void {
delete anyPlayer.companyPosition; delete anyPlayer.companyPosition;
} }
if (ver < "0.56.0") {
for (const q of anyPlayer.queuedAugmentations) {
if (q.name === "Graphene BranchiBlades Upgrade") {
q.name = "Graphene BrachiBlades Upgrade";
}
}
for (const q of anyPlayer.augmentations) {
if (q.name === "Graphene BranchiBlades Upgrade") {
q.name = "Graphene BrachiBlades Upgrade";
}
}
}
} }
function loadGame(saveString: string): boolean { function loadGame(saveString: string): boolean {

@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect, useRef, useMemo } from "react";
import Editor from "@monaco-editor/react"; import Editor from "@monaco-editor/react";
import * as monaco from "monaco-editor"; import * as monaco from "monaco-editor";
type IStandaloneCodeEditor = monaco.editor.IStandaloneCodeEditor; type IStandaloneCodeEditor = monaco.editor.IStandaloneCodeEditor;
@ -21,6 +21,8 @@ import { NetscriptFunctions } from "../../NetscriptFunctions";
import { WorkerScript } from "../../Netscript/WorkerScript"; import { WorkerScript } from "../../Netscript/WorkerScript";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { iTutorialNextStep, ITutorial, iTutorialSteps } from "../../InteractiveTutorial"; import { iTutorialNextStep, ITutorial, iTutorialSteps } from "../../InteractiveTutorial";
import { debounce } from "lodash";
import { saveObject } from "../../SaveObject";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
@ -84,6 +86,7 @@ export function Root(props: IProps): React.ReactElement {
const [filename, setFilename] = useState(props.filename ? props.filename : lastFilename); const [filename, setFilename] = useState(props.filename ? props.filename : lastFilename);
const [code, setCode] = useState<string>(props.filename ? props.code : lastCode); const [code, setCode] = useState<string>(props.filename ? props.code : lastCode);
const [ram, setRAM] = useState("RAM: ???"); const [ram, setRAM] = useState("RAM: ???");
const [updatingRam, setUpdatingRam] = useState(false);
const [optionsOpen, setOptionsOpen] = useState(false); const [optionsOpen, setOptionsOpen] = useState(false);
const [options, setOptions] = useState<Options>({ const [options, setOptions] = useState<Options>({
theme: Settings.MonacoTheme, theme: Settings.MonacoTheme,
@ -91,6 +94,15 @@ export function Root(props: IProps): React.ReactElement {
fontSize: Settings.MonacoFontSize, fontSize: Settings.MonacoFontSize,
}); });
const debouncedSetRAM = useMemo(
() =>
debounce((s) => {
setRAM(s);
setUpdatingRam(false);
}, 300),
[],
);
// store the last known state in case we need to restart without nano. // store the last known state in case we need to restart without nano.
useEffect(() => { useEffect(() => {
if (props.filename === undefined) return; if (props.filename === undefined) return;
@ -165,6 +177,7 @@ export function Root(props: IProps): React.ReactElement {
for (let i = 0; i < server.scripts.length; i++) { for (let i = 0; i < server.scripts.length; i++) {
if (filename == server.scripts[i].filename) { if (filename == server.scripts[i].filename) {
server.scripts[i].saveScript(filename, code, props.player.currentServer, server.scripts); server.scripts[i].saveScript(filename, code, props.player.currentServer, server.scripts);
if (Settings.SaveGameOnFileSave) saveObject.saveGame();
props.router.toTerminal(); props.router.toTerminal();
return; return;
} }
@ -178,6 +191,7 @@ export function Root(props: IProps): React.ReactElement {
for (let i = 0; i < server.textFiles.length; ++i) { for (let i = 0; i < server.textFiles.length; ++i) {
if (server.textFiles[i].fn === filename) { if (server.textFiles[i].fn === filename) {
server.textFiles[i].write(code); server.textFiles[i].write(code);
if (Settings.SaveGameOnFileSave) saveObject.saveGame();
props.router.toTerminal(); props.router.toTerminal();
return; return;
} }
@ -188,6 +202,8 @@ export function Root(props: IProps): React.ReactElement {
dialogBoxCreate("Invalid filename. Must be either a script (.script, .js, or .ns) or " + " or text file (.txt)"); dialogBoxCreate("Invalid filename. Must be either a script (.script, .js, or .ns) or " + " or text file (.txt)");
return; return;
} }
if (Settings.SaveGameOnFileSave) saveObject.saveGame();
props.router.toTerminal(); props.router.toTerminal();
} }
@ -213,38 +229,40 @@ export function Root(props: IProps): React.ReactElement {
lastPosition = editorRef.current.getPosition(); lastPosition = editorRef.current.getPosition();
} }
setCode(newCode); setCode(newCode);
updateRAM(newCode);
} }
async function updateRAM(): Promise<void> { // calculate it once the first time the file is loaded.
const codeCopy = code + ""; useEffect(() => {
updateRAM(code);
}, []);
async function updateRAM(newCode: string): Promise<void> {
setUpdatingRam(true);
const codeCopy = newCode + "";
const ramUsage = await calculateRamUsage(codeCopy, props.player.getCurrentServer().scripts); const ramUsage = await calculateRamUsage(codeCopy, props.player.getCurrentServer().scripts);
if (ramUsage > 0) { if (ramUsage > 0) {
setRAM("RAM: " + numeralWrapper.formatRAM(ramUsage)); debouncedSetRAM("RAM: " + numeralWrapper.formatRAM(ramUsage));
return; return;
} }
switch (ramUsage) { switch (ramUsage) {
case RamCalculationErrorCode.ImportError: { case RamCalculationErrorCode.ImportError: {
setRAM("RAM: Import Error"); debouncedSetRAM("RAM: Import Error");
break; break;
} }
case RamCalculationErrorCode.URLImportError: { case RamCalculationErrorCode.URLImportError: {
setRAM("RAM: HTTP Import Error"); debouncedSetRAM("RAM: HTTP Import Error");
break; break;
} }
case RamCalculationErrorCode.SyntaxError: case RamCalculationErrorCode.SyntaxError:
default: { default: {
setRAM("RAM: Syntax Error"); debouncedSetRAM("RAM: Syntax Error");
break; break;
} }
} }
return new Promise<void>(() => undefined); return new Promise<void>(() => undefined);
} }
useEffect(() => {
const id = setInterval(updateRAM, 1000);
return () => clearInterval(id);
}, [code]);
useEffect(() => { useEffect(() => {
function maybeSave(event: KeyboardEvent): void { function maybeSave(event: KeyboardEvent): void {
if (Settings.DisableHotkeys) return; if (Settings.DisableHotkeys) return;
@ -326,7 +344,9 @@ export function Root(props: IProps): React.ReactElement {
/> />
<Box display="flex" flexDirection="row" sx={{ m: 1 }} alignItems="center"> <Box display="flex" flexDirection="row" sx={{ m: 1 }} alignItems="center">
<Button onClick={beautify}>Beautify</Button> <Button onClick={beautify}>Beautify</Button>
<Typography sx={{ mx: 1 }}>{ram}</Typography> <Typography color={updatingRam ? "secondary" : "primary"} sx={{ mx: 1 }}>
{ram}
</Typography>
<Button onClick={save}>Save & Close (Ctrl/Cmd + b)</Button> <Button onClick={save}>Save & Close (Ctrl/Cmd + b)</Button>
<Link sx={{ mx: 1 }} target="_blank" href="https://bitburner.readthedocs.io/en/latest/index.html"> <Link sx={{ mx: 1 }} target="_blank" href="https://bitburner.readthedocs.io/en/latest/index.html">
<Typography> Netscript Documentation</Typography> <Typography> Netscript Documentation</Typography>

@ -1,4 +1,4 @@
export async function loadThemes(monaco: any): Promise<void> { export async function loadThemes(monaco: { editor: any }): Promise<void> {
monaco.editor.defineTheme("monokai", { monaco.editor.defineTheme("monokai", {
base: "vs-dark", base: "vs-dark",
inherit: true, inherit: true,

@ -125,14 +125,16 @@ export class Server extends BaseServer {
/** /**
* Change this server's maximum money * Change this server's maximum money
* @param n - Value by which to change the server's maximum money * @param n - Value by which to change the server's maximum money
* @param perc - Whether it should be changed by a percentage, or a flat value
*/ */
changeMaximumMoney(n: number, perc = false): void { changeMaximumMoney(n: number): void {
if (perc) { const softCap = 10e12;
this.moneyMax *= n; if (this.moneyMax > softCap) {
} else { const aboveCap = this.moneyMax - softCap;
this.moneyMax += n; n = 1 + (n - 1) / Math.log(aboveCap) / Math.log(8);
} }
console.log(n);
this.moneyMax *= n;
} }
/** /**

@ -68,6 +68,11 @@ interface IDefaultSettings {
*/ */
MaxTerminalCapacity: number; MaxTerminalCapacity: number;
/**
* Save the game when you save any file.
*/
SaveGameOnFileSave: boolean;
/** /**
* Whether the player should be asked to confirm purchasing each and every augmentation. * Whether the player should be asked to confirm purchasing each and every augmentation.
*/ */
@ -168,6 +173,7 @@ export const defaultSettings: IDefaultSettings = {
MaxLogCapacity: 50, MaxLogCapacity: 50,
MaxPortCapacity: 50, MaxPortCapacity: 50,
MaxTerminalCapacity: 200, MaxTerminalCapacity: 200,
SaveGameOnFileSave: true,
SuppressBuyAugmentationConfirmation: false, SuppressBuyAugmentationConfirmation: false,
SuppressFactionInvites: false, SuppressFactionInvites: false,
SuppressHospitalizationPopup: false, SuppressHospitalizationPopup: false,
@ -226,6 +232,7 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
MaxTerminalCapacity: defaultSettings.MaxTerminalCapacity, MaxTerminalCapacity: defaultSettings.MaxTerminalCapacity,
OwnedAugmentationsOrder: OwnedAugmentationsOrderSetting.AcquirementTime, OwnedAugmentationsOrder: OwnedAugmentationsOrderSetting.AcquirementTime,
PurchaseAugmentationsOrder: PurchaseAugmentationsOrderSetting.Default, PurchaseAugmentationsOrder: PurchaseAugmentationsOrderSetting.Default,
SaveGameOnFileSave: defaultSettings.SaveGameOnFileSave,
SuppressBuyAugmentationConfirmation: defaultSettings.SuppressBuyAugmentationConfirmation, SuppressBuyAugmentationConfirmation: defaultSettings.SuppressBuyAugmentationConfirmation,
SuppressFactionInvites: defaultSettings.SuppressFactionInvites, SuppressFactionInvites: defaultSettings.SuppressFactionInvites,
SuppressHospitalizationPopup: defaultSettings.SuppressHospitalizationPopup, SuppressHospitalizationPopup: defaultSettings.SuppressHospitalizationPopup,
@ -234,7 +241,7 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
SuppressBladeburnerPopup: defaultSettings.SuppressBladeburnerPopup, SuppressBladeburnerPopup: defaultSettings.SuppressBladeburnerPopup,
MonacoTheme: "vs-dark", MonacoTheme: "vs-dark",
MonacoInsertSpaces: false, MonacoInsertSpaces: false,
MonacoFontSize: 10, MonacoFontSize: 20,
theme: { theme: {
primarylight: defaultSettings.theme.primarylight, primarylight: defaultSettings.theme.primarylight,

@ -14,6 +14,7 @@ export const TerminalHelpText: string[] = [
"clear Clear all text on the terminal ", "clear Clear all text on the terminal ",
"cls See 'clear' command ", "cls See 'clear' command ",
"connect [hostname] Connects to a remote server", "connect [hostname] Connects to a remote server",
"cp [src] [dst]: Copy a file",
"download [script/text file] Downloads scripts or text files to your computer", "download [script/text file] Downloads scripts or text files to your computer",
"expr [math expression] Evaluate a mathematical expression", "expr [math expression] Evaluate a mathematical expression",
"free Check the machine's memory (RAM) usage", "free Check the machine's memory (RAM) usage",
@ -158,6 +159,7 @@ export const HelpTexts: IMap<string[]> = {
"to this command. Note that only servers that are immediately adjacent to the current server in the network can be connected to. To ", "to this command. Note that only servers that are immediately adjacent to the current server in the network can be connected to. To ",
"see which servers can be connected to, use the 'scan' command.", "see which servers can be connected to, use the 'scan' command.",
], ],
cp: ["cp [src] [dst]", " ", "Copy a file on this server. To copy a file to another server use scp."],
download: [ download: [
"download [script/text file]", "download [script/text file]",
" ", " ",

@ -41,6 +41,7 @@ import { cat } from "./commands/cat";
import { cd } from "./commands/cd"; import { cd } from "./commands/cd";
import { check } from "./commands/check"; import { check } from "./commands/check";
import { connect } from "./commands/connect"; import { connect } from "./commands/connect";
import { cp } from "./commands/cp";
import { download } from "./commands/download"; import { download } from "./commands/download";
import { expr } from "./commands/expr"; import { expr } from "./commands/expr";
import { free } from "./commands/free"; import { free } from "./commands/free";
@ -729,6 +730,7 @@ export class Terminal implements ITerminal {
clear: () => this.clear(), clear: () => this.clear(),
cls: () => this.clear(), cls: () => this.clear(),
connect: connect, connect: connect,
cp: cp,
download: download, download: download,
expr: expr, expr: expr,
free: free, free: free,

@ -0,0 +1,92 @@
import { ITerminal } from "../ITerminal";
import { IRouter } from "../../ui/Router";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { isScriptFilename } from "../../Script/isScriptFilename";
export function cp(
terminal: ITerminal,
router: IRouter,
player: IPlayer,
server: BaseServer,
args: (string | number)[],
): void {
try {
if (args.length !== 2) {
terminal.error("Incorrect usage of cp command. Usage: cp [src] [dst]");
return;
}
const src = args[0] + "";
const dst = args[1] + "";
if (src === dst) {
terminal.error("src and dst cannot be the same");
return;
}
const srcExt = src.slice(src.lastIndexOf("."));
const dstExt = dst.slice(dst.lastIndexOf("."));
if (srcExt !== dstExt) {
terminal.error("src and dst must have the same extension.");
return;
}
const filename = terminal.getFilepath(src);
if (!isScriptFilename(filename) && !filename.endsWith(".txt")) {
terminal.error("cp only works for scripts and .txt files");
return;
}
// Scp for txt files
if (filename.endsWith(".txt")) {
let txtFile = null;
for (let i = 0; i < server.textFiles.length; ++i) {
if (server.textFiles[i].fn === filename) {
txtFile = server.textFiles[i];
break;
}
}
if (txtFile === null) {
return terminal.error("No such file exists!");
}
const tRes = server.writeToTextFile(dst, txtFile.text);
if (!tRes.success) {
terminal.error("scp failed");
return;
}
if (tRes.overwritten) {
terminal.print(`WARNING: ${dst} already exists and will be overwriten`);
terminal.print(`${dst} overwritten`);
return;
}
terminal.print(`${dst} copied`);
return;
}
// Get the current script
let sourceScript = null;
for (let i = 0; i < server.scripts.length; ++i) {
if (filename == server.scripts[i].filename) {
sourceScript = server.scripts[i];
break;
}
}
if (sourceScript == null) {
terminal.error("cp() failed. No such script exists");
return;
}
const sRes = server.writeToScriptFile(dst, sourceScript.code);
if (!sRes.success) {
terminal.error(`scp failed`);
return;
}
if (sRes.overwritten) {
terminal.print(`WARNING: ${dst} already exists and will be overwritten`);
terminal.print(`${dst} overwritten`);
return;
}
terminal.print(`${dst} copied`);
} catch (e) {
terminal.error(e + "");
}
}

@ -7,6 +7,7 @@ import { startWorkerScript } from "../../NetscriptWorker";
import { RunningScript } from "../../Script/RunningScript"; import { RunningScript } from "../../Script/RunningScript";
import { findRunningScript } from "../../Script/ScriptHelpers"; import { findRunningScript } from "../../Script/ScriptHelpers";
import * as libarg from "arg"; import * as libarg from "arg";
import { numeralWrapper } from "../../ui/numeralFormat";
export function runScript( export function runScript(
terminal: ITerminal, terminal: ITerminal,
@ -64,8 +65,8 @@ export function runScript(
"This machine does not have enough RAM to run this script with " + "This machine does not have enough RAM to run this script with " +
numThreads + numThreads +
" threads. Script requires " + " threads. Script requires " +
ramUsage + numeralWrapper.formatRAM(ramUsage) +
"GB of RAM", " of RAM",
); );
return; return;
} }

@ -18,6 +18,7 @@ const commands = [
"clear", "clear",
"cls", "cls",
"connect", "connect",
"cp",
"download", "download",
"expr", "expr",
"free", "free",
@ -239,6 +240,13 @@ export function determineAllPossibilitiesForTabCompletion(
return allPos; return allPos;
} }
if (isCommand("cp") && index === 0) {
addAllScripts();
addAllTextFiles();
addAllDirectories();
return allPos;
}
if (isCommand("connect")) { if (isCommand("connect")) {
// All network connections // All network connections
for (let i = 0; i < currServ.serversOnNetwork.length; ++i) { for (let i = 0; i < currServ.serversOnNetwork.length; ++i) {

@ -34,8 +34,6 @@ import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags";
import { initSymbolToStockMap, processStockPrices } from "./StockMarket/StockMarket"; import { initSymbolToStockMap, processStockPrices } from "./StockMarket/StockMarket";
import { Terminal } from "./Terminal"; import { Terminal } from "./Terminal";
import { Sleeve } from "./PersonObjects/Sleeve/Sleeve"; import { Sleeve } from "./PersonObjects/Sleeve/Sleeve";
import { Locations } from "./Locations/Locations";
import { LocationName } from "./Locations/data/LocationNames";
import { Money } from "./ui/React/Money"; import { Money } from "./ui/React/Money";
import { Hashes } from "./ui/React/Hashes"; import { Hashes } from "./ui/React/Hashes";
@ -91,34 +89,7 @@ const Engine: {
Terminal.process(Router, Player, numCycles); Terminal.process(Router, Player, numCycles);
// Working Player.process(Router, numCycles);
if (Player.isWorking) {
if (Player.workType == CONSTANTS.WorkTypeFaction) {
if (Player.workForFaction(numCycles)) {
Router.toFaction();
}
} else if (Player.workType == CONSTANTS.WorkTypeCreateProgram) {
if (Player.createProgramWork(numCycles)) {
Router.toTerminal();
}
} else if (Player.workType == CONSTANTS.WorkTypeStudyClass) {
if (Player.takeClass(numCycles)) {
Router.toCity();
}
} else if (Player.workType == CONSTANTS.WorkTypeCrime) {
if (Player.commitCrime(numCycles)) {
Router.toLocation(Locations[LocationName.Slums]);
}
} else if (Player.workType == CONSTANTS.WorkTypeCompanyPartTime) {
if (Player.workPartTime(numCycles)) {
Router.toCity();
}
} else {
if (Player.work(numCycles)) {
Router.toCity();
}
}
}
// Update stock prices // Update stock prices
if (Player.hasWseAccount) { if (Player.hasWseAccount) {

@ -77,6 +77,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
const [disableTextEffects, setDisableTextEffects] = useState(Settings.DisableTextEffects); const [disableTextEffects, setDisableTextEffects] = useState(Settings.DisableTextEffects);
const [enableBashHotkeys, setEnableBashHotkeys] = useState(Settings.EnableBashHotkeys); const [enableBashHotkeys, setEnableBashHotkeys] = useState(Settings.EnableBashHotkeys);
const [enableTimestamps, setEnableTimestamps] = useState(Settings.EnableTimestamps); const [enableTimestamps, setEnableTimestamps] = useState(Settings.EnableTimestamps);
const [saveGameOnFileSave, setSaveGameOnFileSave] = useState(Settings.SaveGameOnFileSave);
const [locale, setLocale] = useState(Settings.Locale); const [locale, setLocale] = useState(Settings.Locale);
const [diagnosticOpen, setDiagnosticOpen] = useState(false); const [diagnosticOpen, setDiagnosticOpen] = useState(false);
@ -165,6 +166,10 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
setEnableTimestamps(event.target.checked); setEnableTimestamps(event.target.checked);
Settings.EnableTimestamps = event.target.checked; Settings.EnableTimestamps = event.target.checked;
} }
function handleSaveGameOnFile(event: React.ChangeEvent<HTMLInputElement>): void {
setSaveGameOnFileSave(event.target.checked);
Settings.SaveGameOnFileSave = event.target.checked;
}
function startImport(): void { function startImport(): void {
if (!window.File || !window.FileReader || !window.FileList || !window.Blob) return; if (!window.File || !window.FileReader || !window.FileList || !window.Blob) return;
@ -505,6 +510,19 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
/> />
</ListItem> </ListItem>
<ListItem>
<FormControlLabel
control={<Switch checked={saveGameOnFileSave} onChange={handleSaveGameOnFile} />}
label={
<Tooltip
title={<Typography>Save your game any time a file is saved in the script editor.</Typography>}
>
<Typography>Save game on file save</Typography>
</Tooltip>
}
/>
</ListItem>
<ListItem> <ListItem>
<Tooltip title={<Typography>Sets the locale for displaying numbers.</Typography>}> <Tooltip title={<Typography>Sets the locale for displaying numbers.</Typography>}>
<Typography>Locale&nbsp;</Typography> <Typography>Locale&nbsp;</Typography>

@ -11,6 +11,7 @@ import { BaseServer } from "../../Server/BaseServer";
import { HacknetServer } from "../../Hacknet/HacknetServer"; import { HacknetServer } from "../../Hacknet/HacknetServer";
import Select, { SelectChangeEvent } from "@mui/material/Select"; import Select, { SelectChangeEvent } from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";
import Button from "@mui/material/Button";
// TODO make this an enum when this gets converted to TypeScript // TODO make this an enum when this gets converted to TypeScript
export const ServerType = { export const ServerType = {
@ -21,6 +22,8 @@ export const ServerType = {
}; };
interface IProps { interface IProps {
purchase: () => void;
canPurchase: boolean;
serverType: number; serverType: number;
onChange: (event: SelectChangeEvent<string>) => void; onChange: (event: SelectChangeEvent<string>) => void;
value: string; value: string;
@ -61,7 +64,16 @@ export function ServerDropdown(props: IProps): React.ReactElement {
} }
return ( return (
<Select sx={{ mx: 1 }} value={props.value} onChange={props.onChange}> <Select
startAdornment={
<Button onClick={props.purchase} disabled={!props.canPurchase}>
Buy
</Button>
}
sx={{ mx: 1 }}
value={props.value}
onChange={props.onChange}
>
{servers} {servers}
</Select> </Select>
); );

@ -36,24 +36,16 @@ export function TablePaginationActionsAll(props: TablePaginationActionsProps): R
return ( return (
<Box sx={{ flexShrink: 0, ml: 2.5 }}> <Box sx={{ flexShrink: 0, ml: 2.5 }}>
<IconButton onClick={handleFirstPageButtonClick} disabled={page === 0} aria-label="first page"> <IconButton onClick={handleFirstPageButtonClick} disabled={page === 0}>
{theme.direction === "rtl" ? <LastPageIcon /> : <FirstPageIcon />} {theme.direction === "rtl" ? <LastPageIcon /> : <FirstPageIcon />}
</IconButton> </IconButton>
<IconButton onClick={handleBackButtonClick} disabled={page === 0} aria-label="previous page"> <IconButton onClick={handleBackButtonClick} disabled={page === 0}>
{theme.direction === "rtl" ? <KeyboardArrowRight /> : <KeyboardArrowLeft />} {theme.direction === "rtl" ? <KeyboardArrowRight /> : <KeyboardArrowLeft />}
</IconButton> </IconButton>
<IconButton <IconButton onClick={handleNextButtonClick} disabled={page >= Math.ceil(count / rowsPerPage) - 1}>
onClick={handleNextButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="next page"
>
{theme.direction === "rtl" ? <KeyboardArrowLeft /> : <KeyboardArrowRight />} {theme.direction === "rtl" ? <KeyboardArrowLeft /> : <KeyboardArrowRight />}
</IconButton> </IconButton>
<IconButton <IconButton onClick={handleLastPageButtonClick} disabled={page >= Math.ceil(count / rowsPerPage) - 1}>
onClick={handleLastPageButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="last page"
>
{theme.direction === "rtl" ? <FirstPageIcon /> : <LastPageIcon />} {theme.direction === "rtl" ? <FirstPageIcon /> : <LastPageIcon />}
</IconButton> </IconButton>
</Box> </Box>

@ -276,6 +276,12 @@ export function refreshTheme(): void {
select: { select: {
color: Settings.theme.primary, color: Settings.theme.primary,
}, },
selectLabel: {
color: Settings.theme.primary,
},
displayedRows: {
color: Settings.theme.primary,
},
}, },
}, },
MuiTab: { MuiTab: {

@ -106,6 +106,10 @@ class NumeralFormatter {
} }
formatRAM(n: number): string { 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";
if (n < 1e9) return this.format(n / 1e6, "0.00") + "PB";
if (n < 1e12) return this.format(n / 1e9, "0.00") + "EB";
return this.format(n, "0.00") + "GB"; return this.format(n, "0.00") + "GB";
} }

@ -12,8 +12,7 @@ export interface IReviverValue {
// off to that `fromJSON` fuunction, passing in the value. // off to that `fromJSON` fuunction, passing in the value.
export function Reviver(key: string, value: IReviverValue | null): any { export function Reviver(key: string, value: IReviverValue | null): any {
if (value == null) { if (value == null) {
console.log("Reviver WRONGLY called with key: " + key + ", and value: " + value); return null;
return 0;
} }
if (typeof value === "object" && typeof value.ctor === "string" && typeof value.data !== "undefined") { if (typeof value === "object" && typeof value.ctor === "string" && typeof value.data !== "undefined") {