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-class-assign": ["error"],
"no-compare-neg-zero": ["error"],
"no-cond-assign": ["off", "except-parens"],
"no-confusing-arrow": ["error"],
"no-console": ["off"],
"no-const-assign": ["error"],

@ -84,6 +84,28 @@ changes are okay to contribute:
- Changes that directly affect the game's balance
- 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
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
=========
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)
-------------------------------------------

@ -64,9 +64,9 @@ documentation_title = '{0} Documentation'.format(project)
# built documents.
#
# The short X.Y version.
version = '0.55'
version = '0.56'
# 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
# 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",
"license": "SEE LICENSE IN license.txt",
"version": "0.53.0",
"version": "0.56.0",
"main": "electron-main.js",
"author": {
"name": "Daniel Xie"

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

@ -8,16 +8,18 @@ const path = require("path");
const exec = require("child_process").exec;
const semver = require("./semver");
const getPackageJson = () => new Promise((resolve, reject) => {
const getPackageJson = () =>
new Promise((resolve, reject) => {
try {
/* eslint-disable-next-line global-require */
resolve(require(path.resolve(process.cwd(), "package.json")));
} catch (error) {
reject(error);
}
});
});
const getEngines = (data) => new Promise((resolve, reject) => {
const getEngines = (data) =>
new Promise((resolve, reject) => {
let versions = null;
if (data.engines) {
@ -29,9 +31,10 @@ const getEngines = (data) => new Promise((resolve, reject) => {
} else {
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) => {
if (error) {
reject(`Unable to find NPM version\n${stderr}`);
@ -43,20 +46,25 @@ const checkNpmVersion = (engines) => new Promise((resolve, reject) => {
if (semver.satisfies(npmVersion, engineVersion)) {
resolve();
} 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);
if (semver.satisfies(nodeVersion, engines.node)) {
resolve(engines);
} 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()
.then(getEngines)
@ -69,5 +77,5 @@ getPackageJson()
/* eslint-disable no-console, no-process-exit */
console.error(error);
process.exit(1);
}
},
);

@ -444,7 +444,6 @@ function parseComparator(comp, loose) {
}
class SemVer {
/**
* A semantic version.
* @param {string} version The version.
@ -488,7 +487,7 @@ class SemVer {
// Numberify any prerelease numeric ids
if (matches[4]) {
this.prerelease = matches[4].split(".").map((id) => {
if ((/^[0-9]+$/).test(id)) {
if (/^[0-9]+$/.test(id)) {
const num = Number(id);
if (num >= 0 && num < MAX_SAFE_INTEGER) {
return num;
@ -532,7 +531,9 @@ class SemVer {
}
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 lt = (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}`);
continue;
}
if (facObj.getInfo().special) continue;
facObj.augmentations.push(this.name);
}
}

@ -1,6 +1,114 @@
import { IMap } from "../../types";
export const AugmentationNames: IMap<string> = {
export const AugmentationNames: {
Targeting1: 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",
Targeting2: "Augmented Targeting II",
Targeting3: "Augmented Targeting III",
@ -87,7 +195,7 @@ export const AugmentationNames: IMap<string> = {
NutriGen: "NutriGen Implant",
INFRARet: "INFRARET Enhancement",
DermaForce: "DermaForce Particle Barrier",
GrapheneBrachiBlades: "Graphene BranchiBlades Upgrade",
GrapheneBrachiBlades: "Graphene BrachiBlades Upgrade",
GrapheneBionicArms: "Graphene Bionic Arms Upgrade",
BrachiBlades: "BrachiBlades",
BionicArms: "Bionic Arms",

@ -114,7 +114,7 @@ export const CONSTANTS: {
TotalNumBitNodes: number;
LatestUpdate: string;
} = {
Version: "0.55.0",
Version: "0.56.0",
// Speed (in ms) at which the main loop is updated
_idleSpeed: 200,
@ -281,31 +281,107 @@ export const CONSTANTS: {
TotalNumBitNodes: 24,
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. **
* Corporations can no longer bribe special factions
* Infiltration can no longer lose focus of the keyboard.
* Fix terminal line limit
* Added theme editor
* Theme applies on game load (@Nolshine)
* Sleeves no longer consume all bonus time for some actions
* Fix a bug where the autocomlete list would get duplicates
* Fix tutorial not scaling properly on small screens
* Import should be more consistent
* Typo with 'help' command
* Fix infinite loop in casino
* 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
`,
/*
*/
};

@ -105,6 +105,7 @@ export function SellMaterial(mat: Material, amt: string, price: string): void {
}
//Parse quantity
amt = amt.toUpperCase();
if (amt.includes("MAX") || amt.includes("PROD")) {
let q = amt.replace(/\s+/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);
// Parse quantity
amt = amt.toUpperCase();
if (amt.includes("MAX") || amt.includes("PROD")) {
//Dynamically evaluated quantity. First test to make sure its valid
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 {
// Sanitize amt
let sanitizedAmt = amt.replace(/\s+/g, "");
let sanitizedAmt = amt.replace(/\s+/g, "").toUpperCase();
sanitizedAmt = sanitizedAmt.replace(/[^-()\d/*+.MAX]/g, "");
let temp = sanitizedAmt.replace(/MAX/g, "1");
try {

@ -564,7 +564,7 @@ export class Industry implements IIndustry {
buyAmt = mat.buy * CorporationConstants.SecsPerMarketCycle * marketCycles;
if (matName == "RealEstate") {
maxAmt = buyAmt;
maxAmt = corporation.funds.toNumber() / mat.bCost;
} else {
maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / MaterialSizes[matName]);
}
@ -840,7 +840,7 @@ export class Industry implements IIndustry {
let sellAmt;
if (isString(mat.sllman[1])) {
//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 + "");
try {
sellAmt = eval(tmp);
@ -893,7 +893,7 @@ export class Industry implements IIndustry {
const exp = mat.exp[expI];
const amtStr = exp.amt.replace(
/MAX/g,
mat.qty / (CorporationConstants.SecsPerMarketCycle * marketCycles) + "",
(mat.qty / (CorporationConstants.SecsPerMarketCycle * marketCycles) + "").toUpperCase(),
);
let amt = 0;
try {
@ -1201,7 +1201,7 @@ export class Industry implements IIndustry {
let sellAmt;
if (product.sllman[city][0] && isString(product.sllman[city][1])) {
//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]);
try {
tmp = eval(tmp);

@ -80,10 +80,20 @@ export function ExpandIndustryTab(props: IProps): React.ReactElement {
<Typography>Division name:</Typography>
<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}>
Create Division
Expand
</Button>
),
}}
/>
</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{" "}
<MoneyCost money={CorporationConstants.OfficeInitialCost} corp={corp} />
</Typography>
<Select value={city} onChange={onCityChange}>
<Select
endAdornment={
<Button onClick={expand} disabled={disabled}>
Confirm
</Button>
}
value={city}
onChange={onCityChange}
>
{possibleCities.map((cityName: string) => (
<MenuItem key={cityName} value={cityName}>
{cityName}
</MenuItem>
))}
</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 {
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 {

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

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

@ -50,6 +50,11 @@ export class FactionInfo {
*/
keep: boolean;
/**
* Special faction
*/
special: boolean;
constructor(
infoText: JSX.Element,
enemies: string[],
@ -57,6 +62,7 @@ export class FactionInfo {
offerHackingWork: boolean,
offerFieldWork: boolean,
offerSecurityWork: boolean,
special: boolean,
keep: boolean,
) {
this.infoText = infoText;
@ -70,6 +76,7 @@ export class FactionInfo {
this.augmentationPriceMult = 1;
this.augmentationRepRequirementMult = 1;
this.keep = keep;
this.special = special;
}
offersWork(): boolean {
@ -96,6 +103,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true,
false,
false,
false,
),
Daedalus: new FactionInfo(
@ -106,6 +114,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true,
false,
false,
false,
),
"The Covenant": new FactionInfo(
@ -124,6 +133,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true,
false,
false,
false,
),
// Megacorporations, each forms its own faction
@ -139,6 +149,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true,
true,
true,
false,
true,
),
@ -158,6 +169,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true,
true,
true,
false,
true,
),
@ -175,10 +187,11 @@ export const FactionInfos: IMap<FactionInfo> = {
true,
true,
true,
false,
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(
(
@ -193,10 +206,20 @@ export const FactionInfos: IMap<FactionInfo> = {
true,
true,
true,
false,
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(
<>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,
false,
true,
),
@ -220,10 +244,20 @@ export const FactionInfos: IMap<FactionInfo> = {
true,
true,
true,
false,
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
"Fulcrum Secret Technologies": new FactionInfo(
@ -238,6 +272,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true,
false,
true,
false,
true,
),
@ -261,6 +296,7 @@ export const FactionInfos: IMap<FactionInfo> = {
false,
false,
false,
false,
),
"The Black Hand": new FactionInfo(
@ -280,6 +316,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true,
false,
false,
false,
),
// prettier-ignore
@ -325,6 +362,7 @@ export const FactionInfos: IMap<FactionInfo> = {
false,
false,
false,
false,
),
// City factions, essentially governments
@ -336,8 +374,18 @@ export const FactionInfos: IMap<FactionInfo> = {
true,
true,
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(
<>The East Asian Order of the Future.</>,
["Sector-12", "Aevum", "Volhaven"],
@ -346,6 +394,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true,
true,
false,
false,
),
"New Tokyo": new FactionInfo(
<>Asia's World City.</>,
@ -355,6 +404,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true,
true,
false,
false,
),
"Sector-12": new FactionInfo(
<>The City of the Future.</>,
@ -364,6 +414,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true,
true,
false,
false,
),
Volhaven: new FactionInfo(
<>Benefit, Honor, and Glory.</>,
@ -373,6 +424,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true,
true,
false,
false,
),
// Criminal Organizations/Gangs
@ -384,6 +436,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true,
true,
false,
false,
),
"The Dark Army": new FactionInfo(
@ -394,9 +447,10 @@ export const FactionInfos: IMap<FactionInfo> = {
true,
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(
(
@ -415,6 +469,7 @@ export const FactionInfos: IMap<FactionInfo> = {
true,
false,
false,
false,
),
Tetrads: new FactionInfo(
@ -425,14 +480,15 @@ export const FactionInfos: IMap<FactionInfo> = {
true,
true,
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.
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(
(
@ -448,6 +504,7 @@ export const FactionInfos: IMap<FactionInfo> = {
false,
false,
false,
false,
),
// Special Factions
@ -466,6 +523,7 @@ export const FactionInfos: IMap<FactionInfo> = {
false,
false,
false,
true,
false,
),
};

@ -41,6 +41,7 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
if (isPlayersGang) {
const augs: string[] = [];
for (const augName in Augmentations) {
if (augName === AugmentationNames.NeuroFluxGovernor) continue;
const aug = Augmentations[augName];
if (!aug.isSpecial) {
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.`);
target.changeMaximumMoney(upg.value, true);
const old = target.moneyMax;
target.changeMaximumMoney(upg.value);
console.log(target.moneyMax / old);
} catch (e) {
player.hashManager.refundUpgrade(upgName);
return false;

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

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

@ -64,13 +64,21 @@ export function HacknetUpgradeElem(props: IProps): React.ReactElement {
</Typography>
<Typography>{upg.desc}</Typography>
{!upg.hasTargetServer && (
<Button onClick={purchase} disabled={!canPurchase}>
Purchase
Buy
</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>
);
}

@ -8,6 +8,7 @@ import { purchaseRamForHomeComputer } from "../../Server/ServerPurchases";
import { Money } from "../../ui/React/Money";
import { MathComponent } from "mathjax-react";
import { numeralWrapper } from "../../ui/numeralFormat";
type IProps = {
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)}}}`} />}>
<span>
<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} />
</Button>
</span>

@ -17,6 +17,7 @@ import { getPurchaseServerCost } from "../../Server/ServerPurchases";
import { Money } from "../../ui/React/Money";
import { use } from "../../ui/Context";
import { PurchaseServerModal } from "./PurchaseServerModal";
import { numeralWrapper } from "../../ui/numeralFormat";
interface IServerProps {
ram: number;
@ -30,7 +31,7 @@ function ServerButton(props: IServerProps): React.ReactElement {
return (
<>
<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} />
</Button>
<PurchaseServerModal

@ -10,7 +10,6 @@ import { RunningScript } from "../Script/RunningScript";
import { GetServer } from "../Server/AllServers";
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(workerScript: WorkerScript): boolean;

@ -13,13 +13,12 @@ export function netscriptDelay(time: number, workerScript: WorkerScript): Promis
}
export function makeRuntimeRejectMsg(workerScript: WorkerScript, msg: string): string {
const lineNum = "";
const server = GetServer(workerScript.hostname);
if (server == null) {
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(

@ -122,6 +122,7 @@ import { Router } from "./ui/GameRoot";
import { numeralWrapper } from "./ui/numeralFormat";
import { is2DArray } from "./utils/helpers/is2DArray";
import { convertTimeMsToTimeElapsedString } from "./utils/StringHelperFunctions";
import { SpecialServers } from "./Server/data/SpecialServers";
import { LogBoxEvents } from "./ui/React/LogBoxManager";
import { arrayToString } from "./utils/helpers/arrayToString";
@ -136,6 +137,7 @@ import { IIndustry } from "./Corporation/IIndustry";
import { Faction } from "./Faction/Faction";
import { Augmentation } from "./Augmentation/Augmentation";
import { Page } from "./ui/Router";
import { CodingContract } from "./CodingContracts";
import { Stock } from "./StockMarket/Stock";
@ -713,7 +715,7 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
const gang = NetscriptGang(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 functions = {
@ -3076,6 +3078,10 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
workerScript.log("installBackdoor", `Successfully installed backdoor on '${server.hostname}'`);
server.backdoorInstalled = true;
if (SpecialServers.WorldDaemon === server.hostname) {
Router.toBitVerse(false, false);
}
return Promise.resolve();
});
},
@ -3239,12 +3245,16 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
hospitalize: function (): any {
updateDynamicRam("hospitalize", getRamCost("hospitalize"));
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();
},
isBusy: function (): any {
updateDynamicRam("isBusy", getRamCost("isBusy"));
checkSingularityAccess("isBusy", 1);
return Player.isWorking;
return Player.isWorking || Router.page() === Page.Infiltration || Router.page() === Page.BitVerse;
},
stopAction: function (): any {
updateDynamicRam("stopAction", getRamCost("stopAction"));
@ -3280,7 +3290,9 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain);
workerScript.log(
"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;
},
@ -4170,8 +4182,6 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
},
joinBladeburnerDivision: function (): any {
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 === 8) {
return false;

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

@ -8,6 +8,7 @@ import { WorkerScript } from "../Netscript/WorkerScript";
import { findSleevePurchasableAugs } from "../PersonObjects/Sleeve/SleeveHelpers";
import { Augmentations } from "../Augmentation/Augmentations";
import { CityName } from "../Locations/data/CityNames";
import { findCrime } from "../Crime/CrimeHelpers";
export interface INetscriptSleeve {
getNumSleeves(): number;
@ -87,13 +88,17 @@ export function NetscriptSleeve(
checkSleeveNumber("setToSynchronize", sleeveNumber);
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 crimeName = helper.string("setToUniversityCourse", "crimeName", acrimeName);
const crimeRoughName = helper.string("setToCommitCrime", "crimeName", aCrimeRoughName);
helper.updateDynamicRam("setToCommitCrime", getRamCost("sleeve", "setToCommitCrime"));
checkSleeveAPIAccess("setToCommitCrime");
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 {
const sleeveNumber = helper.number("setToUniversityCourse", "sleeveNumber", asleeveNumber);

@ -124,10 +124,14 @@ function startNetscript2Script(workerScript: WorkerScript): Promise<WorkerScript
.catch((e) => reject(e));
}).catch((e) => {
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,
e.message + ((e.stack && "\nstack:\n" + e.stack.toString()) || ""),
);
}
throw workerScript;
} else if (isScriptErrorMessage(e)) {
workerScript.errorMessage = e;

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

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

@ -77,7 +77,6 @@ export function init(this: IPlayer): void {
}
export function prestigeAugmentation(this: IPlayer): void {
const homeComp = this.getHomeComputer();
this.currentServer = SpecialServers.Home;
this.numPeopleKilled = 0;
@ -576,6 +575,37 @@ export function startWork(this: IPlayer, router: IRouter, companyName: string):
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 {
const server = GetServer(this.companyName);
if (server instanceof Server) {

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

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

@ -141,6 +141,18 @@ function evaluateVersionCompatibility(ver: string): void {
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 {

@ -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 * as monaco from "monaco-editor";
type IStandaloneCodeEditor = monaco.editor.IStandaloneCodeEditor;
@ -21,6 +21,8 @@ import { NetscriptFunctions } from "../../NetscriptFunctions";
import { WorkerScript } from "../../Netscript/WorkerScript";
import { Settings } from "../../Settings/Settings";
import { iTutorialNextStep, ITutorial, iTutorialSteps } from "../../InteractiveTutorial";
import { debounce } from "lodash";
import { saveObject } from "../../SaveObject";
import Button from "@mui/material/Button";
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 [code, setCode] = useState<string>(props.filename ? props.code : lastCode);
const [ram, setRAM] = useState("RAM: ???");
const [updatingRam, setUpdatingRam] = useState(false);
const [optionsOpen, setOptionsOpen] = useState(false);
const [options, setOptions] = useState<Options>({
theme: Settings.MonacoTheme,
@ -91,6 +94,15 @@ export function Root(props: IProps): React.ReactElement {
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.
useEffect(() => {
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++) {
if (filename == server.scripts[i].filename) {
server.scripts[i].saveScript(filename, code, props.player.currentServer, server.scripts);
if (Settings.SaveGameOnFileSave) saveObject.saveGame();
props.router.toTerminal();
return;
}
@ -178,6 +191,7 @@ export function Root(props: IProps): React.ReactElement {
for (let i = 0; i < server.textFiles.length; ++i) {
if (server.textFiles[i].fn === filename) {
server.textFiles[i].write(code);
if (Settings.SaveGameOnFileSave) saveObject.saveGame();
props.router.toTerminal();
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)");
return;
}
if (Settings.SaveGameOnFileSave) saveObject.saveGame();
props.router.toTerminal();
}
@ -213,38 +229,40 @@ export function Root(props: IProps): React.ReactElement {
lastPosition = editorRef.current.getPosition();
}
setCode(newCode);
updateRAM(newCode);
}
async function updateRAM(): Promise<void> {
const codeCopy = code + "";
// calculate it once the first time the file is loaded.
useEffect(() => {
updateRAM(code);
}, []);
async function updateRAM(newCode: string): Promise<void> {
setUpdatingRam(true);
const codeCopy = newCode + "";
const ramUsage = await calculateRamUsage(codeCopy, props.player.getCurrentServer().scripts);
if (ramUsage > 0) {
setRAM("RAM: " + numeralWrapper.formatRAM(ramUsage));
debouncedSetRAM("RAM: " + numeralWrapper.formatRAM(ramUsage));
return;
}
switch (ramUsage) {
case RamCalculationErrorCode.ImportError: {
setRAM("RAM: Import Error");
debouncedSetRAM("RAM: Import Error");
break;
}
case RamCalculationErrorCode.URLImportError: {
setRAM("RAM: HTTP Import Error");
debouncedSetRAM("RAM: HTTP Import Error");
break;
}
case RamCalculationErrorCode.SyntaxError:
default: {
setRAM("RAM: Syntax Error");
debouncedSetRAM("RAM: Syntax Error");
break;
}
}
return new Promise<void>(() => undefined);
}
useEffect(() => {
const id = setInterval(updateRAM, 1000);
return () => clearInterval(id);
}, [code]);
useEffect(() => {
function maybeSave(event: KeyboardEvent): void {
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">
<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>
<Link sx={{ mx: 1 }} target="_blank" href="https://bitburner.readthedocs.io/en/latest/index.html">
<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", {
base: "vs-dark",
inherit: true,

@ -125,14 +125,16 @@ export class Server extends BaseServer {
/**
* Change this 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 {
if (perc) {
this.moneyMax *= n;
} else {
this.moneyMax += n;
changeMaximumMoney(n: number): void {
const softCap = 10e12;
if (this.moneyMax > softCap) {
const aboveCap = this.moneyMax - softCap;
n = 1 + (n - 1) / Math.log(aboveCap) / Math.log(8);
}
console.log(n);
this.moneyMax *= n;
}
/**

@ -68,6 +68,11 @@ interface IDefaultSettings {
*/
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.
*/
@ -168,6 +173,7 @@ export const defaultSettings: IDefaultSettings = {
MaxLogCapacity: 50,
MaxPortCapacity: 50,
MaxTerminalCapacity: 200,
SaveGameOnFileSave: true,
SuppressBuyAugmentationConfirmation: false,
SuppressFactionInvites: false,
SuppressHospitalizationPopup: false,
@ -226,6 +232,7 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
MaxTerminalCapacity: defaultSettings.MaxTerminalCapacity,
OwnedAugmentationsOrder: OwnedAugmentationsOrderSetting.AcquirementTime,
PurchaseAugmentationsOrder: PurchaseAugmentationsOrderSetting.Default,
SaveGameOnFileSave: defaultSettings.SaveGameOnFileSave,
SuppressBuyAugmentationConfirmation: defaultSettings.SuppressBuyAugmentationConfirmation,
SuppressFactionInvites: defaultSettings.SuppressFactionInvites,
SuppressHospitalizationPopup: defaultSettings.SuppressHospitalizationPopup,
@ -234,7 +241,7 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
SuppressBladeburnerPopup: defaultSettings.SuppressBladeburnerPopup,
MonacoTheme: "vs-dark",
MonacoInsertSpaces: false,
MonacoFontSize: 10,
MonacoFontSize: 20,
theme: {
primarylight: defaultSettings.theme.primarylight,

@ -14,6 +14,7 @@ export const TerminalHelpText: string[] = [
"clear Clear all text on the terminal ",
"cls See 'clear' command ",
"connect [hostname] Connects to a remote server",
"cp [src] [dst]: Copy a file",
"download [script/text file] Downloads scripts or text files to your computer",
"expr [math expression] Evaluate a mathematical expression",
"free Check the machine's memory (RAM) usage",
@ -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 ",
"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 [script/text file]",
" ",

@ -41,6 +41,7 @@ import { cat } from "./commands/cat";
import { cd } from "./commands/cd";
import { check } from "./commands/check";
import { connect } from "./commands/connect";
import { cp } from "./commands/cp";
import { download } from "./commands/download";
import { expr } from "./commands/expr";
import { free } from "./commands/free";
@ -729,6 +730,7 @@ export class Terminal implements ITerminal {
clear: () => this.clear(),
cls: () => this.clear(),
connect: connect,
cp: cp,
download: download,
expr: expr,
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 { findRunningScript } from "../../Script/ScriptHelpers";
import * as libarg from "arg";
import { numeralWrapper } from "../../ui/numeralFormat";
export function runScript(
terminal: ITerminal,
@ -64,8 +65,8 @@ export function runScript(
"This machine does not have enough RAM to run this script with " +
numThreads +
" threads. Script requires " +
ramUsage +
"GB of RAM",
numeralWrapper.formatRAM(ramUsage) +
" of RAM",
);
return;
}

@ -18,6 +18,7 @@ const commands = [
"clear",
"cls",
"connect",
"cp",
"download",
"expr",
"free",
@ -239,6 +240,13 @@ export function determineAllPossibilitiesForTabCompletion(
return allPos;
}
if (isCommand("cp") && index === 0) {
addAllScripts();
addAllTextFiles();
addAllDirectories();
return allPos;
}
if (isCommand("connect")) {
// All network connections
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 { Terminal } from "./Terminal";
import { Sleeve } from "./PersonObjects/Sleeve/Sleeve";
import { Locations } from "./Locations/Locations";
import { LocationName } from "./Locations/data/LocationNames";
import { Money } from "./ui/React/Money";
import { Hashes } from "./ui/React/Hashes";
@ -91,34 +89,7 @@ const Engine: {
Terminal.process(Router, Player, numCycles);
// Working
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();
}
}
}
Player.process(Router, numCycles);
// Update stock prices
if (Player.hasWseAccount) {

@ -77,6 +77,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
const [disableTextEffects, setDisableTextEffects] = useState(Settings.DisableTextEffects);
const [enableBashHotkeys, setEnableBashHotkeys] = useState(Settings.EnableBashHotkeys);
const [enableTimestamps, setEnableTimestamps] = useState(Settings.EnableTimestamps);
const [saveGameOnFileSave, setSaveGameOnFileSave] = useState(Settings.SaveGameOnFileSave);
const [locale, setLocale] = useState(Settings.Locale);
const [diagnosticOpen, setDiagnosticOpen] = useState(false);
@ -165,6 +166,10 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
setEnableTimestamps(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 {
if (!window.File || !window.FileReader || !window.FileList || !window.Blob) return;
@ -505,6 +510,19 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
/>
</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>
<Tooltip title={<Typography>Sets the locale for displaying numbers.</Typography>}>
<Typography>Locale&nbsp;</Typography>

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

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

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

@ -106,6 +106,10 @@ class NumeralFormatter {
}
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";
}

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