This commit is contained in:
Olivier Gagnon 2022-05-20 15:58:33 -04:00
commit 7eb4494ac1
85 changed files with 4415 additions and 3422 deletions

@ -1,5 +1,7 @@
# DELETE THIS AFTER READING
# READ CONTRIBUTING.md
# PR title
Formatted as such:

4
dist/main.bundle.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

42
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

11
doc/NEW_BN_GUIDELINE.md Normal file

@ -0,0 +1,11 @@
Promote:
- New mechanic
- Coding problems based on NP problems. This makes solution that are easy to implement inefficient and solutions that are hard to implement efficent. (eg. Stanek)
- inter-mechanic synergy
- Simplicity (eg. Stanek, Hashnet. bad example: Corp)
Avoid:
- Failure conditions, it's very frustrating to revert several days worth of progress.
- Making existing mechanic harder. This makes it hard to port the content to other BNs.

1
doc/POTENTIAL_BN_1.md Normal file

@ -0,0 +1 @@
Sleeves meet Screeps (That's all I got)

3
doc/POTENTIAL_BN_2.md Normal file

@ -0,0 +1,3 @@
A game of risk from the point of view of a politician.
You allocate resources on a world map, trying to win elections.

@ -1 +0,0 @@
I want the wiki here https://bitburner.fandom.com/wiki/Bitburner_Wiki taken down please.

465
package-lock.json generated

@ -56,6 +56,10 @@
"@types/bcryptjs": "^2.4.2",
"@types/escodegen": "^0.0.7",
"@types/file-saver": "^2.0.3",
<<<<<<< HEAD
=======
"@types/jest": "^27.4.1",
>>>>>>> dev
"@types/jquery": "^3.5.14",
"@types/lodash": "^4.14.168",
"@types/numeral": "^2.0.2",
@ -2068,19 +2072,33 @@
"integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
},
"node_modules/@eslint/eslintrc": {
<<<<<<< HEAD
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.2.tgz",
"integrity": "sha512-lTVWHs7O2hjBFZunXTZYnYqtB9GakA1lnxIf+gKq2nY5gxkkNi/lQvveW6t8gFdOHTg6nG50Xs95PrLqVpcaLg==",
=======
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.3.tgz",
"integrity": "sha512-uGo44hIwoLGNyduRpjdEpovcbMdd+Nv7amtmJxnKmI8xj6yd5LncmSwDa5NgX/41lIFJtkjD6YdVfgEzPfJ5UA==",
>>>>>>> dev
"dev": true,
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
<<<<<<< HEAD
"espree": "^9.3.1",
=======
"espree": "^9.3.2",
>>>>>>> dev
"globals": "^13.9.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.0",
<<<<<<< HEAD
"minimatch": "^3.0.4",
=======
"minimatch": "^3.1.2",
>>>>>>> dev
"strip-json-comments": "^3.1.1"
},
"engines": {
@ -2094,9 +2112,15 @@
"dev": true
},
"node_modules/@eslint/eslintrc/node_modules/globals": {
<<<<<<< HEAD
"version": "13.13.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz",
"integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==",
=======
"version": "13.15.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz",
"integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==",
>>>>>>> dev
"dev": true,
"dependencies": {
"type-fest": "^0.20.2"
@ -4062,6 +4086,19 @@
"@types/istanbul-lib-report": "*"
}
},
<<<<<<< HEAD
=======
"node_modules/@types/jest": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.1.tgz",
"integrity": "sha512-fUy7YRpT+rHXto1YlL+J9rs0uLGyiqVt3ZOTQR+4ROc47yNl8WLdVLgUloBRhOxP1PZvguHl44T3H0wAWxahYQ==",
"dev": true,
"dependencies": {
"jest-matcher-utils": "^27.0.0",
"pretty-format": "^27.0.0"
}
},
>>>>>>> dev
"node_modules/@types/jquery": {
"version": "3.5.14",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.14.tgz",
@ -4230,6 +4267,7 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
<<<<<<< HEAD
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.21.0.tgz",
"integrity": "sha512-fTU85q8v5ZLpoZEyn/u1S2qrFOhi33Edo2CZ0+q1gDaWWm0JuPh3bgOyU8lM0edIEYgKLDkPFiZX2MOupgjlyg==",
@ -4243,6 +4281,21 @@
"ignore": "^5.1.8",
"regexpp": "^3.2.0",
"semver": "^7.3.5",
=======
"version": "5.25.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.25.0.tgz",
"integrity": "sha512-icYrFnUzvm+LhW0QeJNKkezBu6tJs9p/53dpPLFH8zoM9w1tfaKzVurkPotEpAqQ8Vf8uaFyL5jHd0Vs6Z0ZQg==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "5.25.0",
"@typescript-eslint/type-utils": "5.25.0",
"@typescript-eslint/utils": "5.25.0",
"debug": "^4.3.4",
"functional-red-black-tree": "^1.0.1",
"ignore": "^5.2.0",
"regexpp": "^3.2.0",
"semver": "^7.3.7",
>>>>>>> dev
"tsutils": "^3.21.0"
},
"engines": {
@ -4262,10 +4315,27 @@
}
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
@ -4278,6 +4348,7 @@
}
},
"node_modules/@typescript-eslint/parser": {
<<<<<<< HEAD
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.21.0.tgz",
"integrity": "sha512-8RUwTO77hstXUr3pZoWZbRQUxXcSXafZ8/5gpnQCfXvgmP9gpNlRGlWzvfbEQ14TLjmtU8eGnONkff8U2ui2Eg==",
@ -4287,6 +4358,17 @@
"@typescript-eslint/types": "5.21.0",
"@typescript-eslint/typescript-estree": "5.21.0",
"debug": "^4.3.2"
=======
"version": "5.25.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.25.0.tgz",
"integrity": "sha512-r3hwrOWYbNKP1nTcIw/aZoH+8bBnh/Lh1iDHoFpyG4DnCpvEdctrSl6LOo19fZbzypjQMHdajolxs6VpYoChgA==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "5.25.0",
"@typescript-eslint/types": "5.25.0",
"@typescript-eslint/typescript-estree": "5.25.0",
"debug": "^4.3.4"
>>>>>>> dev
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -4304,6 +4386,7 @@
}
}
},
<<<<<<< HEAD
"node_modules/@typescript-eslint/scope-manager": {
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.21.0.tgz",
@ -4314,6 +4397,34 @@
"@typescript-eslint/visitor-keys": "5.21.0"
},
"engines": {
=======
"node_modules/@typescript-eslint/parser/node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "5.25.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.25.0.tgz",
"integrity": "sha512-p4SKTFWj+2VpreUZ5xMQsBMDdQ9XdRvODKXN4EksyBjFp2YvQdLkyHqOffakYZPuWJUDNu3jVXtHALDyTv3cww==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.25.0",
"@typescript-eslint/visitor-keys": "5.25.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
@ -4321,6 +4432,26 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "5.25.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.25.0.tgz",
"integrity": "sha512-B6nb3GK3Gv1Rsb2pqalebe/RyQoyG/WDy9yhj8EE0Ikds4Xa8RR28nHz+wlt4tMZk5bnAr0f3oC8TuDAd5CPrw==",
"dev": true,
"dependencies": {
"@typescript-eslint/utils": "5.25.0",
"debug": "^4.3.4",
"tsutils": "^3.21.0"
},
"engines": {
>>>>>>> dev
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
<<<<<<< HEAD
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.21.0.tgz",
@ -4343,14 +4474,45 @@
},
"peerDependenciesMeta": {
"typescript": {
=======
},
"peerDependencies": {
"eslint": "*"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
>>>>>>> dev
"optional": true
}
}
},
"node_modules/@typescript-eslint/types": {
<<<<<<< HEAD
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.21.0.tgz",
"integrity": "sha512-XnOOo5Wc2cBlq8Lh5WNvAgHzpjnEzxn4CJBwGkcau7b/tZ556qrWXQz4DJyChYg8JZAD06kczrdgFPpEQZfDsA==",
=======
"version": "5.25.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.25.0.tgz",
"integrity": "sha512-7fWqfxr0KNHj75PFqlGX24gWjdV/FDBABXL5dyvBOWHpACGyveok8Uj4ipPX/1fGU63fBkzSIycEje4XsOxUFA==",
>>>>>>> dev
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -4361,6 +4523,7 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
<<<<<<< HEAD
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.21.0.tgz",
"integrity": "sha512-Y8Y2T2FNvm08qlcoSMoNchh9y2Uj3QmjtwNMdRQkcFG7Muz//wfJBGBxh8R7HAGQFpgYpdHqUpEoPQk+q9Kjfg==",
@ -4372,6 +4535,19 @@
"globby": "^11.0.4",
"is-glob": "^4.0.3",
"semver": "^7.3.5",
=======
"version": "5.25.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.25.0.tgz",
"integrity": "sha512-MrPODKDych/oWs/71LCnuO7NyR681HuBly2uLnX3r5i4ME7q/yBqC4hW33kmxtuauLTM0OuBOhhkFaxCCOjEEw==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.25.0",
"@typescript-eslint/visitor-keys": "5.25.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"semver": "^7.3.7",
>>>>>>> dev
"tsutils": "^3.21.0"
},
"engines": {
@ -4387,6 +4563,23 @@
}
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
@ -4403,6 +4596,7 @@
}
},
"node_modules/@typescript-eslint/utils": {
<<<<<<< HEAD
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.21.0.tgz",
"integrity": "sha512-q/emogbND9wry7zxy7VYri+7ydawo2HDZhRZ5k6yggIvXa7PvBbAAZ4PFH/oZLem72ezC4Pr63rJvDK/sTlL8Q==",
@ -4412,6 +4606,17 @@
"@typescript-eslint/scope-manager": "5.21.0",
"@typescript-eslint/types": "5.21.0",
"@typescript-eslint/typescript-estree": "5.21.0",
=======
"version": "5.25.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.25.0.tgz",
"integrity": "sha512-qNC9bhnz/n9Kba3yI6HQgQdBLuxDoMgdjzdhSInZh6NaDnFpTUlwNGxplUFWfY260Ya0TRPvkg9dd57qxrJI9g==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.9",
"@typescript-eslint/scope-manager": "5.25.0",
"@typescript-eslint/types": "5.25.0",
"@typescript-eslint/typescript-estree": "5.25.0",
>>>>>>> dev
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0"
},
@ -4427,6 +4632,7 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
<<<<<<< HEAD
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.21.0.tgz",
"integrity": "sha512-SX8jNN+iHqAF0riZQMkm7e8+POXa/fXw5cxL+gjpyP+FI+JVNhii53EmQgDAfDcBpFekYSlO0fGytMQwRiMQCA==",
@ -4434,6 +4640,15 @@
"dependencies": {
"@typescript-eslint/types": "5.21.0",
"eslint-visitor-keys": "^3.0.0"
=======
"version": "5.25.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.25.0.tgz",
"integrity": "sha512-yd26vFgMsC4h2dgX4+LR+GeicSKIfUvZREFLf3DDjZPtqgLx5AJZr6TetMNwFP9hcKreTTeztQYBTNbNoOycwA==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.25.0",
"eslint-visitor-keys": "^3.3.0"
>>>>>>> dev
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -4650,9 +4865,15 @@
}
},
"node_modules/acorn": {
<<<<<<< HEAD
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
=======
"version": "8.7.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz",
"integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==",
>>>>>>> dev
"bin": {
"acorn": "bin/acorn"
},
@ -8244,12 +8465,21 @@
}
},
"node_modules/eslint": {
<<<<<<< HEAD
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.14.0.tgz",
"integrity": "sha512-3/CE4aJX7LNEiE3i6FeodHmI/38GZtWCsAtsymScmzYapx8q1nVVb+eLcLSzATmCPXw5pT4TqVs1E0OmxAd9tw==",
"dev": true,
"dependencies": {
"@eslint/eslintrc": "^1.2.2",
=======
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.15.0.tgz",
"integrity": "sha512-GG5USZ1jhCu8HJkzGgeK8/+RGnHaNYZGrGDzUtigK3BsGESW/rs2az23XqE0WVwDxy1VRvvjSSGu5nB0Bu+6SA==",
"dev": true,
"dependencies": {
"@eslint/eslintrc": "^1.2.3",
>>>>>>> dev
"@humanwhocodes/config-array": "^0.9.2",
"ajv": "^6.10.0",
"chalk": "^4.0.0",
@ -8260,7 +8490,11 @@
"eslint-scope": "^7.1.1",
"eslint-utils": "^3.0.0",
"eslint-visitor-keys": "^3.3.0",
<<<<<<< HEAD
"espree": "^9.3.1",
=======
"espree": "^9.3.2",
>>>>>>> dev
"esquery": "^1.4.0",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
@ -8276,7 +8510,7 @@
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1",
"lodash.merge": "^4.6.2",
"minimatch": "^3.0.4",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
"optionator": "^0.9.1",
"regexpp": "^3.2.0",
@ -8519,6 +8753,7 @@
}
},
"node_modules/espree": {
<<<<<<< HEAD
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz",
"integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==",
@ -8526,6 +8761,15 @@
"dependencies": {
"acorn": "^8.7.0",
"acorn-jsx": "^5.3.1",
=======
"version": "9.3.2",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz",
"integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==",
"dev": true,
"dependencies": {
"acorn": "^8.7.1",
"acorn-jsx": "^5.3.2",
>>>>>>> dev
"eslint-visitor-keys": "^3.3.0"
},
"engines": {
@ -15103,9 +15347,9 @@
"dev": true
},
"node_modules/minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
@ -23764,19 +24008,33 @@
"integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
},
"@eslint/eslintrc": {
<<<<<<< HEAD
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.2.tgz",
"integrity": "sha512-lTVWHs7O2hjBFZunXTZYnYqtB9GakA1lnxIf+gKq2nY5gxkkNi/lQvveW6t8gFdOHTg6nG50Xs95PrLqVpcaLg==",
=======
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.3.tgz",
"integrity": "sha512-uGo44hIwoLGNyduRpjdEpovcbMdd+Nv7amtmJxnKmI8xj6yd5LncmSwDa5NgX/41lIFJtkjD6YdVfgEzPfJ5UA==",
>>>>>>> dev
"dev": true,
"requires": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
<<<<<<< HEAD
"espree": "^9.3.1",
=======
"espree": "^9.3.2",
>>>>>>> dev
"globals": "^13.9.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.0",
<<<<<<< HEAD
"minimatch": "^3.0.4",
=======
"minimatch": "^3.1.2",
>>>>>>> dev
"strip-json-comments": "^3.1.1"
},
"dependencies": {
@ -23787,9 +24045,15 @@
"dev": true
},
"globals": {
<<<<<<< HEAD
"version": "13.13.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz",
"integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==",
=======
"version": "13.15.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz",
"integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==",
>>>>>>> dev
"dev": true,
"requires": {
"type-fest": "^0.20.2"
@ -25205,6 +25469,19 @@
"@types/istanbul-lib-report": "*"
}
},
<<<<<<< HEAD
=======
"@types/jest": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.1.tgz",
"integrity": "sha512-fUy7YRpT+rHXto1YlL+J9rs0uLGyiqVt3ZOTQR+4ROc47yNl8WLdVLgUloBRhOxP1PZvguHl44T3H0wAWxahYQ==",
"dev": true,
"requires": {
"jest-matcher-utils": "^27.0.0",
"pretty-format": "^27.0.0"
}
},
>>>>>>> dev
"@types/jquery": {
"version": "3.5.14",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.14.tgz",
@ -25373,6 +25650,7 @@
}
},
"@typescript-eslint/eslint-plugin": {
<<<<<<< HEAD
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.21.0.tgz",
"integrity": "sha512-fTU85q8v5ZLpoZEyn/u1S2qrFOhi33Edo2CZ0+q1gDaWWm0JuPh3bgOyU8lM0edIEYgKLDkPFiZX2MOupgjlyg==",
@ -25386,13 +25664,37 @@
"ignore": "^5.1.8",
"regexpp": "^3.2.0",
"semver": "^7.3.5",
=======
"version": "5.25.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.25.0.tgz",
"integrity": "sha512-icYrFnUzvm+LhW0QeJNKkezBu6tJs9p/53dpPLFH8zoM9w1tfaKzVurkPotEpAqQ8Vf8uaFyL5jHd0Vs6Z0ZQg==",
"dev": true,
"requires": {
"@typescript-eslint/scope-manager": "5.25.0",
"@typescript-eslint/type-utils": "5.25.0",
"@typescript-eslint/utils": "5.25.0",
"debug": "^4.3.4",
"functional-red-black-tree": "^1.0.1",
"ignore": "^5.2.0",
"regexpp": "^3.2.0",
"semver": "^7.3.7",
>>>>>>> dev
"tsutils": "^3.21.0"
},
"dependencies": {
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"requires": {
"ms": "2.1.2"
}
},
"semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
@ -25401,6 +25703,7 @@
}
},
"@typescript-eslint/parser": {
<<<<<<< HEAD
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.21.0.tgz",
"integrity": "sha512-8RUwTO77hstXUr3pZoWZbRQUxXcSXafZ8/5gpnQCfXvgmP9gpNlRGlWzvfbEQ14TLjmtU8eGnONkff8U2ui2Eg==",
@ -25451,9 +25754,92 @@
"globby": "^11.0.4",
"is-glob": "^4.0.3",
"semver": "^7.3.5",
=======
"version": "5.25.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.25.0.tgz",
"integrity": "sha512-r3hwrOWYbNKP1nTcIw/aZoH+8bBnh/Lh1iDHoFpyG4DnCpvEdctrSl6LOo19fZbzypjQMHdajolxs6VpYoChgA==",
"dev": true,
"requires": {
"@typescript-eslint/scope-manager": "5.25.0",
"@typescript-eslint/types": "5.25.0",
"@typescript-eslint/typescript-estree": "5.25.0",
"debug": "^4.3.4"
},
"dependencies": {
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"requires": {
"ms": "2.1.2"
}
}
}
},
"@typescript-eslint/scope-manager": {
"version": "5.25.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.25.0.tgz",
"integrity": "sha512-p4SKTFWj+2VpreUZ5xMQsBMDdQ9XdRvODKXN4EksyBjFp2YvQdLkyHqOffakYZPuWJUDNu3jVXtHALDyTv3cww==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.25.0",
"@typescript-eslint/visitor-keys": "5.25.0"
}
},
"@typescript-eslint/type-utils": {
"version": "5.25.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.25.0.tgz",
"integrity": "sha512-B6nb3GK3Gv1Rsb2pqalebe/RyQoyG/WDy9yhj8EE0Ikds4Xa8RR28nHz+wlt4tMZk5bnAr0f3oC8TuDAd5CPrw==",
"dev": true,
"requires": {
"@typescript-eslint/utils": "5.25.0",
"debug": "^4.3.4",
"tsutils": "^3.21.0"
},
"dependencies": {
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"requires": {
"ms": "2.1.2"
}
}
}
},
"@typescript-eslint/types": {
"version": "5.25.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.25.0.tgz",
"integrity": "sha512-7fWqfxr0KNHj75PFqlGX24gWjdV/FDBABXL5dyvBOWHpACGyveok8Uj4ipPX/1fGU63fBkzSIycEje4XsOxUFA==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "5.25.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.25.0.tgz",
"integrity": "sha512-MrPODKDych/oWs/71LCnuO7NyR681HuBly2uLnX3r5i4ME7q/yBqC4hW33kmxtuauLTM0OuBOhhkFaxCCOjEEw==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.25.0",
"@typescript-eslint/visitor-keys": "5.25.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"semver": "^7.3.7",
>>>>>>> dev
"tsutils": "^3.21.0"
},
"dependencies": {
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"requires": {
"ms": "2.1.2"
}
},
"semver": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
@ -25466,6 +25852,7 @@
}
},
"@typescript-eslint/utils": {
<<<<<<< HEAD
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.21.0.tgz",
"integrity": "sha512-q/emogbND9wry7zxy7VYri+7ydawo2HDZhRZ5k6yggIvXa7PvBbAAZ4PFH/oZLem72ezC4Pr63rJvDK/sTlL8Q==",
@ -25475,11 +25862,23 @@
"@typescript-eslint/scope-manager": "5.21.0",
"@typescript-eslint/types": "5.21.0",
"@typescript-eslint/typescript-estree": "5.21.0",
=======
"version": "5.25.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.25.0.tgz",
"integrity": "sha512-qNC9bhnz/n9Kba3yI6HQgQdBLuxDoMgdjzdhSInZh6NaDnFpTUlwNGxplUFWfY260Ya0TRPvkg9dd57qxrJI9g==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.9",
"@typescript-eslint/scope-manager": "5.25.0",
"@typescript-eslint/types": "5.25.0",
"@typescript-eslint/typescript-estree": "5.25.0",
>>>>>>> dev
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0"
}
},
"@typescript-eslint/visitor-keys": {
<<<<<<< HEAD
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.21.0.tgz",
"integrity": "sha512-SX8jNN+iHqAF0riZQMkm7e8+POXa/fXw5cxL+gjpyP+FI+JVNhii53EmQgDAfDcBpFekYSlO0fGytMQwRiMQCA==",
@ -25487,6 +25886,15 @@
"requires": {
"@typescript-eslint/types": "5.21.0",
"eslint-visitor-keys": "^3.0.0"
=======
"version": "5.25.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.25.0.tgz",
"integrity": "sha512-yd26vFgMsC4h2dgX4+LR+GeicSKIfUvZREFLf3DDjZPtqgLx5AJZr6TetMNwFP9hcKreTTeztQYBTNbNoOycwA==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.25.0",
"eslint-visitor-keys": "^3.3.0"
>>>>>>> dev
}
},
"@webassemblyjs/ast": {
@ -25693,9 +26101,15 @@
}
},
"acorn": {
<<<<<<< HEAD
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ=="
=======
"version": "8.7.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz",
"integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A=="
>>>>>>> dev
},
"acorn-globals": {
"version": "4.3.4",
@ -28606,12 +29020,21 @@
}
},
"eslint": {
<<<<<<< HEAD
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.14.0.tgz",
"integrity": "sha512-3/CE4aJX7LNEiE3i6FeodHmI/38GZtWCsAtsymScmzYapx8q1nVVb+eLcLSzATmCPXw5pT4TqVs1E0OmxAd9tw==",
"dev": true,
"requires": {
"@eslint/eslintrc": "^1.2.2",
=======
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.15.0.tgz",
"integrity": "sha512-GG5USZ1jhCu8HJkzGgeK8/+RGnHaNYZGrGDzUtigK3BsGESW/rs2az23XqE0WVwDxy1VRvvjSSGu5nB0Bu+6SA==",
"dev": true,
"requires": {
"@eslint/eslintrc": "^1.2.3",
>>>>>>> dev
"@humanwhocodes/config-array": "^0.9.2",
"ajv": "^6.10.0",
"chalk": "^4.0.0",
@ -28622,7 +29045,11 @@
"eslint-scope": "^7.1.1",
"eslint-utils": "^3.0.0",
"eslint-visitor-keys": "^3.3.0",
<<<<<<< HEAD
"espree": "^9.3.1",
=======
"espree": "^9.3.2",
>>>>>>> dev
"esquery": "^1.4.0",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
@ -28638,7 +29065,7 @@
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1",
"lodash.merge": "^4.6.2",
"minimatch": "^3.0.4",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
"optionator": "^0.9.1",
"regexpp": "^3.2.0",
@ -28807,6 +29234,7 @@
"integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA=="
},
"espree": {
<<<<<<< HEAD
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz",
"integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==",
@ -28814,6 +29242,15 @@
"requires": {
"acorn": "^8.7.0",
"acorn-jsx": "^5.3.1",
=======
"version": "9.3.2",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz",
"integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==",
"dev": true,
"requires": {
"acorn": "^8.7.1",
"acorn-jsx": "^5.3.2",
>>>>>>> dev
"eslint-visitor-keys": "^3.3.0"
}
},
@ -33925,9 +34362,9 @@
"dev": true
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"

@ -1,7 +1,7 @@
{
"name": "bitburner",
"license": "SEE LICENSE IN license.txt",
"version": "1.6.4",
"version": "1.7.0",
"main": "electron-main.js",
"author": {
"name": "Daniel Xie & Olivier Gagnon"
@ -57,6 +57,7 @@
"@types/bcryptjs": "^2.4.2",
"@types/escodegen": "^0.0.7",
"@types/file-saver": "^2.0.3",
"@types/jest": "^27.4.1",
"@types/jquery": "^3.5.14",
"@types/lodash": "^4.14.168",
"@types/numeral": "^2.0.2",

@ -631,6 +631,7 @@ export class Augmentation {
augmentationReference.baseCost *
getGenericAugmentationPriceMultiplier() *
BitNodeMultipliers.AugmentationMoneyCost;
repCost = augmentationReference.baseRepRequirement * BitNodeMultipliers.AugmentationRepCost;
}
return { moneyCost, repCost };
}

@ -45,13 +45,13 @@ function BitNodeModifiedStats(props: IBitNodeModifiedStatsProps): React.ReactEle
);
}
type MultiplierListItemData = [
multiplier: string,
currentValue: number,
augmentedValue: number,
bitNodeMultiplier: number,
color: string,
];
interface MultiplierListItemData {
mult: string;
current: number;
augmented: number;
bnMult?: number;
color?: string;
}
interface IMultiplierListProps {
rows: MultiplierListItemData[];
@ -60,23 +60,23 @@ interface IMultiplierListProps {
function MultiplierList(props: IMultiplierListProps): React.ReactElement {
const listItems = props.rows
.map((data) => {
const [multiplier, currentValue, augmentedValue, bitNodeMultiplier, color] = data;
const { mult, current, augmented, bnMult = 1, color = Settings.theme.primary } = data;
if (!isNaN(augmentedValue)) {
if (!isNaN(augmented)) {
return (
<ListItem key={multiplier} disableGutters sx={{ py: 0 }}>
<ListItem key={mult} disableGutters sx={{ py: 0 }}>
<ListItemText
sx={{ my: 0.1 }}
primary={
<Typography color={color}>
<b>{multiplier}</b>
<b>{mult}</b>
</Typography>
}
secondary={
<span style={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
<BitNodeModifiedStats base={currentValue} mult={bitNodeMultiplier} color={color} />
<BitNodeModifiedStats base={current} mult={bnMult} color={color} />
<DoubleArrow fontSize="small" color="success" sx={{ mb: 0.5, mx: 1 }} />
<BitNodeModifiedStats base={augmentedValue} mult={bitNodeMultiplier} color={Settings.theme.success} />
<BitNodeModifiedStats base={augmented} mult={bnMult} color={Settings.theme.success} />
</span>
}
disableTypography
@ -94,177 +94,205 @@ function MultiplierList(props: IMultiplierListProps): React.ReactElement {
export function PlayerMultipliers(): React.ReactElement {
const mults = calculateAugmentedStats();
// Column data is a bit janky, so it's set up here to allow for
// easier logic in setting up the layout
const leftColData: MultiplierListItemData[] = [
...[
["Hacking Chance ", Player.hacking_chance_mult, Player.hacking_chance_mult * mults.hacking_chance_mult, 1],
["Hacking Speed ", Player.hacking_speed_mult, Player.hacking_speed_mult * mults.hacking_speed_mult, 1],
["Hacking Money ", Player.hacking_money_mult, Player.hacking_money_mult * mults.hacking_money_mult, 1],
["Hacking Growth ", Player.hacking_grow_mult, Player.hacking_grow_mult * mults.hacking_grow_mult, 1],
[
"Hacking Level ",
Player.hacking_mult,
Player.hacking_mult * mults.hacking_mult,
BitNodeMultipliers.HackingLevelMultiplier,
],
[
"Hacking Experience ",
Player.hacking_exp_mult,
Player.hacking_exp_mult * mults.hacking_exp_mult,
BitNodeMultipliers.HackExpGain,
],
].map((data): MultiplierListItemData => (data as any).concat([Settings.theme.hack])),
{
mult: "Hacking Chance",
current: Player.hacking_chance_mult,
augmented: Player.hacking_chance_mult * mults.hacking_chance_mult,
},
{
mult: "Hacking Speed",
current: Player.hacking_speed_mult,
augmented: Player.hacking_speed_mult * mults.hacking_speed_mult,
},
{
mult: "Hacking Money",
current: Player.hacking_money_mult,
augmented: Player.hacking_money_mult * mults.hacking_money_mult,
bnMult: BitNodeMultipliers.ScriptHackMoney,
},
{
mult: "Hacking Growth",
current: Player.hacking_grow_mult,
augmented: Player.hacking_grow_mult * mults.hacking_grow_mult,
},
{
mult: "Hacking Level",
current: Player.hacking_mult,
augmented: Player.hacking_mult * mults.hacking_mult,
bnMult: BitNodeMultipliers.HackingLevelMultiplier,
},
{
mult: "Hacking Experience",
current: Player.hacking_exp_mult,
augmented: Player.hacking_exp_mult * mults.hacking_exp_mult,
bnMult: BitNodeMultipliers.HackExpGain,
},
].map((data: MultiplierListItemData) =>
Object.defineProperty(data, "color", {
value: Settings.theme.hack,
}),
),
...[
[
"Strength Level ",
Player.strength_mult,
Player.strength_mult * mults.strength_mult,
BitNodeMultipliers.StrengthLevelMultiplier,
],
["Strength Experience ", Player.strength_exp_mult, Player.strength_exp_mult * mults.strength_exp_mult, 1],
[
"Defense Level ",
Player.defense_mult,
Player.defense_mult * mults.defense_mult,
BitNodeMultipliers.DefenseLevelMultiplier,
],
["Defense Experience ", Player.defense_exp_mult, Player.defense_exp_mult * mults.defense_exp_mult, 1],
[
"Dexterity Level ",
Player.dexterity_mult,
Player.dexterity_mult * mults.dexterity_mult,
BitNodeMultipliers.DexterityLevelMultiplier,
],
["Dexterity Experience ", Player.dexterity_exp_mult, Player.dexterity_exp_mult * mults.dexterity_exp_mult, 1],
[
"Agility Level ",
Player.agility_mult,
Player.agility_mult * mults.agility_mult,
BitNodeMultipliers.AgilityLevelMultiplier,
],
["Agility Experience ", Player.agility_exp_mult, Player.agility_exp_mult * mults.agility_exp_mult, 1],
].map((data): MultiplierListItemData => (data as any).concat([Settings.theme.combat])),
[
"Charisma Level ",
Player.charisma_mult,
Player.charisma_mult * mults.charisma_mult,
BitNodeMultipliers.CharismaLevelMultiplier,
Settings.theme.cha,
],
[
"Charisma Experience ",
Player.charisma_exp_mult,
Player.charisma_exp_mult * mults.charisma_exp_mult,
1,
Settings.theme.cha,
],
{
mult: "Strength Level",
current: Player.strength_mult,
augmented: Player.strength_mult * mults.strength_mult,
bnMult: BitNodeMultipliers.StrengthLevelMultiplier,
},
{
mult: "Strength Experience",
current: Player.strength_exp_mult,
augmented: Player.strength_exp_mult * mults.strength_exp_mult,
},
{
mult: "Defense Level",
current: Player.defense_mult,
augmented: Player.defense_mult * mults.defense_mult,
bnMult: BitNodeMultipliers.DefenseLevelMultiplier,
},
{
mult: "Defense Experience",
current: Player.defense_exp_mult,
augmented: Player.defense_exp_mult * mults.defense_exp_mult,
},
{
mult: "Dexterity Level",
current: Player.dexterity_mult,
augmented: Player.dexterity_mult * mults.dexterity_mult,
bnMult: BitNodeMultipliers.DexterityLevelMultiplier,
},
{
mult: "Dexterity Experience",
current: Player.dexterity_exp_mult,
augmented: Player.dexterity_exp_mult * mults.dexterity_exp_mult,
},
{
mult: "Agility Level",
current: Player.agility_mult,
augmented: Player.agility_mult * mults.agility_mult,
bnMult: BitNodeMultipliers.AgilityLevelMultiplier,
},
{
mult: "Agility Experience",
current: Player.agility_exp_mult,
augmented: Player.agility_exp_mult * mults.agility_exp_mult,
},
].map((data: MultiplierListItemData) =>
Object.defineProperty(data, "color", {
value: Settings.theme.combat,
}),
),
{
mult: "Charisma Level",
current: Player.charisma_mult,
augmented: Player.charisma_mult * mults.charisma_mult,
bnMult: BitNodeMultipliers.CharismaLevelMultiplier,
color: Settings.theme.cha,
},
{
mult: "Charisma Experience",
current: Player.charisma_exp_mult,
augmented: Player.charisma_exp_mult * mults.charisma_exp_mult,
color: Settings.theme.cha,
},
];
const rightColData: MultiplierListItemData[] = [
...[
[
"Hacknet Node production ",
Player.hacknet_node_money_mult,
Player.hacknet_node_money_mult * mults.hacknet_node_money_mult,
BitNodeMultipliers.HacknetNodeMoney,
],
[
"Hacknet Node purchase cost ",
Player.hacknet_node_purchase_cost_mult,
Player.hacknet_node_purchase_cost_mult * mults.hacknet_node_purchase_cost_mult,
1,
],
[
"Hacknet Node RAM upgrade cost ",
Player.hacknet_node_ram_cost_mult,
Player.hacknet_node_ram_cost_mult * mults.hacknet_node_ram_cost_mult,
1,
],
[
"Hacknet Node Core purchase cost ",
Player.hacknet_node_core_cost_mult,
Player.hacknet_node_core_cost_mult * mults.hacknet_node_core_cost_mult,
1,
],
[
"Hacknet Node level upgrade cost ",
Player.hacknet_node_level_cost_mult,
Player.hacknet_node_level_cost_mult * mults.hacknet_node_level_cost_mult,
1,
],
["Company reputation gain ", Player.company_rep_mult, Player.company_rep_mult * mults.company_rep_mult, 1],
[
"Faction reputation gain ",
Player.faction_rep_mult,
Player.faction_rep_mult * mults.faction_rep_mult,
BitNodeMultipliers.FactionWorkRepGain,
],
].map((data): MultiplierListItemData => (data as any).concat([Settings.theme.primary])),
[
"Salary ",
Player.work_money_mult,
Player.work_money_mult * mults.work_money_mult,
BitNodeMultipliers.CompanyWorkMoney,
Settings.theme.money,
],
[
"Crime success ",
Player.crime_success_mult,
Player.crime_success_mult * mults.crime_success_mult,
1,
Settings.theme.combat,
],
[
"Crime money ",
Player.crime_money_mult,
Player.crime_money_mult * mults.crime_money_mult,
BitNodeMultipliers.CrimeMoney,
Settings.theme.money,
],
{
mult: "Hacknet Node Production",
current: Player.hacknet_node_money_mult,
augmented: Player.hacknet_node_money_mult * mults.hacknet_node_money_mult,
bnMult: BitNodeMultipliers.HacknetNodeMoney,
},
{
mult: "Hacknet Node Purchase Cost",
current: Player.hacknet_node_purchase_cost_mult,
augmented: Player.hacknet_node_purchase_cost_mult * mults.hacknet_node_purchase_cost_mult,
},
{
mult: "Hacknet Node RAM Upgrade Cost",
current: Player.hacknet_node_ram_cost_mult,
augmented: Player.hacknet_node_ram_cost_mult * mults.hacknet_node_ram_cost_mult,
},
{
mult: "Hacknet Node Core Purchase Cost",
current: Player.hacknet_node_core_cost_mult,
augmented: Player.hacknet_node_core_cost_mult * mults.hacknet_node_core_cost_mult,
},
{
mult: "Hacknet Node Level Upgrade Cost",
current: Player.hacknet_node_level_cost_mult,
augmented: Player.hacknet_node_level_cost_mult * mults.hacknet_node_level_cost_mult,
},
{
mult: "Company Reputation Gain",
current: Player.company_rep_mult,
augmented: Player.company_rep_mult * mults.company_rep_mult,
},
{
mult: "Faction Reputation Gain",
current: Player.faction_rep_mult,
augmented: Player.faction_rep_mult * mults.faction_rep_mult,
bnMult: BitNodeMultipliers.FactionWorkRepGain,
},
{
mult: "Salary",
current: Player.work_money_mult,
augmented: Player.work_money_mult * mults.work_money_mult,
bnMult: BitNodeMultipliers.CompanyWorkMoney,
color: Settings.theme.money,
},
{
mult: "Crime Success Chance",
current: Player.crime_success_mult,
augmented: Player.crime_success_mult * mults.crime_success_mult,
color: Settings.theme.combat,
},
{
mult: "Crime Money",
current: Player.crime_money_mult,
augmented: Player.crime_money_mult * mults.crime_money_mult,
bnMult: BitNodeMultipliers.CrimeMoney,
color: Settings.theme.money,
},
];
if (Player.canAccessBladeburner()) {
rightColData.push(
...[
[
"Bladeburner Success Chance",
Player.bladeburner_success_chance_mult,
Player.bladeburner_success_chance_mult * mults.bladeburner_success_chance_mult,
1,
],
[
"Bladeburner Max Stamina",
Player.bladeburner_max_stamina_mult,
Player.bladeburner_max_stamina_mult * mults.bladeburner_max_stamina_mult,
1,
],
[
"Bladeburner Stamina Gain",
Player.bladeburner_stamina_gain_mult,
Player.bladeburner_stamina_gain_mult * mults.bladeburner_stamina_gain_mult,
1,
],
[
"Bladeburner Field Analysis",
Player.bladeburner_analysis_mult,
Player.bladeburner_analysis_mult * mults.bladeburner_analysis_mult,
1,
],
].map((data): MultiplierListItemData => (data as any).concat([Settings.theme.primary])),
{
mult: "Bladeburner Success Chance",
current: Player.bladeburner_success_chance_mult,
augmented: Player.bladeburner_success_chance_mult * mults.bladeburner_success_chance_mult,
},
{
mult: "Bladeburner Max Stamina",
current: Player.bladeburner_max_stamina_mult,
augmented: Player.bladeburner_max_stamina_mult * mults.bladeburner_max_stamina_mult,
},
{
mult: "Bladeburner Stamina Gain",
current: Player.bladeburner_stamina_gain_mult,
augmented: Player.bladeburner_stamina_gain_mult * mults.bladeburner_stamina_gain_mult,
},
{
mult: "Bladeburner Field Analysis",
current: Player.bladeburner_analysis_mult,
augmented: Player.bladeburner_analysis_mult * mults.bladeburner_analysis_mult,
},
);
}
const hasLeftImprovements = +!!(leftColData.filter((item) => item[2] !== 0).length > 0),
hasRightImprovements = +!!(rightColData.filter((item) => item[2] !== 0).length > 0);
return (
<Paper
sx={{
p: 1,
maxHeight: 400,
overflowY: "scroll",
display: "grid",
gridTemplateColumns: `repeat(${hasLeftImprovements + hasRightImprovements}, 1fr)`,
display: "flex",
flexDirection: "column",
flexWrap: "wrap",
gap: 1,
}}
>
<MultiplierList rows={leftColData} />

@ -83,7 +83,7 @@ const Exclusive = (props: IExclusiveProps): React.ReactElement => {
<li>
<b>{props.aug.factions[0]}</b> faction
</li>
{props.player.canAccessGang() && !props.aug.isSpecial && (
{props.player.isAwareOfGang() && !props.aug.isSpecial && (
<li>
Certain <b>gangs</b>
</li>

@ -488,7 +488,7 @@ export const defaultMultipliers: IBitNodeMultipliers = {
FourSigmaMarketDataApiCost: 1,
CorporationValuation: 1,
CorporationSoftCap: 1,
CorporationSoftcap: 1,
BladeburnerRank: 1,
BladeburnerSkillCost: 1,
@ -504,6 +504,8 @@ export const defaultMultipliers: IBitNodeMultipliers = {
WorldDaemonDifficulty: 1,
};
Object.freeze(defaultMultipliers);
export function getBitNodeMultipliers(n: number, lvl: number): IBitNodeMultipliers {
const mults = Object.assign({}, defaultMultipliers);
switch (n) {
@ -523,7 +525,7 @@ export function getBitNodeMultipliers(n: number, lvl: number): IBitNodeMultiplie
StaneksGiftPowerMultiplier: 2,
StaneksGiftExtraSize: -6,
PurchasedServerSoftcap: 1.3,
CorporationSoftCap: 0.9,
CorporationSoftcap: 0.9,
WorldDaemonDifficulty: 5,
});
}
@ -609,7 +611,7 @@ export function getBitNodeMultipliers(n: number, lvl: number): IBitNodeMultiplie
StaneksGiftPowerMultiplier: 0.5,
StaneksGiftExtraSize: 2,
GangSoftcap: 0.7,
CorporationSoftCap: 0.9,
CorporationSoftcap: 0.9,
WorldDaemonDifficulty: 2,
GangUniqueAugs: 0.2,
});
@ -637,7 +639,7 @@ export function getBitNodeMultipliers(n: number, lvl: number): IBitNodeMultiplie
StaneksGiftPowerMultiplier: 0.9,
StaneksGiftExtraSize: -1,
GangSoftcap: 0.7,
CorporationSoftCap: 0.9,
CorporationSoftcap: 0.9,
WorldDaemonDifficulty: 2,
GangUniqueAugs: 0.2,
});
@ -657,7 +659,7 @@ export function getBitNodeMultipliers(n: number, lvl: number): IBitNodeMultiplie
StaneksGiftExtraSize: -99,
PurchasedServerSoftcap: 4,
GangSoftcap: 0,
CorporationSoftCap: 0,
CorporationSoftcap: 0,
GangUniqueAugs: 0,
});
}
@ -685,7 +687,7 @@ export function getBitNodeMultipliers(n: number, lvl: number): IBitNodeMultiplie
StaneksGiftPowerMultiplier: 0.5,
StaneksGiftExtraSize: 2,
GangSoftcap: 0.8,
CorporationSoftCap: 0.7,
CorporationSoftcap: 0.7,
WorldDaemonDifficulty: 2,
GangUniqueAugs: 0.25,
});
@ -717,7 +719,7 @@ export function getBitNodeMultipliers(n: number, lvl: number): IBitNodeMultiplie
StaneksGiftExtraSize: -3,
PurchasedServerSoftcap: 1.1,
GangSoftcap: 0.9,
CorporationSoftCap: 0.9,
CorporationSoftcap: 0.9,
WorldDaemonDifficulty: 2,
GangUniqueAugs: 0.25,
});
@ -741,7 +743,7 @@ export function getBitNodeMultipliers(n: number, lvl: number): IBitNodeMultiplie
FourSigmaMarketDataCost: 4,
FourSigmaMarketDataApiCost: 4,
PurchasedServerSoftcap: 2,
CorporationSoftCap: 0.9,
CorporationSoftcap: 0.9,
WorldDaemonDifficulty: 1.5,
GangUniqueAugs: 0.75,
});
@ -809,7 +811,7 @@ export function getBitNodeMultipliers(n: number, lvl: number): IBitNodeMultiplie
StaneksGiftPowerMultiplier: inc,
StaneksGiftExtraSize: inc,
GangSoftcap: 0.8,
CorporationSoftCap: 0.8,
CorporationSoftcap: 0.8,
WorldDaemonDifficulty: inc,
GangUniqueAugs: dec,
@ -854,7 +856,7 @@ export function getBitNodeMultipliers(n: number, lvl: number): IBitNodeMultiplie
StaneksGiftPowerMultiplier: 2,
StaneksGiftExtraSize: 1,
GangSoftcap: 0.3,
CorporationSoftCap: 0.3,
CorporationSoftcap: 0.3,
WorldDaemonDifficulty: 3,
GangUniqueAugs: 0.1,
});

@ -242,7 +242,7 @@ export interface IBitNodeMultipliers {
/**
* Influences corporation dividends.
*/
CorporationSoftCap: number;
CorporationSoftcap: number;
// Index signature
[key: string]: number;
@ -252,4 +252,4 @@ export interface IBitNodeMultipliers {
* The multipliers that are influenced by current Bitnode progression.
*/
// tslint:disable-next-line:variable-name
export const BitNodeMultipliers = defaultMultipliers;
export const BitNodeMultipliers = Object.assign({}, defaultMultipliers);

@ -1,39 +1,55 @@
import ExpandMore from "@mui/icons-material/ExpandMore";
import ExpandLess from "@mui/icons-material/ExpandLess";
import { Box, Collapse, ListItemButton, ListItemText, Paper, Typography } from "@mui/material";
import ExpandMore from "@mui/icons-material/ExpandMore";
import { Box, Collapse, ListItemButton, ListItemText, Paper, Table, TableBody, Typography } from "@mui/material";
import { uniqueId } from "lodash";
import React from "react";
import { SpecialServers } from "../../Server/data/SpecialServers";
import { Settings } from "../../Settings/Settings";
import { use } from "../../ui/Context";
import { StatsRow } from "../../ui/React/StatsRow";
import { defaultMultipliers, getBitNodeMultipliers } from "../BitNode";
import { IBitNodeMultipliers } from "../BitNodeMultipliers";
import { SpecialServers } from "../../Server/data/SpecialServers";
interface IProps {
n: number;
level?: number;
}
export function BitnodeMultiplierDescription({ n }: IProps): React.ReactElement {
const player = use.Player();
export function BitnodeMultiplierDescription({ n, level }: IProps): React.ReactElement {
const [open, setOpen] = React.useState(false);
const mults = getBitNodeMultipliers(n, player.sourceFileLvl(n));
if (n === 1) return <></>;
return (
<>
<br />
<Box component={Paper}>
<ListItemButton onClick={() => setOpen((old) => !old)}>
<ListItemText primary={<Typography>Bitnode multipliers:</Typography>} />
<Box component={Paper} sx={{ mt: 1, p: 1 }}>
<ListItemButton disableGutters onClick={() => setOpen((old) => !old)}>
<ListItemText primary={<Typography variant="h6">Bitnode Multipliers</Typography>} />
{open ? <ExpandLess color="primary" /> : <ExpandMore color="primary" />}
</ListItemButton>
<Box mx={2}>
<Collapse in={open}>
<BitNodeMultipliersDisplay n={n} level={level} />
</Collapse>
</Box>
);
}
export const BitNodeMultipliersDisplay = ({ n, level }: IProps): React.ReactElement => {
const player = use.Player();
// If a level argument has been provided, use that as the multiplier level
// If not, then we have to assume that we want the next level up from the
// current node's source file, so we get the min of that, the SF's max level,
// or if it's BN12, ∞
const maxSfLevel = n === 12 ? Infinity : 3;
const mults = getBitNodeMultipliers(n, level ?? Math.min(player.sourceFileLvl(n) + 1, maxSfLevel));
return (
<Box sx={{ columnCount: 2, columnGap: 1, mb: -2 }}>
<GeneralMults n={n} mults={mults} />
<SkillMults n={n} mults={mults} />
<FactionMults n={n} mults={mults} />
<AugmentationMults n={n} mults={mults} />
<StockMults n={n} mults={mults} />
<SkillMults n={n} mults={mults} />
<HackingMults n={n} mults={mults} />
<PurchasedServersMults n={n} mults={mults} />
<StockMults n={n} mults={mults} />
<CrimeMults n={n} mults={mults} />
<InfiltrationMults n={n} mults={mults} />
<CompanyMults n={n} mults={mults} />
@ -41,514 +57,279 @@ export function BitnodeMultiplierDescription({ n }: IProps): React.ReactElement
<CorporationMults n={n} mults={mults} />
<BladeburnerMults n={n} mults={mults} />
<StanekMults n={n} mults={mults} />
<br />
</Collapse>
</Box>
</Box>
</>
);
};
interface IBNMultRows {
[mult: string]: {
name: string;
content?: string;
color?: string;
};
}
interface IBNMultTableProps {
sectionName: string;
rowData: IBNMultRows;
mults: IBitNodeMultipliers;
}
const BNMultTable = (props: IBNMultTableProps): React.ReactElement => {
const rowsArray = Object.entries(props.rowData)
.filter(([key, _value]) => props.mults[key] !== defaultMultipliers[key])
.map(([key, value]) => (
<StatsRow
key={uniqueId()}
name={value.name}
data={{ content: value.content ?? `${(props.mults[key] * 100).toFixed(3)}%` }}
color={value.color ?? Settings.theme.primary}
/>
));
return rowsArray.length > 0 ? (
<span style={{ display: "inline-block", width: "100%", marginBottom: "16px" }}>
<Typography variant="h6">{props.sectionName}</Typography>
<Table>
<TableBody>{rowsArray}</TableBody>
</Table>
</span>
) : (
<></>
);
};
interface IMultsProps {
n: number;
mults: IBitNodeMultipliers;
}
function GeneralMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.ClassGymExpGain === defaultMultipliers.ClassGymExpGain &&
mults.CodingContractMoney === defaultMultipliers.CodingContractMoney &&
mults.DaedalusAugsRequirement === defaultMultipliers.DaedalusAugsRequirement &&
mults.WorldDaemonDifficulty === defaultMultipliers.WorldDaemonDifficulty &&
mults.HacknetNodeMoney === defaultMultipliers.HacknetNodeMoney
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>General:</Typography>
<Box mx={1}>
{mults.WorldDaemonDifficulty !== defaultMultipliers.WorldDaemonDifficulty ? (
<Typography>
{SpecialServers.WorldDaemon} difficulty: x{mults.WorldDaemonDifficulty.toFixed(3)}
</Typography>
) : (
<></>
)}
{mults.DaedalusAugsRequirement !== defaultMultipliers.DaedalusAugsRequirement ? (
<Typography>Daedalus aug req.: {mults.DaedalusAugsRequirement}</Typography>
) : (
<></>
)}
{mults.HacknetNodeMoney !== defaultMultipliers.HacknetNodeMoney ? (
<Typography>Hacknet production: x{mults.HacknetNodeMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.CodingContractMoney !== defaultMultipliers.CodingContractMoney ? (
<Typography>Coding contract reward: x{mults.CodingContractMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ClassGymExpGain !== defaultMultipliers.ClassGymExpGain ? (
<Typography>Class/Gym exp: x{mults.ClassGymExpGain.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
WorldDaemonDifficulty: { name: `${SpecialServers.WorldDaemon} Difficulty` },
DaedalusAugsRequirement: {
name: "Daedalus Augs Requirement",
content: String(mults.DaedalusAugsRequirement),
},
HacknetNodeMoney: { name: "Hacknet Production" },
CodingContractMoney: { name: "Coding Contract Reward" },
ClassGymExpGain: { name: "Class/Gym Exp" },
};
return <BNMultTable sectionName="General" rowData={rows} mults={mults} />;
}
function AugmentationMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.AugmentationMoneyCost === defaultMultipliers.AugmentationMoneyCost &&
mults.AugmentationRepCost === defaultMultipliers.AugmentationRepCost
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Augmentations:</Typography>
<Box mx={1}>
{mults.AugmentationMoneyCost !== defaultMultipliers.AugmentationMoneyCost ? (
<Typography>Cost: x{mults.AugmentationMoneyCost.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.AugmentationRepCost !== defaultMultipliers.AugmentationRepCost ? (
<Typography>Reputation: x{mults.AugmentationRepCost.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
AugmentationMoneyCost: { name: "Money Cost" },
AugmentationRepCost: {
name: "Reputation Cost",
color: Settings.theme.rep,
},
};
return <BNMultTable sectionName="Augmentations" rowData={rows} mults={mults} />;
}
function CompanyMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.CompanyWorkExpGain === defaultMultipliers.CompanyWorkExpGain &&
mults.CompanyWorkMoney === defaultMultipliers.CompanyWorkMoney
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Company:</Typography>
<Box mx={1}>
{mults.CompanyWorkMoney !== defaultMultipliers.CompanyWorkMoney ? (
<Typography>Money: x{mults.CompanyWorkMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.CompanyWorkExpGain !== defaultMultipliers.CompanyWorkExpGain ? (
<Typography>Exp: x{mults.CompanyWorkExpGain.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
CompanyWorkMoney: {
name: "Work Money",
color: Settings.theme.money,
},
CompanyWorkExpGain: { name: "Work Exp" },
};
return <BNMultTable sectionName="Company" rowData={rows} mults={mults} />;
}
function StockMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.FourSigmaMarketDataApiCost === defaultMultipliers.FourSigmaMarketDataApiCost &&
mults.FourSigmaMarketDataCost === defaultMultipliers.FourSigmaMarketDataCost
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Stock market:</Typography>
<Box mx={1}>
{mults.FourSigmaMarketDataCost !== defaultMultipliers.FourSigmaMarketDataCost ? (
<Typography>Market data cost: x{mults.FourSigmaMarketDataCost.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.FourSigmaMarketDataApiCost !== defaultMultipliers.FourSigmaMarketDataApiCost ? (
<Typography>Market data API cost: x{mults.FourSigmaMarketDataApiCost.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
FourSigmaMarketDataCost: { name: "Market Data Cost" },
FourSigmaMarketDataApiCost: { name: "Market Data API Cost" },
};
return <BNMultTable sectionName="Stock Market" rowData={rows} mults={mults} />;
}
function FactionMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.FactionPassiveRepGain === defaultMultipliers.FactionPassiveRepGain &&
mults.FactionWorkExpGain === defaultMultipliers.FactionWorkExpGain &&
mults.FactionWorkRepGain === defaultMultipliers.FactionWorkRepGain &&
mults.RepToDonateToFaction === defaultMultipliers.RepToDonateToFaction
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Faction:</Typography>
<Box mx={1}>
{mults.RepToDonateToFaction !== defaultMultipliers.RepToDonateToFaction ? (
<Typography>Favor to donate: x{mults.RepToDonateToFaction.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.FactionWorkRepGain !== defaultMultipliers.FactionWorkRepGain ? (
<Typography>Work rep: x{mults.FactionWorkRepGain.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.FactionWorkExpGain !== defaultMultipliers.FactionWorkExpGain ? (
<Typography>Work exp: x{mults.FactionWorkExpGain.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.FactionPassiveRepGain !== defaultMultipliers.FactionPassiveRepGain ? (
<Typography>Passive rep: x{mults.FactionPassiveRepGain.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
RepToDonateToFaction: { name: "Favor to Donate" },
FactionWorkRepGain: {
name: "Work Reputation",
color: Settings.theme.rep,
},
FactionWorkExpGain: { name: "Work Exp" },
FactionPassiveRepGain: {
name: "Passive Rep",
color: Settings.theme.rep,
},
};
return <BNMultTable sectionName="Faction" rowData={rows} mults={mults} />;
}
function CrimeMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (mults.CrimeExpGain === defaultMultipliers.CrimeExpGain && mults.CrimeMoney === defaultMultipliers.CrimeMoney)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Crime:</Typography>
<Box mx={1}>
{mults.CrimeExpGain !== defaultMultipliers.CrimeExpGain ? (
<Typography>Exp: x{mults.CrimeExpGain.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.CrimeMoney !== defaultMultipliers.CrimeMoney ? (
<Typography>Money: x{mults.CrimeMoney.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
CrimeExpGain: {
name: "Crime Exp",
color: Settings.theme.combat,
},
CrimeMoney: {
name: "Crime Money",
color: Settings.theme.combat,
},
};
return <BNMultTable sectionName="Crime" rowData={rows} mults={mults} />;
}
function SkillMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.HackingLevelMultiplier === defaultMultipliers.HackingLevelMultiplier &&
mults.AgilityLevelMultiplier === defaultMultipliers.AgilityLevelMultiplier &&
mults.DefenseLevelMultiplier === defaultMultipliers.DefenseLevelMultiplier &&
mults.DexterityLevelMultiplier === defaultMultipliers.DexterityLevelMultiplier &&
mults.StrengthLevelMultiplier === defaultMultipliers.StrengthLevelMultiplier &&
mults.CharismaLevelMultiplier === defaultMultipliers.CharismaLevelMultiplier
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Skills:</Typography>
<Box mx={1}>
{mults.HackingLevelMultiplier !== defaultMultipliers.HackingLevelMultiplier ? (
<Typography>Hacking: x{mults.HackingLevelMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.AgilityLevelMultiplier !== defaultMultipliers.AgilityLevelMultiplier ? (
<Typography>Agility: x{mults.AgilityLevelMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.DefenseLevelMultiplier !== defaultMultipliers.DefenseLevelMultiplier ? (
<Typography>Defense: x{mults.DefenseLevelMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.DexterityLevelMultiplier !== defaultMultipliers.DexterityLevelMultiplier ? (
<Typography>Dexterity: x{mults.DexterityLevelMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.StrengthLevelMultiplier !== defaultMultipliers.StrengthLevelMultiplier ? (
<Typography>Strength: x{mults.StrengthLevelMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.CharismaLevelMultiplier !== defaultMultipliers.CharismaLevelMultiplier ? (
<Typography>Charisma: x{mults.CharismaLevelMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
HackingLevelMultiplier: {
name: "Hacking Level",
color: Settings.theme.hack,
},
StrengthLevelMultiplier: {
name: "Strength Level",
color: Settings.theme.combat,
},
DefenseLevelMultiplier: {
name: "Defense Level",
color: Settings.theme.combat,
},
DexterityLevelMultiplier: {
name: "Dexterity Level",
color: Settings.theme.combat,
},
AgilityLevelMultiplier: {
name: "Agility Level",
color: Settings.theme.combat,
},
CharismaLevelMultiplier: {
name: "Charisma Level",
color: Settings.theme.cha,
},
};
return <BNMultTable sectionName="Skills" rowData={rows} mults={mults} />;
}
function HackingMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.ServerGrowthRate === defaultMultipliers.ServerGrowthRate &&
mults.ServerMaxMoney === defaultMultipliers.ServerMaxMoney &&
mults.ServerStartingMoney === defaultMultipliers.ServerStartingMoney &&
mults.ServerStartingSecurity === defaultMultipliers.ServerStartingSecurity &&
mults.ServerWeakenRate === defaultMultipliers.ServerWeakenRate &&
mults.ManualHackMoney === defaultMultipliers.ManualHackMoney &&
mults.ScriptHackMoney === defaultMultipliers.ScriptHackMoney &&
mults.ScriptHackMoneyGain === defaultMultipliers.ScriptHackMoneyGain &&
mults.HackExpGain === defaultMultipliers.HackExpGain
)
return <></>;
const rows: IBNMultRows = {
HackExpGain: {
name: "Hacking Exp",
color: Settings.theme.hack,
},
ServerGrowthRate: { name: "Server Growth Rate" },
ServerMaxMoney: { name: "Server Max Money" },
ServerStartingMoney: { name: "Server Starting Money" },
ServerStartingSecurity: { name: "Server Starting Security" },
ServerWeakenRate: { name: "Server Weaken Rate" },
ManualHackMoney: {
name: "Manual Hack Money",
color: Settings.theme.money,
},
ScriptHackMoney: {
name: "Script Hack Money",
color: Settings.theme.money,
},
ScriptHackMoneyGain: {
name: "Money Gained From Hack",
color: Settings.theme.money,
},
};
return (
<>
<br />
<Typography variant={"h5"}>Hacking:</Typography>
<Box mx={1}>
{mults.HackExpGain !== defaultMultipliers.HackExpGain ? (
<Typography>Exp: x{mults.HackExpGain.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ServerGrowthRate !== defaultMultipliers.ServerGrowthRate ? (
<Typography>Growth rate: x{mults.ServerGrowthRate.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ServerMaxMoney !== defaultMultipliers.ServerMaxMoney ? (
<Typography>Max money: x{mults.ServerMaxMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ServerStartingMoney !== defaultMultipliers.ServerStartingMoney ? (
<Typography>Starting money: x{mults.ServerStartingMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ServerStartingSecurity !== defaultMultipliers.ServerStartingSecurity ? (
<Typography>Starting security: x{mults.ServerStartingSecurity.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ServerWeakenRate !== defaultMultipliers.ServerWeakenRate ? (
<Typography>Weaken rate: x{mults.ServerWeakenRate.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ManualHackMoney !== defaultMultipliers.ManualHackMoney ? (
<Typography>Manual hack money: x{mults.ManualHackMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ScriptHackMoney !== defaultMultipliers.ScriptHackMoney ? (
<Typography>Hack money stolen: x{mults.ScriptHackMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ScriptHackMoneyGain !== defaultMultipliers.ScriptHackMoneyGain ? (
<Typography>Money gained from hack: x{mults.ScriptHackMoneyGain.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
return <BNMultTable sectionName="Hacking" rowData={rows} mults={mults} />;
}
function PurchasedServersMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.PurchasedServerCost === defaultMultipliers.PurchasedServerCost &&
mults.PurchasedServerSoftcap === defaultMultipliers.PurchasedServerSoftcap &&
mults.PurchasedServerLimit === defaultMultipliers.PurchasedServerLimit &&
mults.PurchasedServerMaxRam === defaultMultipliers.PurchasedServerMaxRam &&
mults.HomeComputerRamCost === defaultMultipliers.HomeComputerRamCost
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Purchased servers:</Typography>
<Box mx={1}>
{mults.PurchasedServerCost !== defaultMultipliers.PurchasedServerCost ? (
<Typography>Base cost: {mults.PurchasedServerCost.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.PurchasedServerSoftcap !== defaultMultipliers.PurchasedServerSoftcap ? (
<Typography>Softcap cost: {mults.PurchasedServerSoftcap.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.PurchasedServerLimit !== defaultMultipliers.PurchasedServerLimit ? (
<Typography>Limit: x{mults.PurchasedServerLimit.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.PurchasedServerMaxRam !== defaultMultipliers.PurchasedServerMaxRam ? (
<Typography>Max ram: x{mults.PurchasedServerMaxRam.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.HomeComputerRamCost !== defaultMultipliers.HomeComputerRamCost ? (
<Typography>Home ram cost: x{mults.HomeComputerRamCost.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
PurchasedServerCost: {
name: "Base Cost",
content: mults.PurchasedServerCost.toFixed(3),
},
PurchasedServerSoftcap: {
name: "Softcap Cost",
content: mults.PurchasedServerSoftcap.toFixed(3),
},
PurchasedServerLimit: { name: "Server Limit" },
PurchasedServerMaxRam: { name: "Max RAM" },
HomeComputerRamCost: { name: "Home RAM Cost" },
};
return <BNMultTable sectionName="Purchased Servers" rowData={rows} mults={mults} />;
}
function InfiltrationMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.InfiltrationMoney === defaultMultipliers.InfiltrationMoney &&
mults.InfiltrationRep === defaultMultipliers.InfiltrationRep
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Infiltration:</Typography>
<Box mx={1}>
{mults.InfiltrationMoney !== defaultMultipliers.InfiltrationMoney ? (
<Typography>Money: {mults.InfiltrationMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.InfiltrationRep !== defaultMultipliers.InfiltrationRep ? (
<Typography>Reputation: x{mults.InfiltrationRep.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
InfiltrationMoney: {
name: "Infiltration Money",
color: Settings.theme.money,
},
InfiltrationRep: {
name: "Infiltration Reputation",
color: Settings.theme.rep,
},
};
return <BNMultTable sectionName="Infiltration" rowData={rows} mults={mults} />;
}
function BladeburnerMults({ n, mults }: IMultsProps): React.ReactElement {
function BladeburnerMults({ mults }: IMultsProps): React.ReactElement {
const player = use.Player();
// access check
if (n !== 6 && n !== 7 && player.sourceFileLvl(6) === 0) return <></>;
//default mults check
if (mults.BladeburnerRank === 1 && mults.BladeburnerSkillCost === 1) return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Bladeburner:</Typography>
<Box mx={1}>
{mults.BladeburnerRank !== 1 ? <Typography>Rank gain: x{mults.BladeburnerRank.toFixed(3)}</Typography> : <></>}
{mults.BladeburnerSkillCost !== 1 ? (
<Typography>Skill cost: x{mults.BladeburnerSkillCost.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
if (!player.canAccessBladeburner()) return <></>;
const rows: IBNMultRows = {
BladeburnerRank: { name: "Rank Gain" },
BladeburnerSkillCost: { name: "Skill Cost" },
};
return <BNMultTable sectionName="Bladeburner" rowData={rows} mults={mults} />;
}
function StanekMults({ n, mults }: IMultsProps): React.ReactElement {
function StanekMults({ mults }: IMultsProps): React.ReactElement {
const player = use.Player();
// access check
if (n !== 13 && player.sourceFileLvl(13) === 0) return <></>;
//default mults check
if (
mults.StaneksGiftExtraSize === defaultMultipliers.StaneksGiftExtraSize &&
mults.StaneksGiftPowerMultiplier === defaultMultipliers.StaneksGiftPowerMultiplier
)
return <></>;
if (!player.canAccessCotMG()) return <></>;
const s = mults.StaneksGiftExtraSize;
return (
<>
<br />
<Typography variant={"h5"}>Stanek's Gift:</Typography>
<Box mx={1}>
{mults.StaneksGiftPowerMultiplier !== defaultMultipliers.StaneksGiftPowerMultiplier ? (
<Typography>Gift power: x{mults.StaneksGiftPowerMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
{s !== defaultMultipliers.StaneksGiftExtraSize ? (
<Typography>Base size modifier: {s > defaultMultipliers.StaneksGiftExtraSize ? `+${s}` : s}</Typography>
) : (
<></>
)}
</Box>
</>
);
const extraSize = mults.StaneksGiftExtraSize.toFixed(3);
const rows: IBNMultRows = {
StnakesGiftPowerMultiplier: { name: "Gift Power" },
StaneksGiftExtraSize: {
name: "Base Size Modifier",
content: `${mults.StaneksGiftExtraSize > defaultMultipliers.StaneksGiftExtraSize ? `+${extraSize}` : extraSize}`,
},
};
return <BNMultTable sectionName="Stanek's Gift" rowData={rows} mults={mults} />;
}
function GangMults({ n, mults }: IMultsProps): React.ReactElement {
function GangMults({ mults }: IMultsProps): React.ReactElement {
const player = use.Player();
// access check
if (n !== 2 && player.sourceFileLvl(2) === 0) return <></>;
// is it empty check
if (
mults.GangSoftcap === defaultMultipliers.GangSoftcap &&
mults.GangUniqueAugs === defaultMultipliers.GangUniqueAugs
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Gang:</Typography>
<Box mx={1}>
{mults.GangSoftcap !== defaultMultipliers.GangSoftcap ? (
<Typography>Softcap: {mults.GangSoftcap.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.GangUniqueAugs !== defaultMultipliers.GangUniqueAugs ? (
<Typography>Unique augs: x{mults.GangUniqueAugs.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
if (player.bitNodeN !== 2 && player.sourceFileLvl(2) <= 0) return <></>;
const rows: IBNMultRows = {
GangSoftcap: {
name: "Gang Softcap",
content: mults.GangSoftcap.toFixed(3),
},
GangUniqueAugs: { name: "Unique Augmentations" },
};
return <BNMultTable sectionName="Gang" rowData={rows} mults={mults} />;
}
function CorporationMults({ n, mults }: IMultsProps): React.ReactElement {
function CorporationMults({ mults }: IMultsProps): React.ReactElement {
const player = use.Player();
// access check
if (n !== 3 && player.sourceFileLvl(3) === 0) return <></>;
// is it empty check
if (
mults.CorporationSoftCap === defaultMultipliers.CorporationSoftCap &&
mults.CorporationValuation === defaultMultipliers.CorporationValuation
)
return <></>;
if (!player.canAccessCorporation()) return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Corporation:</Typography>
<Box mx={1}>
{mults.CorporationSoftCap !== defaultMultipliers.CorporationSoftCap ? (
<Typography>Softcap: {mults.CorporationSoftCap.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.CorporationValuation !== defaultMultipliers.CorporationValuation ? (
<Typography>Valuation: x{mults.CorporationValuation.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
CorporationSoftcap: {
name: "Corporation Softcap",
content: mults.CorporationSoftcap.toFixed(3),
},
CorporationValuation: { name: "Valuation" },
};
return <BNMultTable sectionName="Corporation" rowData={rows} mults={mults} />;
}

@ -41,7 +41,7 @@ export function PortalModal(props: IProps): React.ReactElement {
<br />
<br />
<Typography>{bitNode.info}</Typography>
<BitnodeMultiplierDescription n={props.n} />
<BitnodeMultiplierDescription n={props.n} level={newLevel} />
<br />
<br />
<Button

@ -700,7 +700,7 @@ export class Bladeburner implements IBladeburner {
// Set variables
if (args.length === 4) {
const variable = args[1];
const variable = args[1].toLowerCase(); // allows Action Type to be with or without capitalisation.
const val = args[2];
let highLow = false; // True for high, false for low
@ -1993,7 +1993,7 @@ export class Bladeburner implements IBladeburner {
}
// If the Player starts doing some other actions, set action to idle and alert
if (player.hasAugmentation(AugmentationNames.BladesSimulacrum) === false && player.isWorking) {
if (!player.hasAugmentation(AugmentationNames.BladesSimulacrum, true) && player.isWorking) {
if (this.action.type !== ActionTypes["Idle"]) {
let msg = "Your Bladeburner action was cancelled because you started doing something else.";
if (this.automateEnabled) {

@ -84,11 +84,12 @@ export const CONSTANTS: {
SoARepMult: number;
EntropyEffect: number;
TotalNumBitNodes: number;
InfiniteLoopLimit: number;
Donations: number; // number of blood/plasma/palette donation the dev have verified., boosts NFG
LatestUpdate: string;
} = {
VersionString: "1.6.4",
VersionNumber: 17,
VersionString: "1.7.0",
VersionNumber: 18,
// Speed (in ms) at which the main loop is updated
_idleSpeed: 200,
@ -226,22 +227,173 @@ export const CONSTANTS: {
// BitNode/Source-File related stuff
TotalNumBitNodes: 24,
InfiniteLoopLimit: 1000,
Donations: 7,
LatestUpdate: `
v1.6.3 - 2022-04-01 Few stanek fixes
----------------------------
## [draft] v1.7.0 - 2022-04-13 to 2022-05-20
Stanek Gift
#### Information
* Has a minimum size of 2x3
* Active Fragment property 'avgCharge' renamed to 'highestCharge'
* Formula for fragment effect updated to make 561% more sense.
Now you can charge to your heart content.
* Logs for the 'chargeFragment' function updated.
Modifications included between **2022-04-13** and **2022-05-20** 'b5e4d70' to '0fbe4a1').
Misc.
_[See Pull Requests on GitHub](https://github.com/search?q=user%3Adanielyxie%20repo%3Abitburner%20is%3Apr%20is%3Amerged%20merged%3A%222022-04-13T16%3A32%3A26.000Z..2022-05-20T06%3A08%3A51.000Z%22)_
#### Merged Pull Requests
- [Feature] Monaco Theme Editor (by @nickofolas) #[3438](https://github.com/danielyxie/bitburner/pull/3438)
- [Fix] Dummy Stanek grid width (by @nickofolas) #[3442](https://github.com/danielyxie/bitburner/pull/3442)
- [Fix] Theme browser assets not loading (by @nickofolas) #[3446](https://github.com/danielyxie/bitburner/pull/3446)
- Accept valid JSON arrays in coding contracts (by @Savlik) #[3247](https://github.com/danielyxie/bitburner/pull/3247)
- another dark theme? (by @hydroflame) #[3450](https://github.com/danielyxie/bitburner/pull/3450)
- API: Add repFromDonation() to the Formula API (by @Hoekstraa) #[3461](https://github.com/danielyxie/bitburner/pull/3461)
- API: Add safeguard to ns.killall(), preventing killing itself by default (by @Hoekstraa) #[3607](https://github.com/danielyxie/bitburner/pull/3607)
- API: FIX #2993 sleeve.travel with invalid city names (by @TheMas3212) #[3458](https://github.com/danielyxie/bitburner/pull/3458)
- API: Fix inconsistent return value in 'ns.grafting.getAugmentationGraftTime' (by @nickofolas) #[3539](https://github.com/danielyxie/bitburner/pull/3539)
- API: Fix leak of real Employee object in hireEmployee (by @TheMas3212) #[3483](https://github.com/danielyxie/bitburner/pull/3483)
- API: replace a number of references to workerscript.log with \_ctx.log (by @TheMas3212) #[3470](https://github.com/danielyxie/bitburner/pull/3470)
- API: Terminal screen can now be cleared from within scripts with ns.ui.clearTerminal() (by @Hoekstraa) #[3618](https://github.com/danielyxie/bitburner/pull/3618)
- AUGMENTATIONS: Fix 'isSpecial' filter in helper (Removes NeuroFlux, Stanek's Gift, etc from gangs) (by @nickofolas) #[3565](https://github.com/danielyxie/bitburner/pull/3565)
- AUGMENTATIONS: Fix Augmentation rep req not being properly influenced by BitNode multipliers (by @nickofolas) #[3652](https://github.com/danielyxie/bitburner/pull/3652)
- AUGMENTATIONS: Fix NeuroFlux being applied improperly and migrate broken saves (by @nickofolas) #[3613](https://github.com/danielyxie/bitburner/pull/3613)
- AUGMENTATIONS: Fix reputation check for faction augs (by @nickofolas) #[3609](https://github.com/danielyxie/bitburner/pull/3609)
- AUGMENTATIONS: Tweak a couple small UI elements (by @nickofolas) #[3614](https://github.com/danielyxie/bitburner/pull/3614)
- basic doc no longer hacker themed (by @hydroflame) #[3449](https://github.com/danielyxie/bitburner/pull/3449)
- BITNODE: FIX #3546 BitVerse now shows proper BN level when accessed via flume (by @nickofolas) #[3550](https://github.com/danielyxie/bitburner/pull/3550)
- BLADEBURNER: fixes #3648 : Automate console command capitalisation inconsistent (by @Vic1970) #[3647](https://github.com/danielyxie/bitburner/pull/3647)
- BLADEBURNER: Fix #3594 Blade's Simulacrum worked without being installed (by @Undeemiss) #[3639](https://github.com/danielyxie/bitburner/pull/3639)
- blood (by @hydroflame) #[3495](https://github.com/danielyxie/bitburner/pull/3495)
- BUGFIX: getAugmentationCost response backwards (by @phyzical) #[3617](https://github.com/danielyxie/bitburner/pull/3617)
- BUGFIX: Handle edge case in LZ compression code and fix docs (by @stalefishies) #[3581](https://github.com/danielyxie/bitburner/pull/3581)
- BUGFIX: make bonustime for gang in miliseconds (by @phyzical) #[3578](https://github.com/danielyxie/bitburner/pull/3578)
- BUGFIX: sleeve stale object refence during augmentation (by @phyzical) #[3601](https://github.com/danielyxie/bitburner/pull/3601)
- Bugfix/corp updates (by @phyzical) #[3321](https://github.com/danielyxie/bitburner/pull/3321)
- Bump async from 2.6.3 to 2.6.4 (by @dependabot[bot]) #[3463](https://github.com/danielyxie/bitburner/pull/3463)
- CODINGCONTRACT: Fix #3391 Double contract reward exploit (by @Undeemiss) #[3646](https://github.com/danielyxie/bitburner/pull/3646)
- CODINGCONTRACT: FIX #3484 BREAKING Fixed capitalization in contract name (by @Undeemiss) #[3537](https://github.com/danielyxie/bitburner/pull/3537)
- CODINGCONTRACT: New "Proper 2-Coloring of a Graph" contract (by @Undeemiss) #[3530](https://github.com/danielyxie/bitburner/pull/3530)
- CODINGCONTRACT: Three new compression contracts (by @stalefishies) #[3541](https://github.com/danielyxie/bitburner/pull/3541)
- CODINGCONTRACT: Typo & clarity fixes to description of Encoded Binary to Integer contract (by @ActuallyCurtis) #[3469](https://github.com/danielyxie/bitburner/pull/3469)
- CODINGCONTRACT: Updated description of 2-coloring contract (by @Undeemiss) #[3531](https://github.com/danielyxie/bitburner/pull/3531)
- COMPANY: Fix #3551 Applying for a new job will not change active employer if player is performing company work (by @Snarling) #[3552](https://github.com/danielyxie/bitburner/pull/3552)
- CORPORATIONS: Expose makeProducts on NSDivision interface (by @DavidGrinberg) #[3570](https://github.com/danielyxie/bitburner/pull/3570)
- CORPORATIONS: Expose sales cost on NSMaterial interface (by @DavidGrinberg) #[3574](https://github.com/danielyxie/bitburner/pull/3574)
- Corrected example grids found in Stanek help (by @Undeemiss) #[3441](https://github.com/danielyxie/bitburner/pull/3441)
- Create program action no longer creates duplicates (by @Undeemiss) #[3436](https://github.com/danielyxie/bitburner/pull/3436)
- DOCUMENTATION: Add descriptions for compression contracts (by @stalefishies) #[3559](https://github.com/danielyxie/bitburner/pull/3559)
- DOCUMENTATION: Add new coding contract descriptions (by @stalefishies) #[3542](https://github.com/danielyxie/bitburner/pull/3542)
- DOCUMENTATION: Clarify definition for installAugmentations() (by @PSEUDOSTAGE) #[3560](https://github.com/danielyxie/bitburner/pull/3560)
- DOCUMENTATION: FIX #3516 "cannot" misspelled as "cannnot" (by @Undeemiss) #[3533](https://github.com/danielyxie/bitburner/pull/3533)
- EDITOR: FIX #3502 Editor theme migration crash (by @nickofolas) #[3503](https://github.com/danielyxie/bitburner/pull/3503)
- FEATURE: added logic to allow quitJob to be called from singularity (by @phyzical) #[3577](https://github.com/danielyxie/bitburner/pull/3577)
- fix #3395 donating to special factions possible via singularity (by @TheMas3212) #[3456](https://github.com/danielyxie/bitburner/pull/3456)
- fix b1tflum3 and destroyW0r1dD43m0n singularity functions to check for sf4 (by @TheMas3212) #[3443](https://github.com/danielyxie/bitburner/pull/3443)
- Fix inconsistancy with trying to work for gang factions while running a gang (by @TheMas3212) #[3454](https://github.com/danielyxie/bitburner/pull/3454)
- Fix infiltration rep BN mult calculation (by @trambelus) #[3632](https://github.com/danielyxie/bitburner/pull/3632)
- Fix script editor settings. (by @hydroflame) #[3504](https://github.com/danielyxie/bitburner/pull/3504)
- Fix test/jest/Netscript/DynamicRamCalculation.test.js (by @TheMas3212) #[3455](https://github.com/danielyxie/bitburner/pull/3455)
- GRAFTING: Fix Grafting not being handled in singularity stop work (by @nickofolas) #[3568](https://github.com/danielyxie/bitburner/pull/3568)
- GRAFTING: Implement sorting options (by @nickofolas) #[3654](https://github.com/danielyxie/bitburner/pull/3654)
- INFILTRATION: Added new faction called infiltrators that provide infiltration specific augs. (by @phyzical) #[3241](https://github.com/danielyxie/bitburner/pull/3241)
- INFILTRATION: Fix minigame cycle (by @nickofolas) #[3549](https://github.com/danielyxie/bitburner/pull/3549)
- INFILTRATION: Fix phyzical WKS aug effects being applied before aug is installed (by @nickofolas) #[3555](https://github.com/danielyxie/bitburner/pull/3555)
- INFILTRATION: Fix rep reward being substantially higher than intended (by @nickofolas) #[3562](https://github.com/danielyxie/bitburner/pull/3562)
- INFILTRATION: New faction, Shadows of Anarchy, provides various augs to help infiltrations. (by @hydroflame) #[3543](https://github.com/danielyxie/bitburner/pull/3543)
- INFILTRATION: Update gameplay UI (by @nickofolas) #[3587](https://github.com/danielyxie/bitburner/pull/3587)
- keeping up to date (by @hydroflame) #[3432](https://github.com/danielyxie/bitburner/pull/3432)
- Keeping up to date. (by @hydroflame) #[3561](https://github.com/danielyxie/bitburner/pull/3561)
- Make .lit and .msg files clickable (by @Chris380) #[3453](https://github.com/danielyxie/bitburner/pull/3453)
- MESSAGES: Added the name of NiteSec's server to their .msg (by @Undeemiss) #[3466](https://github.com/danielyxie/bitburner/pull/3466)
- MISC: add better typing to Electron.tsx (by @taralx) #[3540](https://github.com/danielyxie/bitburner/pull/3540)
- MISC: Added NS function closeTail to close tail windows (by @Undeemiss) #[3666](https://github.com/danielyxie/bitburner/pull/3666)
- MISC: Adjust deps to current usage (by @taralx) #[3519](https://github.com/danielyxie/bitburner/pull/3519)
- MISC: Close some GitHub issues that do not need action (by @Undeemiss) #[3640](https://github.com/danielyxie/bitburner/pull/3640)
- MISC: Closing more GitHub issues I missed last time (by @Undeemiss) #[3665](https://github.com/danielyxie/bitburner/pull/3665)
- MISC: Correct BB Skill point achievement name (by @Undeemiss) #[3571](https://github.com/danielyxie/bitburner/pull/3571)
- MISC: Correct typos in getScriptRam docs. (by @nzdjb) #[3590](https://github.com/danielyxie/bitburner/pull/3590)
- MISC: Fix #3125 BREAKING Renamed BN mult CorporationSoftCap to CorporationSoftcap (by @Undeemiss) #[3638](https://github.com/danielyxie/bitburner/pull/3638)
- MISC: FIX #3593 Float errors can no longer prevent full usage of a server's available ram. (by @Snarling) #[3619](https://github.com/danielyxie/bitburner/pull/3619)
- MISC: fix typing conflict between jest and cypress (by @taralx) #[3518](https://github.com/danielyxie/bitburner/pull/3518)
- MISC: fix typing conflict between jest and cypress (by @taralx) #[3644](https://github.com/danielyxie/bitburner/pull/3644)
- MISC: Fixed typo in exceptionAlert.ts (by @Undeemiss) #[3572](https://github.com/danielyxie/bitburner/pull/3572)
- MISC: Fixed typos in game options (by @notacompsciguy) #[3584](https://github.com/danielyxie/bitburner/pull/3584)
- MISC: HammingCodingContracts need rework (by @Hedrauta) #[3479](https://github.com/danielyxie/bitburner/pull/3479)
- MISC: Implemented infinite loop safety net. (by @hydroflame) #[3624](https://github.com/danielyxie/bitburner/pull/3624)
- MISC: make jQuery use explicit (by @taralx) #[3517](https://github.com/danielyxie/bitburner/pull/3517)
- MISC: Make tutorial explain ns1 vs ns2 better (by @hydroflame) #[3586](https://github.com/danielyxie/bitburner/pull/3586)
- MISC: Remove comments that describe nonexistent augs (by @Undeemiss) #[3569](https://github.com/danielyxie/bitburner/pull/3569)
- MISC: update @types/numeral and fix type errors (by @taralx) #[3521](https://github.com/danielyxie/bitburner/pull/3521)
- MISC: Update logic for stats page BitNode level (by @nickofolas) #[3512](https://github.com/danielyxie/bitburner/pull/3512)
- MISC: upgrade to eslint v8 (by @taralx) #[3523](https://github.com/danielyxie/bitburner/pull/3523)
- MISC: Wrap most of the API in the new api wrapper (by @hydroflame) #[3627](https://github.com/danielyxie/bitburner/pull/3627)
- OPTIONS: Fix sliders not sliding correctly (by @nickofolas) #[3642](https://github.com/danielyxie/bitburner/pull/3642)
- REFACTOR: augmentation cost, rep cost and level to be calculated in place (by @phyzical) #[3544](https://github.com/danielyxie/bitburner/pull/3544)
- REFACTOR: augmentation isSpecial adjustments (by @phyzical) #[3564](https://github.com/danielyxie/bitburner/pull/3564)
- Reran npm format and lint to fix formatting (by @Undeemiss) #[3434](https://github.com/danielyxie/bitburner/pull/3434)
- Revert "MISC: fix typing conflict between jest and cypress" (by @hydroflame) #[3608](https://github.com/danielyxie/bitburner/pull/3608)
- Revert "MISC: HammingCodingContracts need rework" (by @hydroflame) #[3500](https://github.com/danielyxie/bitburner/pull/3500)
- revert theme (by @hydroflame) #[3451](https://github.com/danielyxie/bitburner/pull/3451)
- Singularity: Fix #3489 Disable checkTixApiAccess for purchase4SMarketData (by @DavidGrinberg) #[3490](https://github.com/danielyxie/bitburner/pull/3490)
- SLEEVES: Fix issues with Sleeve UI crashing when Sleeve task faction becomes gang faction (by @nickofolas) #[3557](https://github.com/danielyxie/bitburner/pull/3557)
- STANEK: Fix #3196 Charging booster fragments throws an error (by @Undeemiss) #[3637](https://github.com/danielyxie/bitburner/pull/3637)
- STANEK: FIX #3277 Can no longer overlap rotated fragments (by @Undeemiss) #[3460](https://github.com/danielyxie/bitburner/pull/3460)
- STANEK: FIX #3282 Added NS function stanek.acceptGift (by @Undeemiss) #[3513](https://github.com/danielyxie/bitburner/pull/3513)
- STANEK: Properly reapply entropy in Stanek's Gift (by @nickofolas) #[3673](https://github.com/danielyxie/bitburner/pull/3673)
- STANEK: Stanek NS functions correctly throw errors when stanek not installed (by @Undeemiss) #[3660](https://github.com/danielyxie/bitburner/pull/3660)
- Started collecting lore so that additions to it are simpler (by @Undeemiss) #[3465](https://github.com/danielyxie/bitburner/pull/3465)
- TERMINAL: FIX #3492 Allow cd .. even when destination directory is empty (by @Snarling) #[3525](https://github.com/danielyxie/bitburner/pull/3525)
- TERMINAL: FIX #3651 Make directory name regex more flexible (by @Dane-Horn) #[3653](https://github.com/danielyxie/bitburner/pull/3653)
- TOOLING: Add GitHub action to validate PR titles (by @MartinFournier) #[3471](https://github.com/danielyxie/bitburner/pull/3471)
- UI FIX #3485 - Allow bulk purchasing when smart supply is enabled (by @phyzical) #[3486](https://github.com/danielyxie/bitburner/pull/3486)
- UI: Change text color of Augmentations page backup button (by @nickofolas) #[3511](https://github.com/danielyxie/bitburner/pull/3511)
- UI: FIX #1754 Stanek effect summary & slight tweak. (by @borisflagell) #[3622](https://github.com/danielyxie/bitburner/pull/3622)
- UI: FIX #2228,#2958 Fix tab highlights and highlight files not on home. (by @phyzical) #[2989](https://github.com/danielyxie/bitburner/pull/2989)
- UI: FIX #2256 Hacknet server's upgrade tooltip were not handling RAM (by @borisflagell) #[3532](https://github.com/danielyxie/bitburner/pull/3532)
- UI: FIX #2741 Allow using modifier keys inside the typing infiltration (by @Dane-Horn) #[3634](https://github.com/danielyxie/bitburner/pull/3634)
- UI: FIX #2829 Remove defeated NPC gangs from territory page (by @Dane-Horn) #[3633](https://github.com/danielyxie/bitburner/pull/3633)
- UI: FIX #3313 Streamline the GraftingRoot page by making it rerender. (by @borisflagell) #[3558](https://github.com/danielyxie/bitburner/pull/3558)
- UI: FIX #3341 Enable touch-clicks in react-draggable (by @Snarling) #[3488](https://github.com/danielyxie/bitburner/pull/3488)
- UI: FIX #3415 Tweak Manage Gang button visibility (by @borisflagell) #[3528](https://github.com/danielyxie/bitburner/pull/3528)
- UI: FIX #3457 autocomplete suggestions no longer require hovering terminal input (by @Snarling) #[3493](https://github.com/danielyxie/bitburner/pull/3493)
- UI: FIX #3473 'mv' now says destination script is running instead of returning an error (by @Hoekstraa) #[3474](https://github.com/danielyxie/bitburner/pull/3474)
- UI: FIX #3522 realigned autocomplete popup (by @Snarling) #[3524](https://github.com/danielyxie/bitburner/pull/3524)
- UI: FIX #3592 Sidebar and bash shortcuts now work on MacOS with US-like layouts (by @Hoekstraa) #[3605](https://github.com/danielyxie/bitburner/pull/3605)
- UI: Fix Agility BitNode multiplier not appearing in UI (by @nickofolas) #[3662](https://github.com/danielyxie/bitburner/pull/3662)
- UI: Fix exclusive augs not always showing as purchasable through gangs when they should (by @nickofolas) #[3676](https://github.com/danielyxie/bitburner/pull/3676)
- UI: Fix the achievement covenant icon was not shown (by @Risenafis) #[3510](https://github.com/danielyxie/bitburner/pull/3510)
- UI: Fix z-index of modals overriding everything (by @nickofolas) #[3620](https://github.com/danielyxie/bitburner/pull/3620)
- UI: lightweight description update on "increase maximum money" hash spending option. (by @borisflagell) #[3547](https://github.com/danielyxie/bitburner/pull/3547)
- UI: Minor improvements to log boxes (by @nickofolas) #[3641](https://github.com/danielyxie/bitburner/pull/3641)
- UI: Overhaul GameOptions UI (by @nickofolas) #[3505](https://github.com/danielyxie/bitburner/pull/3505)
- UI: Positioning improved for tail titlebar buttons, and tail window has minimum size constraints. (by @Snarling) #[3548](https://github.com/danielyxie/bitburner/pull/3548)
- UI: Redesign purchasable Augmentations (by @nickofolas) #[3545](https://github.com/danielyxie/bitburner/pull/3545)
- UI: Refactor and redesign WorkInProgress interface (by @nickofolas) #[3611](https://github.com/danielyxie/bitburner/pull/3611)
- UI: Refactors, redesigns, and new section to stats page (by @nickofolas) #[3626](https://github.com/danielyxie/bitburner/pull/3626)
- UI: Sort and color Graft Augmentation list (by @jaype87) #[3616](https://github.com/danielyxie/bitburner/pull/3616)
- UI: Update Factions list interface (by @nickofolas) #[3675](https://github.com/danielyxie/bitburner/pull/3675)
- WORK: FIX #3435 Quitting the active job now sets first remaining job as active (by @Snarling) #[3507](https://github.com/danielyxie/bitburner/pull/3507)
- WORK: Refactor work types to use 'enum's instead of constants (by @nickofolas) #[3612](https://github.com/danielyxie/bitburner/pull/3612)
#### Other Changes
- increase donation counter (by @hydroflame) - [8456410](https://github.com/danielyxie/bitburner/commit/84564100e90c46ae4b816853c2cdea0bc309af4d)
- allbuild commit 7f9e3775 (by @hydroflame) - [791c19c](https://github.com/danielyxie/bitburner/commit/791c19c4fe447c9231bfb423b9fc48114e783b43)
- allbuild commit bcbda22a (by @hydroflame) - [032c440](https://github.com/danielyxie/bitburner/commit/032c440eaeb069eecd720ec2f8e069f705a0c1b4)
- fix documentation for getDarkwebPrograms (by @hydroflame) - [4056956](https://github.com/danielyxie/bitburner/commit/4056956c2ada37946333bdad44cb0b6eb3909bf8)
- support ASNI (by @hydroflame) - [36c7ef1](https://github.com/danielyxie/bitburner/commit/36c7ef1ad7ea8bb69fca23bce5883a3c2e23f1e0)
- allbuild commit 22b6d0d5 (by @hydroflame) - [b46718d](https://github.com/danielyxie/bitburner/commit/b46718d188880ecf716ae045861d81d61e00af4b)
- allbuild commit 36c7ef1a (by @hydroflame) - [d0ebf5e](https://github.com/danielyxie/bitburner/commit/d0ebf5e14e0498cb063fde35d63c9f59f2c01e35)
- Update documentation for employee (by @hydroflame) - [100e81c](https://github.com/danielyxie/bitburner/commit/100e81c8ab4a408f74cc9bd9ffe2b8bad3d03462)
- allbuild commit c799b291 (by @hydroflame) - [f5f5879](https://github.com/danielyxie/bitburner/commit/f5f5879fc380678d978e2b0a29ba7b6f0b4c9ec0)
- ideas (by @hydroflame) - [0121fee](https://github.com/danielyxie/bitburner/commit/0121fee6e4c690d01650d1e68a80ea363bb48bce)
- allbuild commit 0121fee6 (by @hydroflame) - [5c417e9](https://github.com/danielyxie/bitburner/commit/5c417e9b4df236df8bf3e2f8262b7bce87c934df)
- Update codebase for stanek (by @hydroflame) - [c2b4a5b](https://github.com/danielyxie/bitburner/commit/c2b4a5b52a2162d2e49c7317b0a60a349984eb47)
- fix lint (by @hydroflame) - [4cc518f](https://github.com/danielyxie/bitburner/commit/4cc518f37723aafb3168b64cd689408afdb74877)
- Fix (by @hydroflame) - [9af553f](https://github.com/danielyxie/bitburner/commit/9af553f63cb1380795550648b0134b608564fab8)
- Fix stanek leaking classes (by @hydroflame) - [fda3f02](https://github.com/danielyxie/bitburner/commit/fda3f02d73dba27034128c9be5e810a51e475e38)
- fix conflicts (by @hydroflame) - [ca1a2aa](https://github.com/danielyxie/bitburner/commit/ca1a2aad333fa838b6d0e57f89e1cedba086a4a0)
- Nerf noodle bar.
* Nerf noodle bar.
`,
};

@ -159,7 +159,7 @@ export class Corporation {
if (this.unlockUpgrades[6] === 1) {
upgrades += 0.1;
}
return Math.pow(dividends, BitNodeMultipliers.CorporationSoftCap + upgrades);
return Math.pow(dividends, BitNodeMultipliers.CorporationSoftcap + upgrades);
}
determineValuation(): number {

@ -68,13 +68,7 @@ export class ActiveFragment {
}
copy(): ActiveFragment {
// We have to do a round trip because the constructor.
const fragment = FragmentById(this.id);
if (fragment === null) throw new Error("ActiveFragment id refers to unknown Fragment.");
const c = new ActiveFragment({ x: this.x, y: this.y, rotation: this.rotation, fragment: fragment });
c.highestCharge = this.highestCharge;
c.numCharge = this.numCharge;
return c;
return Object.assign({}, this);
}
/**

@ -76,13 +76,7 @@ export class Fragment {
}
copy(): Fragment {
return new Fragment(
this.id,
this.shape.map((a) => a.slice()),
this.type,
this.power,
this.limit,
);
return Object.assign({}, this);
}
}

@ -136,8 +136,9 @@ export class StaneksGift implements IStaneksGift {
}
updateMults(p: IPlayer): void {
p.reapplyAllAugmentations(true);
p.reapplyAllSourceFiles();
// applyEntropy also reapplies all augmentations and source files
// This wraps up the reset nicely
p.applyEntropy(p.entropy);
for (const aFrag of this.fragments) {
const fragment = aFrag.fragment();

@ -1,5 +0,0 @@
import { StanekConstants } from "../data/Constants";
export function CalculateCharge(ram: number): number {
return ram * Math.pow(1 + Math.log2(ram) * StanekConstants.RAMBonus, 0.7);
}

@ -1,9 +1,9 @@
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
export function CalculateEffect(avgCharge: number, numCharge: number, power: number, boost: number): number {
export function CalculateEffect(highestCharge: number, numCharge: number, power: number, boost: number): number {
return (
1 +
(Math.log(avgCharge + 1) / (Math.log(1.8) * 100)) *
(Math.log(highestCharge + 1) / 60) *
Math.pow((numCharge + 1) / 5, 0.07) *
power *
boost *

@ -0,0 +1,88 @@
import React from "react";
import { ActiveFragment } from "../ActiveFragment";
import { IStaneksGift } from "../IStaneksGift";
import { FragmentType, Effect } from "../FragmentType";
import { numeralWrapper } from "../../ui/numeralFormat";
import Paper from "@mui/material/Paper";
import Typography from "@mui/material/Typography";
import Table from "@mui/material/Table";
import { TableBody, TableCell, TableRow } from "@mui/material";
type IProps = {
gift: IStaneksGift;
};
function formatEffect(effect: number, type: FragmentType): string {
if (Effect(type).includes("+x%")) {
return Effect(type).replace(/-*x%/, numeralWrapper.formatPercentage(effect - 1));
} else if (Effect(type).includes("-x%")) {
const perc = numeralWrapper.formatPercentage(1 - 1 / effect);
return Effect(type).replace(/-x%/, perc);
} else {
return Effect(type);
}
}
export function ActiveFragmentSummary(props: IProps): React.ReactElement {
const summary: { coordinate: { x: number; y: number }[]; effect: number; type: FragmentType }[] = [];
// Iterate through Active Fragment
props.gift.fragments.forEach((fragment: ActiveFragment) => {
const f = fragment.fragment();
// Discard ToolBrush and Booster.
if (![FragmentType.Booster, FragmentType.None, FragmentType.Delete].includes(f.type)) {
// Check for an existing entry in summary for this fragment's type
const entry = summary.find((e) => {
return e.type === f.type;
});
if (entry) {
// If there's one, update the existing entry
entry.effect *= props.gift.effect(fragment);
entry.coordinate.push({ x: fragment.x, y: fragment.y });
} else {
// If there's none, create a new entry
summary.push({
coordinate: [{ x: fragment.x, y: fragment.y }],
effect: props.gift.effect(fragment),
type: f.type,
});
}
}
});
return (
<Paper sx={{ mb: 1 }}>
<Typography variant="h5">Summary of active fragments:</Typography>
<Table sx={{ display: "table", width: "100%" }}>
<TableBody>
<TableRow>
<TableCell sx={{ borderBottom: "none", p: 0, m: 0 }}>
<Typography>Coordinate</Typography>
</TableCell>
<TableCell sx={{ borderBottom: "none", p: 0, m: 0 }}>
<Typography>Effect</Typography>
</TableCell>
</TableRow>
{summary.map((entry) => {
return (
<TableRow>
<TableCell sx={{ borderBottom: "none", p: 0, m: 0 }}>
<Typography>
{entry.coordinate.map((coord) => {
return "[" + coord.x + "," + coord.y + "]";
})}
</Typography>
</TableCell>
<TableCell sx={{ borderBottom: "none", p: 0, m: 0 }}>
<Typography>{formatEffect(entry.effect, entry.type)}</Typography>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</Paper>
);
}

@ -25,23 +25,21 @@ export function FragmentInspector(props: IProps): React.ReactElement {
if (props.fragment === undefined) {
return (
<Paper>
<Paper sx={{ flexGrow: 1 }}>
<Typography>
[X, Y] {props.x}, {props.y}
<br />
<br />
ID: N/A
<br />
Effect: N/A
<br />
Magnitude: N/A
Base Power: N/A
<br />
Charge: N/A
<br />
Heat: N/A
root [X, Y] N/A
<br />
Effect: N/A
<br />
[X, Y] N/A
<br />
[X, Y] {props.x}, {props.y}
</Typography>
</Paper>
);
@ -63,8 +61,11 @@ export function FragmentInspector(props: IProps): React.ReactElement {
}
return (
<Paper>
<Paper sx={{ flexGrow: 1 }}>
<Typography>
[X, Y] {props.x}, {props.y}
<br />
<br />
ID: {props.fragment.id}
<br />
Effect: {effect}
@ -73,10 +74,8 @@ export function FragmentInspector(props: IProps): React.ReactElement {
<br />
Charge: {charge}
<br />
<br />
root [X, Y] {props.fragment.x}, {props.fragment.y}
<br />
[X, Y] {props.x}, {props.y}
</Typography>
</Paper>
);

@ -9,6 +9,9 @@ import Button from "@mui/material/Button";
import { Table } from "../../ui/React/Table";
import { Grid } from "./Grid";
import { zeros, calculateGrid } from "../Helper";
import { ActiveFragmentSummary } from "./ActiveFragmentSummary";
import Tooltip from "@mui/material/Tooltip";
import Typography from "@mui/material/Typography";
interface IProps {
gift: IStaneksGift;
@ -84,9 +87,8 @@ export function MainBoard(props: IProps): React.ReactElement {
return (
<>
<Button onClick={clear}>Clear</Button>
<Box display="flex">
<Table>
<Box display="flex" sx={{ mb: 1 }}>
<Table sx={{ mr: 1 }}>
<Grid
width={props.gift.width()}
height={props.gift.height()}
@ -99,7 +101,22 @@ export function MainBoard(props: IProps): React.ReactElement {
</Table>
<FragmentInspector gift={props.gift} x={pos[0]} y={pos[1]} fragment={props.gift.fragmentAt(pos[0], pos[1])} />
</Box>
<Box display="flex" sx={{ mb: 1 }}>
<FragmentSelector gift={props.gift} selectFragment={updateSelectedFragment} />
</Box>
<ActiveFragmentSummary gift={props.gift} />
<Tooltip
title={
<Typography>
WARNING : This will remove all active fragment from the grid. <br />
All cumulated charges will be lost.
</Typography>
}
>
<Button onClick={clear}>Clear grid</Button>
</Tooltip>
</>
);
}

@ -10,6 +10,7 @@ import Typography from "@mui/material/Typography";
import { ActiveFragment } from "../ActiveFragment";
import { Fragments } from "../Fragment";
import { DummyGrid } from "./DummyGrid";
import Container from "@mui/material/Container";
type IProps = {
staneksGift: IStaneksGift;
@ -22,7 +23,7 @@ export function StaneksGiftRoot({ staneksGift }: IProps): React.ReactElement {
}
useEffect(() => StaneksGiftEvents.subscribe(rerender), []);
return (
<>
<Container maxWidth="lg" disableGutters sx={{ mx: 0 }}>
<Typography variant="h4">
Stanek's Gift
<Info
@ -184,18 +185,18 @@ export function StaneksGiftRoot({ staneksGift }: IProps): React.ReactElement {
/>
</Typography>
<Typography>
<Typography sx={{ mb: 1 }}>
The gift is a grid on which you can place upgrades called fragments. The main type of fragment increases a stat,
like your hacking skill or agility exp. Once a stat fragment is placed it then needs to be charged via scripts
in order to become useful. The other kind of fragments are called booster fragments. They increase the
efficiency of the neighboring fragments (not diagonally). Use Q/E to rotate fragments.
</Typography>
{staneksGift.storedCycles > 5 && (
<Typography>
<Typography sx={{ mb: 1 }}>
Bonus time: {convertTimeMsToTimeElapsedString(CONSTANTS._idleSpeed * staneksGift.storedCycles)}
</Typography>
)}
<MainBoard gift={staneksGift} />
</>
</Container>
);
}

@ -1,24 +1,194 @@
import { Explore, Info, LastPage, LocalPolice, NewReleases, Report, SportsMma } from "@mui/icons-material";
import { Box, Button, Container, Paper, Tooltip, Typography, useTheme } from "@mui/material";
import React, { useEffect, useState } from "react";
import { Box, Button, Container, Paper, TableBody, TableRow, Typography } from "@mui/material";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Table, TableCell } from "../../ui/React/Table";
import { Settings } from "../../Settings/Settings";
import { numeralWrapper } from "../../ui/numeralFormat";
import { IRouter } from "../../ui/Router";
import { Faction } from "../Faction";
import { joinFaction, getFactionAugmentationsFiltered } from "../FactionHelpers";
import { Factions } from "../Factions";
import { FactionNames } from "../data/FactionNames";
import { Faction } from "../Faction";
import { getFactionAugmentationsFiltered, joinFaction } from "../FactionHelpers";
import { Factions } from "../Factions";
export const InvitationsSeen: string[] = [];
const getAugsLeft = (faction: Faction, player: IPlayer): number => {
const augs = getFactionAugmentationsFiltered(player, faction);
return augs.filter((augmentation: string) => !player.hasAugmentation(augmentation)).length;
};
interface IWorkTypeProps {
faction: Faction;
}
const fontSize = "small";
const marginRight = 0.5;
const WorkTypesOffered = (props: IWorkTypeProps): React.ReactElement => {
const info = props.faction.getInfo();
return (
<>
{info.offerFieldWork && (
<Tooltip title="This Faction offers field work">
<Explore sx={{ color: Settings.theme.info, mr: marginRight }} fontSize={fontSize} />
</Tooltip>
)}
{info.offerHackingWork && (
<Tooltip title="This Faction offers hacking work">
<LastPage sx={{ color: Settings.theme.hack, mr: marginRight }} fontSize={fontSize} />
</Tooltip>
)}
{info.offerSecurityWork && (
<Tooltip title="This Faction offers security work">
<LocalPolice sx={{ color: Settings.theme.combat, mr: marginRight }} fontSize={fontSize} />
</Tooltip>
)}
</>
);
};
interface IFactionProps {
player: IPlayer;
router: IRouter;
faction: Faction;
joined: boolean;
rerender: () => void;
}
const FactionElement = (props: IFactionProps): React.ReactElement => {
const facInfo = props.faction.getInfo();
function openFaction(faction: Faction): void {
props.router.toFaction(faction);
}
function openFactionAugPage(faction: Faction): void {
props.router.toFaction(faction, true);
}
function acceptInvitation(event: React.MouseEvent<HTMLButtonElement, MouseEvent>, faction: string): void {
if (!event.isTrusted) return;
joinFaction(Factions[faction]);
props.rerender();
}
return (
<Paper
sx={{
display: "grid",
p: 1,
alignItems: "center",
gridTemplateColumns: "minmax(0, 4fr)" + (props.joined ? " 1fr" : ""),
}}
>
<Box display="flex" sx={{ alignItems: "center" }}>
{props.joined ? (
<Box
display="grid"
sx={{
mr: 1,
gridTemplateColumns: "1fr 1fr",
minWidth: "fit-content",
gap: 0.5,
"& .MuiButton-root": { height: "48px" },
}}
>
<Button onClick={() => openFaction(props.faction)}>Details</Button>
<Button onClick={() => openFactionAugPage(props.faction)}>Augments</Button>
</Box>
) : (
<Button sx={{ height: "48px", mr: 1 }} onClick={(e) => acceptInvitation(e, props.faction.name)}>
Join!
</Button>
)}
<span style={{ maxWidth: props.joined ? "70%" : "95%" }}>
<Typography
variant="h6"
sx={{
mr: 1,
display: "grid",
gridTemplateColumns: "fit-content(100vw) max-content",
alignItems: "center",
}}
>
<span
style={{ overflow: "hidden", whiteSpace: "nowrap", textOverflow: "ellipsis" }}
title={props.faction.name}
>
{props.faction.name}
</span>
<span style={{ display: "flex", alignItems: "center" }}>
{props.player.hasGangWith(props.faction.name) && (
<Tooltip title="You have a gang with this Faction">
<SportsMma sx={{ color: Settings.theme.hp, ml: 1 }} />
</Tooltip>
)}
{facInfo.special && (
<Tooltip title="This is a special Faction">
<NewReleases sx={{ ml: 1, color: Settings.theme.money, transform: "rotate(180deg)" }} />
</Tooltip>
)}
{!props.joined && facInfo.enemies.length > 0 && (
<Tooltip
title={
<Typography>
This Faction is enemies with:
<ul>
{facInfo.enemies.map((enemy) => (
<li key={enemy}>{enemy}</li>
))}
</ul>
Joining this Faction will prevent you from joining its enemies
</Typography>
}
>
<Report sx={{ ml: 1, color: Settings.theme.error }} />
</Tooltip>
)}
</span>
</Typography>
<span style={{ display: "flex", alignItems: "center" }}>
{!props.player.hasGangWith(props.faction.name) && <WorkTypesOffered faction={props.faction} />}
{props.joined && (
<Typography variant="body2" sx={{ display: "flex" }}>
{getAugsLeft(props.faction, props.player)} Augmentations left
</Typography>
)}
</span>
</span>
</Box>
{props.joined && (
<Box display="grid" sx={{ alignItems: "center", justifyItems: "left", gridAutoFlow: "row" }}>
<Typography sx={{ color: Settings.theme.rep }}>
{numeralWrapper.formatFavor(props.faction.favor)} favor
</Typography>
<Typography sx={{ color: Settings.theme.rep }}>
{numeralWrapper.formatReputation(props.faction.playerReputation)} rep
</Typography>
</Box>
)}
</Paper>
);
};
interface IProps {
player: IPlayer;
router: IRouter;
}
export function FactionsRoot(props: IProps): React.ReactElement {
const theme = useTheme();
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
@ -35,99 +205,90 @@ export function FactionsRoot(props: IProps): React.ReactElement {
});
}, []);
function openFaction(faction: Faction): void {
props.router.toFaction(faction);
}
function openFactionAugPage(faction: Faction): void {
props.router.toFaction(faction, true);
}
function acceptInvitation(event: React.MouseEvent<HTMLButtonElement, MouseEvent>, faction: string): void {
if (!event.isTrusted) return;
joinFaction(Factions[faction]);
setRerender((x) => !x);
}
const getAugsLeft = (faction: Faction, player: IPlayer): number => {
const augs = getFactionAugmentationsFiltered(player, faction);
return augs.filter((augmentation: string) => !player.hasAugmentation(augmentation)).length;
};
const allFactions = Object.values(FactionNames).map((faction) => faction as string);
const allJoinedFactions = props.player.factions.slice(0);
const allJoinedFactions = [...props.player.factions];
allJoinedFactions.sort((a, b) => allFactions.indexOf(a) - allFactions.indexOf(b));
const invitations = props.player.factionInvitations;
return (
<Container disableGutters maxWidth="md" sx={{ mx: 0, mb: 10 }}>
<Typography variant="h4">Factions</Typography>
<Typography mb={4}>
<Container disableGutters maxWidth="lg" sx={{ mx: 0, mb: 10 }}>
<Typography variant="h4">
Factions
<Tooltip
title={
<Typography>
Throughout the game you may receive invitations from factions. There are many different factions, and each
faction has different criteria for determining its potential members. Joining a faction and furthering its cause
is crucial to progressing in the game and unlocking endgame content.
faction has different criteria for determining its potential members. Joining a faction and furthering its
cause is crucial to progressing in the game and unlocking endgame content.
</Typography>
}
>
<Info sx={{ ml: 1, mb: 0 }} color="info" />
</Tooltip>
</Typography>
<Typography variant="h5" color="primary" mt={2} mb={1}>
Factions you have joined:
<Box
display="grid"
sx={{
gap: 1,
gridTemplateColumns: (invitations.length > 0 ? "1fr " : "") + "2fr",
[theme.breakpoints.down("lg")]: { gridTemplateColumns: "1fr", "& > span:nth-child(1)": { order: 1 } },
gridTemplateRows: "minmax(0, 1fr)",
"& > span > .MuiBox-root": {
display: "grid",
gridAutoRows: "70px",
gap: 1,
},
}}
>
{invitations.length > 0 && (
<span>
<Typography variant="h5" color="primary">
Faction Invitations
</Typography>
{(allJoinedFactions.length > 0 && (
<Paper sx={{ my: 1, p: 1, pb: 0, display: "inline-block" }}>
<Table padding="none" style={{ width: "fit-content" }}>
<TableBody>
{allJoinedFactions.map((faction: string) => (
<TableRow key={faction}>
<TableCell>
<Typography noWrap mb={1}>
{faction}
</Typography>
</TableCell>
<TableCell align="right">
<Box ml={1} mb={1}>
<Button onClick={() => openFaction(Factions[faction])}>Details</Button>
<Box>
{invitations.map((facName) => {
if (!Factions.hasOwnProperty(facName)) return null;
return (
<FactionElement
key={facName}
faction={Factions[facName]}
player={props.player}
router={props.router}
joined={false}
rerender={rerender}
/>
);
})}
</Box>
</TableCell>
<TableCell align="right">
<Box ml={1} mb={1}>
<Button sx={{ width: "100%" }} onClick={() => openFactionAugPage(Factions[faction])}>
Augmentations Left: {getAugsLeft(Factions[faction], props.player)}
</Button>
</span>
)}
<span>
<Typography variant="h5" color="primary">
Your Factions
</Typography>
<Box>
{allJoinedFactions.length > 0 ? (
allJoinedFactions.map((facName) => {
if (!Factions.hasOwnProperty(facName)) return null;
return (
<FactionElement
key={facName}
faction={Factions[facName]}
player={props.player}
router={props.router}
joined={true}
rerender={rerender}
/>
);
})
) : (
<Typography>You have not yet joined any Factions.</Typography>
)}
</Box>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Paper>
)) || <Typography>You haven't joined any factions.</Typography>}
<Typography variant="h5" color="primary" mt={4} mb={1}>
Outstanding Faction Invitations
</Typography>
<Typography mb={1}>
Factions you have been invited to. You can accept these faction invitations at any time:
</Typography>
{(props.player.factionInvitations.length > 0 && (
<Paper sx={{ my: 1, mb: 4, p: 1, pb: 0, display: "inline-block" }}>
<Table padding="none">
<TableBody>
{props.player.factionInvitations.map((faction: string) => (
<TableRow key={faction}>
<TableCell>
<Typography noWrap mb={1}>
{faction}
</Typography>
</TableCell>
<TableCell align="right">
<Box ml={1} mb={1}>
<Button onClick={(e) => acceptInvitation(e, faction)}>Join!</Button>
</span>
</Box>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Paper>
)) || <Typography>You have no outstanding faction invites.</Typography>}
</Container>
);
}

@ -70,7 +70,7 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
<>
<OptionsSlider
label=".script exec time (ms)"
value={execTime}
initialValue={execTime}
callback={handleExecTimeChange}
step={1}
min={5}
@ -84,7 +84,7 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
/>
<OptionsSlider
label="Recently killed scripts size"
value={recentScriptsSize}
initialValue={recentScriptsSize}
callback={handleRecentScriptsSizeChange}
step={25}
min={0}
@ -98,7 +98,7 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
/>
<OptionsSlider
label="Netscript log size"
value={logSize}
initialValue={logSize}
callback={handleLogSizeChange}
step={20}
min={20}
@ -112,7 +112,7 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
/>
<OptionsSlider
label="Netscript port size"
value={portSize}
initialValue={portSize}
callback={handlePortSizeChange}
step={1}
min={20}
@ -126,7 +126,7 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
/>
<OptionsSlider
label="Terminal capacity"
value={terminalSize}
initialValue={terminalSize}
callback={handleTerminalSizeChange}
step={50}
min={50}
@ -141,7 +141,7 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
/>
<OptionsSlider
label="Autosave interval (s)"
value={autosaveInterval}
initialValue={autosaveInterval}
callback={handleAutosaveIntervalChange}
step={30}
min={0}
@ -179,6 +179,12 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
</>
}
/>
<OptionSwitch
checked={Settings.InfinityLoopSafety}
onChange={(newValue) => (Settings.InfinityLoopSafety = newValue)}
text="Script infinite loop safety net"
tooltip={<>If this is set the game will attempt to automatically kill scripts stuck in infinite loops.</>}
/>
</GameOptionsPage>
),
[GameOptionsTab.INTERFACE]: (

@ -1,8 +1,8 @@
import { Slider, Tooltip, Typography, Box } from "@mui/material";
import React from "react";
import React, { useState } from "react";
interface IProps {
value: any;
initialValue: any;
callback: (event: any, newValue: number | number[]) => void;
step: number;
min: number;
@ -13,14 +13,21 @@ interface IProps {
}
export const OptionsSlider = (props: IProps): React.ReactElement => {
const [value, setValue] = useState(props.initialValue);
const onChange = (_evt: Event, newValue: number | Array<number>): void => {
setValue(newValue);
};
return (
<Box>
<Tooltip title={<Typography>{props.tooltip}</Typography>}>
<Typography>{props.label}</Typography>
</Tooltip>
<Slider
value={props.value}
onChange={props.callback}
value={value}
onChange={onChange}
onChangeCommitted={props.callback}
step={props.step}
min={props.min}
max={props.max}

@ -85,7 +85,13 @@ export function TerritorySubpage(): React.ReactElement {
</Typography>
</Box>
<Box sx={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)" }}>
{gangNames.map((name) => (
{gangNames
.sort((a, b) => {
if (AllGangs[a].territory <= 0 && AllGangs[b].territory > 0) return 1;
if (AllGangs[a].territory > 0 && AllGangs[b].territory <= 0) return -1;
return 0;
})
.map((name) => (
<OtherGangTerritory key={name} name={name} />
))}
</Box>
@ -114,14 +120,16 @@ function OtherGangTerritory(props: ITerritoryProps): React.ReactElement {
const playerPower = AllGangs[gang.facName].power;
const power = AllGangs[props.name].power;
const clashVictoryChance = playerPower / (power + playerPower);
const territory = AllGangs[props.name].territory;
const opacity = territory ? 1 : 0.75;
return (
<Box component={Paper} sx={{ p: 1 }}>
<Box component={Paper} sx={{ p: 1, opacity }}>
<Typography variant="h6" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
{props.name}
</Typography>
<Typography>
<b>Power:</b> {formatNumber(power, 3)} <br />
<b>Territory:</b> {formatTerritory(AllGangs[props.name].territory)}% <br />
<b>Territory:</b> {formatTerritory(territory)}% <br />
<b>Clash Win Chance:</b> {numeralWrapper.formatPercentage(clashVictoryChance, 3)}
</Typography>
</Box>

@ -36,7 +36,7 @@ export function calculateTradeInformationRepReward(
30 *
levelBonus *
(player.hasAugmentation(AugmentationNames.WKSharmonizer, true) ? 1.5 : 1) *
BitNodeMultipliers.InfiltrationMoney
BitNodeMultipliers.InfiltrationRep
);
}

@ -37,9 +37,13 @@ export function BackwardGame(props: IMinigameProps): React.ReactElement {
const [guess, setGuess] = useState("");
const hasAugment = Player.hasAugmentation(AugmentationNames.ChaosOfDionysus, true);
function ignorableKeyboardEvent(event: KeyboardEvent): boolean {
return event.key === KEY.BACKSPACE || (event.shiftKey && event.key === "Shift") || event.ctrlKey || event.altKey;
}
function press(this: Document, event: KeyboardEvent): void {
event.preventDefault();
if (event.key === KEY.BACKSPACE) return;
if (ignorableKeyboardEvent(event)) return;
const nextGuess = guess + event.key.toUpperCase();
if (!answer.startsWith(nextGuess)) props.onFailure();
else if (answer === nextGuess) props.onSuccess();

@ -2,11 +2,11 @@
* Location and traveling-related helper functions.
* Mostly used for UI
*/
import { SpecialServers } from "../Server/data/SpecialServers";
import { CONSTANTS } from "../Constants";
import { IPlayer } from "../PersonObjects/IPlayer";
import { AddToAllServers, createUniqueRandomIp } from "../Server/AllServers";
import { safetlyCreateUniqueServer } from "../Server/ServerHelpers";
import { GetServer } from "../Server/AllServers";
import { dialogBoxCreate } from "../ui/React/DialogBox";
@ -25,19 +25,14 @@ export function purchaseTorRouter(p: IPlayer): void {
}
p.loseMoney(CONSTANTS.TorRouterCost, "other");
const darkweb = safetlyCreateUniqueServer({
ip: createUniqueRandomIp(),
hostname: "darkweb",
organizationName: "",
isConnectedTo: false,
adminRights: false,
purchasedByPlayer: false,
maxRam: 1,
});
AddToAllServers(darkweb);
const darkweb = GetServer(SpecialServers.DarkWeb);
if (!darkweb) {
throw new Error("Dark web is not a server.");
}
p.getHomeComputer().serversOnNetwork.push(darkweb.hostname);
darkweb.serversOnNetwork.push(p.getHomeComputer().hostname);
console.log(darkweb);
dialogBoxCreate(
"You have purchased a TOR router!<br>" +
"You now have access to the dark web from your home computer.<br>" +

@ -32,7 +32,7 @@ export function RamButton(props: IProps): React.ReactElement {
}
const bnMult = BitNodeMultipliers.HomeComputerRamCost === 1 ? "" : `\\cdot ${BitNodeMultipliers.HomeComputerRamCost}`;
console.log(BitNodeMultipliers.HomeComputerRamCost);
return (
<Tooltip
title={

@ -317,7 +317,7 @@ export function SpecialLocation(props: IProps): React.ReactElement {
return renderGrafting();
}
case LocationName.Sector12CityHall: {
return (BitNodeMultipliers.CorporationSoftCap < 0.15 && <></>) || <CreateCorporation />;
return (BitNodeMultipliers.CorporationSoftcap < 0.15 && <></>) || <CreateCorporation />;
}
case LocationName.Sector12NSA: {
return renderBladeburner();

@ -5,6 +5,8 @@ import type { WorkerScript } from "./WorkerScript";
import { makeRuntimeRejectMsg } from "../NetscriptEvaluator";
import { Player } from "../Player";
import { CityName } from "src/Locations/data/CityNames";
import { Settings } from "../Settings/Settings";
import { CONSTANTS } from "../Constants";
type ExternalFunction = (...args: any[]) => any;
type ExternalAPI = {
@ -91,8 +93,14 @@ function wrapFunction(
getValidPort: (port: any) => helpers.getValidPort(functionPath, port),
},
};
const safetyEnabled = Settings.InfinityLoopSafety;
function wrappedFunction(...args: unknown[]): unknown {
helpers.updateDynamicRam(ctx.function, getRamCost(Player, ...tree, ctx.function));
if (safetyEnabled) workerScript.infiniteLoopSafetyCounter++;
if (workerScript.infiniteLoopSafetyCounter > CONSTANTS.InfiniteLoopLimit)
throw new Error(
`Infinite loop without sleep detected. ${CONSTANTS.InfiniteLoopLimit} ns functions were called without sleep. This will cause your UI to hang.`,
);
return func(ctx)(...args);
}
const parent = getNestedProperty(wrappedAPI, ...tree);

@ -51,6 +51,7 @@ export const RamCostConstants: IMap<number> = {
ScriptCodingContractBaseRamCost: 10,
ScriptSleeveBaseRamCost: 4,
ScriptGetOwnedSourceFiles: 5,
ScriptClearTerminalCost: 0.2,
ScriptSingularityFn1RamCost: 2,
ScriptSingularityFn2RamCost: 3,
@ -359,6 +360,7 @@ export const RamCosts: IMap<any> = {
enableLog: 0,
isLogEnabled: 0,
getScriptLogs: 0,
clearTerminal: RamCostConstants.ScriptClearTerminalCost,
nuke: RamCostConstants.ScriptPortProgramRamCost,
brutessh: RamCostConstants.ScriptPortProgramRamCost,
ftpcrack: RamCostConstants.ScriptPortProgramRamCost,

@ -111,6 +111,11 @@ export class WorkerScript {
*/
atExit: any;
/**
* Once this counter reaches it's limit the script crashes. It is reset when a promise completes.
*/
infiniteLoopSafetyCounter = 0;
constructor(runningScriptObj: RunningScript, pid: number, nsFuncsGenerator?: (ws: WorkerScript) => any) {
this.name = runningScriptObj.filename;
this.hostname = runningScriptObj.server;

@ -14,6 +14,7 @@ export function netscriptDelay(time: number, workerScript: WorkerScript): Promis
workerScript.delay = null;
workerScript.delayReject = undefined;
workerScript.infiniteLoopSafetyCounter = 0;
if (workerScript.env.stopFlag) reject(new ScriptDeath(workerScript));
else resolve();
}, time);

@ -55,7 +55,7 @@ import { makeRuntimeRejectMsg, netscriptDelay, resolveNetscriptRequestedThreads
import { numeralWrapper } from "./ui/numeralFormat";
import { convertTimeMsToTimeElapsedString } from "./utils/StringHelperFunctions";
import { LogBoxEvents } from "./ui/React/LogBoxManager";
import { LogBoxEvents, LogBoxCloserEvents } from "./ui/React/LogBoxManager";
import { arrayToString } from "./utils/helpers/arrayToString";
import { isString } from "./utils/helpers/isString";
@ -82,6 +82,7 @@ import {
Gang as IGang,
Bladeburner as IBladeburner,
Stanek as IStanek,
Sleeve as ISleeve,
Infiltration as IInfiltration,
RunningScript as IRunningScript,
RecentScript as IRecentScript,
@ -93,6 +94,12 @@ import {
BitNodeMultipliers as IBNMults,
Server as IServerDef,
RunningScript as IRunningScriptDef,
Grafting as IGrafting,
UserInterface as IUserInterface,
TIX as ITIX,
Corporation as ICorporation,
CodingContract as ICodingContract,
Hacknet as IHacknet,
// ToastVariant,
} from "./ScriptEditor/NetscriptDefinitions";
import { NetscriptSingularity } from "./NetscriptFunctions/Singularity";
@ -360,7 +367,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
}
};
const hack = function (
const hack = async function (
hostname: string,
manual: boolean,
{ threads: requestedThreads, stock }: any = {},
@ -524,23 +531,35 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
},
};
const gang = NetscriptGang(Player, workerScript, helper);
const sleeve = NetscriptSleeve(Player, workerScript, helper);
const extra = NetscriptExtra(Player, workerScript, helper);
const hacknet = NetscriptHacknet(Player, workerScript, helper);
const formulas = NetscriptFormulas(Player, workerScript, helper);
const gang = wrapAPI(helper, {}, workerScript, NetscriptGang(Player, workerScript), "gang").gang as unknown as IGang;
const sleeve = wrapAPI(helper, {}, workerScript, NetscriptSleeve(Player), "sleeve").sleeve as unknown as ISleeve;
const hacknet = wrapAPI(helper, {}, workerScript, NetscriptHacknet(Player, workerScript), "hacknet")
.hacknet as unknown as IHacknet;
const bladeburner = wrapAPI(helper, {}, workerScript, NetscriptBladeburner(Player, workerScript), "bladeburner")
.bladeburner as unknown as IBladeburner;
const codingcontract = wrapAPI(
helper,
{},
workerScript,
NetscriptCodingContract(Player, workerScript),
"codingcontract",
).codingcontract as unknown as ICodingContract;
const infiltration = wrapAPI(helper, {}, workerScript, NetscriptInfiltration(Player), "infiltration")
.infiltration as unknown as IInfiltration;
const stanek = wrapAPI(helper, {}, workerScript, NetscriptStanek(Player, workerScript, helper), "stanek")
.stanek as unknown as IStanek;
const bladeburner = NetscriptBladeburner(Player, workerScript, helper);
const codingcontract = NetscriptCodingContract(Player, workerScript, helper);
const corporation = NetscriptCorporation(Player, workerScript, helper);
const formulas = NetscriptFormulas(Player, workerScript, helper);
const corporation = wrapAPI(helper, {}, workerScript, NetscriptCorporation(Player, workerScript), "corporation")
.corporation as unknown as ICorporation;
const singularity = wrapAPI(helper, {}, workerScript, NetscriptSingularity(Player, workerScript), "singularity")
.singularity as unknown as ISingularity;
const stockmarket = NetscriptStockMarket(Player, workerScript, helper);
const ui = NetscriptUserInterface(Player, workerScript, helper);
const grafting = NetscriptGrafting(Player, workerScript, helper);
const stockmarket = wrapAPI(helper, {}, workerScript, NetscriptStockMarket(Player, workerScript), "stock")
.stock as unknown as ITIX;
const ui = wrapAPI(helper, {}, workerScript, NetscriptUserInterface(), "ui").ui as unknown as IUserInterface;
const grafting = wrapAPI(helper, {}, workerScript, NetscriptGrafting(Player), "grafting")
.grafting as unknown as IGrafting;
const base: INS = {
...singularity,
@ -988,6 +1007,12 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
LogBoxEvents.emit(runningScriptObj);
},
closeTail: function (_pid: unknown = workerScript.scriptRef.pid): void {
updateDynamicRam("closeTail", getRamCost(Player, "closeTail"));
const pid = helper.number("closeTail", "pid", _pid);
//Emit an event to tell the game to close the tail window if it exists
LogBoxCloserEvents.emit(pid);
},
nuke: function (_hostname: unknown): boolean {
updateDynamicRam("nuke", getRamCost(Player, "nuke"));
const hostname = helper.string("tail", "hostname", _hostname);

@ -1,18 +1,13 @@
import { INetscriptHelper } from "./INetscriptHelper";
import { WorkerScript } from "../Netscript/WorkerScript";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Bladeburner } from "../Bladeburner/Bladeburner";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { Bladeburner as INetscriptBladeburner, BladeburnerCurAction } from "../ScriptEditor/NetscriptDefinitions";
import { IAction } from "src/Bladeburner/IAction";
import { InternalAPI, NetscriptContext } from "src/Netscript/APIWrapper";
export function NetscriptBladeburner(
player: IPlayer,
workerScript: WorkerScript,
helper: INetscriptHelper,
): INetscriptBladeburner {
const checkBladeburnerAccess = function (func: string, skipjoined = false): void {
export function NetscriptBladeburner(player: IPlayer, workerScript: WorkerScript): InternalAPI<INetscriptBladeburner> {
const checkBladeburnerAccess = function (ctx: NetscriptContext, skipjoined = false): void {
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Must have joined bladeburner");
const apiAccess =
@ -22,160 +17,155 @@ export function NetscriptBladeburner(
});
if (!apiAccess) {
const apiDenied = `You do not currently have access to the Bladeburner API. You must either be in BitNode-7 or have Source-File 7.`;
throw helper.makeRuntimeErrorMsg(`bladeburner.${func}`, apiDenied);
throw ctx.makeRuntimeErrorMsg(apiDenied);
}
if (!skipjoined) {
const bladeburnerAccess = bladeburner instanceof Bladeburner;
if (!bladeburnerAccess) {
const bladeburnerDenied = `You must be a member of the Bladeburner division to use this API.`;
throw helper.makeRuntimeErrorMsg(`bladeburner.${func}`, bladeburnerDenied);
throw ctx.makeRuntimeErrorMsg(bladeburnerDenied);
}
}
};
const checkBladeburnerCity = function (func: string, city: string): void {
const checkBladeburnerCity = function (ctx: NetscriptContext, city: string): void {
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Must have joined bladeburner");
if (!bladeburner.cities.hasOwnProperty(city)) {
throw helper.makeRuntimeErrorMsg(`bladeburner.${func}`, `Invalid city: ${city}`);
throw ctx.makeRuntimeErrorMsg(`Invalid city: ${city}`);
}
};
const getBladeburnerActionObject = function (func: string, type: string, name: string): IAction {
const getBladeburnerActionObject = function (ctx: NetscriptContext, type: string, name: string): IAction {
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Must have joined bladeburner");
const actionId = bladeburner.getActionIdFromTypeAndName(type, name);
if (!actionId) {
throw helper.makeRuntimeErrorMsg(`bladeburner.${func}`, `Invalid action type='${type}', name='${name}'`);
throw ctx.makeRuntimeErrorMsg(`Invalid action type='${type}', name='${name}'`);
}
const actionObj = bladeburner.getActionObject(actionId);
if (!actionObj) {
throw helper.makeRuntimeErrorMsg(`bladeburner.${func}`, `Invalid action type='${type}', name='${name}'`);
throw ctx.makeRuntimeErrorMsg(`Invalid action type='${type}', name='${name}'`);
}
return actionObj;
};
const updateRam = (funcName: string): void =>
helper.updateDynamicRam(funcName, getRamCost(player, "bladeburner", funcName));
return {
getContractNames: function (): string[] {
updateRam("getContractNames");
checkBladeburnerAccess("getContractNames");
getContractNames: (ctx: NetscriptContext) => (): string[] => {
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.getContractNamesNetscriptFn();
},
getOperationNames: function (): string[] {
updateRam("getOperationNames");
checkBladeburnerAccess("getOperationNames");
getOperationNames: (ctx: NetscriptContext) => (): string[] => {
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.getOperationNamesNetscriptFn();
},
getBlackOpNames: function (): string[] {
updateRam("getBlackOpNames");
checkBladeburnerAccess("getBlackOpNames");
getBlackOpNames: (ctx: NetscriptContext) => (): string[] => {
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.getBlackOpNamesNetscriptFn();
},
getBlackOpRank: function (_blackOpName: unknown): number {
updateRam("getBlackOpRank");
const blackOpName = helper.string("getBlackOpRank", "blackOpName", _blackOpName);
checkBladeburnerAccess("getBlackOpRank");
const action: any = getBladeburnerActionObject("getBlackOpRank", "blackops", blackOpName);
getBlackOpRank:
(ctx: NetscriptContext) =>
(_blackOpName: unknown): number => {
const blackOpName = ctx.helper.string("blackOpName", _blackOpName);
checkBladeburnerAccess(ctx);
const action: any = getBladeburnerActionObject(ctx, "blackops", blackOpName);
return action.reqdRank;
},
getGeneralActionNames: function (): string[] {
updateRam("getGeneralActionNames");
checkBladeburnerAccess("getGeneralActionNames");
getGeneralActionNames: (ctx: NetscriptContext) => (): string[] => {
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.getGeneralActionNamesNetscriptFn();
},
getSkillNames: function (): string[] {
updateRam("getSkillNames");
checkBladeburnerAccess("getSkillNames");
getSkillNames: (ctx: NetscriptContext) => (): string[] => {
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.getSkillNamesNetscriptFn();
},
startAction: function (_type: unknown, _name: unknown): boolean {
updateRam("startAction");
const type = helper.string("startAction", "type", _type);
const name = helper.string("startAction", "name", _name);
checkBladeburnerAccess("startAction");
startAction:
(ctx: NetscriptContext) =>
(_type: unknown, _name: unknown): boolean => {
const type = ctx.helper.string("type", _type);
const name = ctx.helper.string("name", _name);
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.startActionNetscriptFn(player, type, name, workerScript);
} catch (e: any) {
throw helper.makeRuntimeErrorMsg("bladeburner.startAction", e);
throw ctx.makeRuntimeErrorMsg(e);
}
},
stopBladeburnerAction: function (): void {
updateRam("stopBladeburnerAction");
checkBladeburnerAccess("stopBladeburnerAction");
stopBladeburnerAction: (ctx: NetscriptContext) => (): void => {
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.resetAction();
},
getCurrentAction: function (): BladeburnerCurAction {
updateRam("getCurrentAction");
checkBladeburnerAccess("getCurrentAction");
getCurrentAction: (ctx: NetscriptContext) => (): BladeburnerCurAction => {
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.getTypeAndNameFromActionId(bladeburner.action);
},
getActionTime: function (_type: unknown, _name: unknown): number {
updateRam("getActionTime");
const type = helper.string("getActionTime", "type", _type);
const name = helper.string("getActionTime", "name", _name);
checkBladeburnerAccess("getActionTime");
getActionTime:
(ctx: NetscriptContext) =>
(_type: unknown, _name: unknown): number => {
const type = ctx.helper.string("type", _type);
const name = ctx.helper.string("name", _name);
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
const time = bladeburner.getActionTimeNetscriptFn(player, type, name);
if (typeof time === "string") {
const errorLogText = `Invalid action: type='${type}' name='${name}'`;
workerScript.log("bladeburner.getActionTime", () => errorLogText);
ctx.log(() => errorLogText);
return -1;
} else {
return time;
}
} catch (e: any) {
throw helper.makeRuntimeErrorMsg("bladeburner.getActionTime", e);
throw ctx.makeRuntimeErrorMsg(e);
}
},
getActionEstimatedSuccessChance: function (_type: unknown, _name: unknown): [number, number] {
updateRam("getActionEstimatedSuccessChance");
const type = helper.string("getActionEstimatedSuccessChance", "type", _type);
const name = helper.string("getActionEstimatedSuccessChance", "name", _name);
checkBladeburnerAccess("getActionEstimatedSuccessChance");
getActionEstimatedSuccessChance:
(ctx: NetscriptContext) =>
(_type: unknown, _name: unknown): [number, number] => {
const type = ctx.helper.string("type", _type);
const name = ctx.helper.string("name", _name);
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
const chance = bladeburner.getActionEstimatedSuccessChanceNetscriptFn(player, type, name);
if (typeof chance === "string") {
const errorLogText = `Invalid action: type='${type}' name='${name}'`;
workerScript.log("bladeburner.getActionTime", () => errorLogText);
ctx.log(() => errorLogText);
return [-1, -1];
} else {
return chance;
}
} catch (e: any) {
throw helper.makeRuntimeErrorMsg("bladeburner.getActionEstimatedSuccessChance", e);
throw ctx.makeRuntimeErrorMsg(e);
}
},
getActionRepGain: function (_type: unknown, _name: unknown, _level: unknown): number {
updateRam("getActionRepGain");
const type = helper.string("getActionRepGain", "type", _type);
const name = helper.string("getActionRepGain", "name", _name);
const level = helper.number("getActionRepGain", "level", _level);
checkBladeburnerAccess("getActionRepGain");
const action = getBladeburnerActionObject("getActionRepGain", type, name);
getActionRepGain:
(ctx: NetscriptContext) =>
(_type: unknown, _name: unknown, _level: unknown): number => {
const type = ctx.helper.string("type", _type);
const name = ctx.helper.string("name", _name);
const level = ctx.helper.number("level", _level);
checkBladeburnerAccess(ctx);
const action = getBladeburnerActionObject(ctx, type, name);
let rewardMultiplier;
if (level == null || isNaN(level)) {
rewardMultiplier = Math.pow(action.rewardFac, action.level - 1);
@ -185,204 +175,210 @@ export function NetscriptBladeburner(
return action.rankGain * rewardMultiplier * BitNodeMultipliers.BladeburnerRank;
},
getActionCountRemaining: function (_type: unknown, _name: unknown): number {
updateRam("getActionCountRemaining");
const type = helper.string("getActionCountRemaining", "type", _type);
const name = helper.string("getActionCountRemaining", "name", _name);
checkBladeburnerAccess("getActionCountRemaining");
getActionCountRemaining:
(ctx: NetscriptContext) =>
(_type: unknown, _name: unknown): number => {
const type = ctx.helper.string("type", _type);
const name = ctx.helper.string("name", _name);
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.getActionCountRemainingNetscriptFn(type, name, workerScript);
} catch (e: any) {
throw helper.makeRuntimeErrorMsg("bladeburner.getActionCountRemaining", e);
throw ctx.makeRuntimeErrorMsg(e);
}
},
getActionMaxLevel: function (_type: unknown, _name: unknown): number {
updateRam("getActionMaxLevel");
const type = helper.string("getActionMaxLevel", "type", _type);
const name = helper.string("getActionMaxLevel", "name", _name);
checkBladeburnerAccess("getActionMaxLevel");
const action = getBladeburnerActionObject("getActionMaxLevel", type, name);
getActionMaxLevel:
(ctx: NetscriptContext) =>
(_type: unknown, _name: unknown): number => {
const type = ctx.helper.string("type", _type);
const name = ctx.helper.string("name", _name);
checkBladeburnerAccess(ctx);
const action = getBladeburnerActionObject(ctx, type, name);
return action.maxLevel;
},
getActionCurrentLevel: function (_type: unknown, _name: unknown): number {
updateRam("getActionCurrentLevel");
const type = helper.string("getActionCurrentLevel", "type", _type);
const name = helper.string("getActionCurrentLevel", "name", _name);
checkBladeburnerAccess("getActionCurrentLevel");
const action = getBladeburnerActionObject("getActionCurrentLevel", type, name);
getActionCurrentLevel:
(ctx: NetscriptContext) =>
(_type: unknown, _name: unknown): number => {
const type = ctx.helper.string("type", _type);
const name = ctx.helper.string("name", _name);
checkBladeburnerAccess(ctx);
const action = getBladeburnerActionObject(ctx, type, name);
return action.level;
},
getActionAutolevel: function (_type: unknown, _name: unknown): boolean {
updateRam("getActionAutolevel");
const type = helper.string("getActionAutolevel", "type", _type);
const name = helper.string("getActionAutolevel", "name", _name);
checkBladeburnerAccess("getActionAutolevel");
const action = getBladeburnerActionObject("getActionCurrentLevel", type, name);
getActionAutolevel:
(ctx: NetscriptContext) =>
(_type: unknown, _name: unknown): boolean => {
const type = ctx.helper.string("type", _type);
const name = ctx.helper.string("name", _name);
checkBladeburnerAccess(ctx);
const action = getBladeburnerActionObject(ctx, type, name);
return action.autoLevel;
},
setActionAutolevel: function (_type: unknown, _name: unknown, _autoLevel: unknown = true): void {
updateRam("setActionAutolevel");
const type = helper.string("setActionAutolevel", "type", _type);
const name = helper.string("setActionAutolevel", "name", _name);
const autoLevel = helper.boolean(_autoLevel);
checkBladeburnerAccess("setActionAutolevel");
const action = getBladeburnerActionObject("setActionAutolevel", type, name);
setActionAutolevel:
(ctx: NetscriptContext) =>
(_type: unknown, _name: unknown, _autoLevel: unknown = true): void => {
const type = ctx.helper.string("type", _type);
const name = ctx.helper.string("name", _name);
const autoLevel = ctx.helper.boolean(_autoLevel);
checkBladeburnerAccess(ctx);
const action = getBladeburnerActionObject(ctx, type, name);
action.autoLevel = autoLevel;
},
setActionLevel: function (_type: unknown, _name: unknown, _level: unknown = 1): void {
updateRam("setActionLevel");
const type = helper.string("setActionLevel", "type", _type);
const name = helper.string("setActionLevel", "name", _name);
const level = helper.number("setActionLevel", "level", _level);
checkBladeburnerAccess("setActionLevel");
const action = getBladeburnerActionObject("setActionLevel", type, name);
setActionLevel:
(ctx: NetscriptContext) =>
(_type: unknown, _name: unknown, _level: unknown = 1): void => {
const type = ctx.helper.string("type", _type);
const name = ctx.helper.string("name", _name);
const level = ctx.helper.number("level", _level);
checkBladeburnerAccess(ctx);
const action = getBladeburnerActionObject(ctx, type, name);
if (level < 1 || level > action.maxLevel) {
throw helper.makeRuntimeErrorMsg(
"bladeburner.setActionLevel",
`Level must be between 1 and ${action.maxLevel}, is ${level}`,
);
ctx.helper.makeRuntimeErrorMsg(`Level must be between 1 and ${action.maxLevel}, is ${level}`);
}
action.level = level;
},
getRank: function (): number {
updateRam("getRank");
checkBladeburnerAccess("getRank");
getRank: (ctx: NetscriptContext) => (): number => {
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.rank;
},
getSkillPoints: function (): number {
updateRam("getSkillPoints");
checkBladeburnerAccess("getSkillPoints");
getSkillPoints: (ctx: NetscriptContext) => (): number => {
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.skillPoints;
},
getSkillLevel: function (_skillName: unknown): number {
updateRam("getSkillLevel");
const skillName = helper.string("getSkillLevel", "skillName", _skillName);
checkBladeburnerAccess("getSkillLevel");
getSkillLevel:
(ctx: NetscriptContext) =>
(_skillName: unknown): number => {
const skillName = ctx.helper.string("skillName", _skillName);
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.getSkillLevelNetscriptFn(skillName, workerScript);
} catch (e: any) {
throw helper.makeRuntimeErrorMsg("bladeburner.getSkillLevel", e);
throw ctx.makeRuntimeErrorMsg(e);
}
},
getSkillUpgradeCost: function (_skillName: unknown): number {
updateRam("getSkillUpgradeCost");
const skillName = helper.string("getSkillUpgradeCost", "skillName", _skillName);
checkBladeburnerAccess("getSkillUpgradeCost");
getSkillUpgradeCost:
(ctx: NetscriptContext) =>
(_skillName: unknown): number => {
const skillName = ctx.helper.string("skillName", _skillName);
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.getSkillUpgradeCostNetscriptFn(skillName, workerScript);
} catch (e: any) {
throw helper.makeRuntimeErrorMsg("bladeburner.getSkillUpgradeCost", e);
throw ctx.makeRuntimeErrorMsg(e);
}
},
upgradeSkill: function (_skillName: unknown): boolean {
updateRam("upgradeSkill");
const skillName = helper.string("upgradeSkill", "skillName", _skillName);
checkBladeburnerAccess("upgradeSkill");
upgradeSkill:
(ctx: NetscriptContext) =>
(_skillName: unknown): boolean => {
const skillName = ctx.helper.string("skillName", _skillName);
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.upgradeSkillNetscriptFn(skillName, workerScript);
} catch (e: any) {
throw helper.makeRuntimeErrorMsg("bladeburner.upgradeSkill", e);
throw ctx.makeRuntimeErrorMsg(e);
}
},
getTeamSize: function (_type: unknown, _name: unknown): number {
updateRam("getTeamSize");
const type = helper.string("getTeamSize", "type", _type);
const name = helper.string("getTeamSize", "name", _name);
checkBladeburnerAccess("getTeamSize");
getTeamSize:
(ctx: NetscriptContext) =>
(_type: unknown, _name: unknown): number => {
const type = ctx.helper.string("type", _type);
const name = ctx.helper.string("name", _name);
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.getTeamSizeNetscriptFn(type, name, workerScript);
} catch (e: any) {
throw helper.makeRuntimeErrorMsg("bladeburner.getTeamSize", e);
throw ctx.makeRuntimeErrorMsg(e);
}
},
setTeamSize: function (_type: unknown, _name: unknown, _size: unknown): number {
updateRam("setTeamSize");
const type = helper.string("setTeamSize", "type", _type);
const name = helper.string("setTeamSize", "name", _name);
const size = helper.number("setTeamSize", "size", _size);
checkBladeburnerAccess("setTeamSize");
setTeamSize:
(ctx: NetscriptContext) =>
(_type: unknown, _name: unknown, _size: unknown): number => {
const type = ctx.helper.string("type", _type);
const name = ctx.helper.string("name", _name);
const size = ctx.helper.number("size", _size);
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.setTeamSizeNetscriptFn(type, name, size, workerScript);
} catch (e: any) {
throw helper.makeRuntimeErrorMsg("bladeburner.setTeamSize", e);
throw ctx.makeRuntimeErrorMsg(e);
}
},
getCityEstimatedPopulation: function (_cityName: unknown): number {
updateRam("getCityEstimatedPopulation");
const cityName = helper.string("getCityEstimatedPopulation", "cityName", _cityName);
checkBladeburnerAccess("getCityEstimatedPopulation");
checkBladeburnerCity("getCityEstimatedPopulation", cityName);
getCityEstimatedPopulation:
(ctx: NetscriptContext) =>
(_cityName: unknown): number => {
const cityName = ctx.helper.string("cityName", _cityName);
checkBladeburnerAccess(ctx);
checkBladeburnerCity(ctx, cityName);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.cities[cityName].popEst;
},
getCityCommunities: function (_cityName: unknown): number {
updateRam("getCityCommunities");
const cityName = helper.string("getCityCommunities", "cityName", _cityName);
checkBladeburnerAccess("getCityCommunities");
checkBladeburnerCity("getCityCommunities", cityName);
getCityCommunities:
(ctx: NetscriptContext) =>
(_cityName: unknown): number => {
const cityName = ctx.helper.string("cityName", _cityName);
checkBladeburnerAccess(ctx);
checkBladeburnerCity(ctx, cityName);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.cities[cityName].comms;
},
getCityChaos: function (_cityName: unknown): number {
updateRam("getCityChaos");
const cityName = helper.string("getCityChaos", "cityName", _cityName);
checkBladeburnerAccess("getCityChaos");
checkBladeburnerCity("getCityChaos", cityName);
getCityChaos:
(ctx: NetscriptContext) =>
(_cityName: unknown): number => {
const cityName = ctx.helper.string("cityName", _cityName);
checkBladeburnerAccess(ctx);
checkBladeburnerCity(ctx, cityName);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.cities[cityName].chaos;
},
getCity: function (): string {
updateRam("getCity");
checkBladeburnerAccess("getCityChaos");
getCity: (ctx: NetscriptContext) => (): string => {
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.city;
},
switchCity: function (_cityName: unknown): boolean {
updateRam("switchCity");
const cityName = helper.string("switchCity", "cityName", _cityName);
checkBladeburnerAccess("switchCity");
checkBladeburnerCity("switchCity", cityName);
switchCity:
(ctx: NetscriptContext) =>
(_cityName: unknown): boolean => {
const cityName = ctx.helper.string("cityName", _cityName);
checkBladeburnerAccess(ctx);
checkBladeburnerCity(ctx, cityName);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
bladeburner.city = cityName;
return true;
},
getStamina: function (): [number, number] {
updateRam("getStamina");
checkBladeburnerAccess("getStamina");
getStamina: (ctx: NetscriptContext) => (): [number, number] => {
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return [bladeburner.stamina, bladeburner.maxStamina];
},
joinBladeburnerFaction: function (): boolean {
updateRam("joinBladeburnerFaction");
checkBladeburnerAccess("joinBladeburnerFaction", true);
joinBladeburnerFaction: (ctx: NetscriptContext) => (): boolean => {
checkBladeburnerAccess(ctx, true);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.joinBladeburnerFactionNetscriptFn(workerScript);
},
joinBladeburnerDivision: function (): boolean {
updateRam("joinBladeburnerDivision");
joinBladeburnerDivision: (ctx: NetscriptContext) => (): boolean => {
if (player.bitNodeN === 7 || player.sourceFileLvl(7) > 0) {
if (player.bitNodeN === 8) {
return false;
@ -396,22 +392,18 @@ export function NetscriptBladeburner(
player.agility >= 100
) {
player.bladeburner = new Bladeburner(player);
workerScript.log("joinBladeburnerDivision", () => "You have been accepted into the Bladeburner division");
ctx.log(() => "You have been accepted into the Bladeburner division");
return true;
} else {
workerScript.log(
"joinBladeburnerDivision",
() => "You do not meet the requirements for joining the Bladeburner division",
);
ctx.log(() => "You do not meet the requirements for joining the Bladeburner division");
return false;
}
}
return false;
},
getBonusTime: function (): number {
updateRam("getBonusTime");
checkBladeburnerAccess("getBonusTime");
getBonusTime: (ctx: NetscriptContext) => (): number => {
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return Math.round(bladeburner.storedCycles / 5) * 1000;

@ -1,43 +1,38 @@
import { INetscriptHelper } from "./INetscriptHelper";
import { WorkerScript } from "../Netscript/WorkerScript";
import { IPlayer } from "../PersonObjects/IPlayer";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { is2DArray } from "../utils/helpers/is2DArray";
import { CodingContract } from "../CodingContracts";
import { CodingAttemptOptions, CodingContract as ICodingContract } from "../ScriptEditor/NetscriptDefinitions";
import { InternalAPI, NetscriptContext } from "src/Netscript/APIWrapper";
export function NetscriptCodingContract(
player: IPlayer,
workerScript: WorkerScript,
helper: INetscriptHelper,
): ICodingContract {
const getCodingContract = function (func: string, hostname: string, filename: string): CodingContract {
const server = helper.getServer(hostname, func);
export function NetscriptCodingContract(player: IPlayer, workerScript: WorkerScript): InternalAPI<ICodingContract> {
const getCodingContract = function (
ctx: NetscriptContext,
func: string,
hostname: string,
filename: string,
): CodingContract {
const server = ctx.helper.getServer(hostname);
const contract = server.getContract(filename);
if (contract == null) {
throw helper.makeRuntimeErrorMsg(
`codingcontract.${func}`,
`Cannot find contract '${filename}' on server '${hostname}'`,
);
throw ctx.makeRuntimeErrorMsg(`Cannot find contract '${filename}' on server '${hostname}'`);
}
return contract;
};
const updateRam = (funcName: string): void =>
helper.updateDynamicRam(funcName, getRamCost(player, "codingcontract", funcName));
return {
attempt: function (
attempt:
(ctx: NetscriptContext) =>
(
answer: any,
_filename: unknown,
_hostname: unknown = workerScript.hostname,
{ returnReward }: CodingAttemptOptions = { returnReward: false },
): boolean | string {
updateRam("attempt");
const filename = helper.string("attempt", "filename", _filename);
const hostname = helper.string("attempt", "hostname", _hostname);
const contract = getCodingContract("attempt", hostname, filename);
): boolean | string => {
const filename = ctx.helper.string("filename", _filename);
const hostname = ctx.helper.string("hostname", _hostname);
const contract = getCodingContract(ctx, "attempt", hostname, filename);
// Convert answer to string. If the answer is a 2D array, then we have to
// manually add brackets for the inner arrays
@ -55,26 +50,19 @@ export function NetscriptCodingContract(
const creward = contract.reward;
if (creward === null) throw new Error("Somehow solved a contract that didn't have a reward");
const serv = helper.getServer(hostname, "codingcontract.attempt");
const serv = ctx.helper.getServer(hostname);
if (contract.isSolution(answer)) {
const reward = player.gainCodingContractReward(creward, contract.getDifficulty());
workerScript.log(
"codingcontract.attempt",
() => `Successfully completed Coding Contract '${filename}'. Reward: ${reward}`,
);
ctx.log(() => `Successfully completed Coding Contract '${filename}'. Reward: ${reward}`);
serv.removeContract(filename);
return returnReward ? reward : true;
} else {
++contract.tries;
if (contract.tries >= contract.getMaxNumTries()) {
workerScript.log(
"codingcontract.attempt",
() => `Coding Contract attempt '${filename}' failed. Contract is now self-destructing`,
);
ctx.log(() => `Coding Contract attempt '${filename}' failed. Contract is now self-destructing`);
serv.removeContract(filename);
} else {
workerScript.log(
"codingcontract.attempt",
ctx.log(
() =>
`Coding Contract attempt '${filename}' failed. ${
contract.getMaxNumTries() - contract.tries
@ -85,18 +73,20 @@ export function NetscriptCodingContract(
return returnReward ? "" : false;
}
},
getContractType: function (_filename: unknown, _hostname: unknown = workerScript.hostname): string {
updateRam("getContractType");
const filename = helper.string("getContractType", "filename", _filename);
const hostname = helper.string("getContractType", "hostname", _hostname);
const contract = getCodingContract("getContractType", hostname, filename);
getContractType:
(ctx: NetscriptContext) =>
(_filename: unknown, _hostname: unknown = workerScript.hostname): string => {
const filename = ctx.helper.string("filename", _filename);
const hostname = ctx.helper.string("hostname", _hostname);
const contract = getCodingContract(ctx, "getContractType", hostname, filename);
return contract.getType();
},
getData: function (_filename: unknown, _hostname: unknown = workerScript.hostname): any {
updateRam("getData");
const filename = helper.string("getContractType", "filename", _filename);
const hostname = helper.string("getContractType", "hostname", _hostname);
const contract = getCodingContract("getData", hostname, filename);
getData:
(ctx: NetscriptContext) =>
(_filename: unknown, _hostname: unknown = workerScript.hostname): any => {
const filename = ctx.helper.string("filename", _filename);
const hostname = ctx.helper.string("hostname", _hostname);
const contract = getCodingContract(ctx, "getData", hostname, filename);
const data = contract.getData();
if (data.constructor === Array) {
// For two dimensional arrays, we have to copy the internal arrays using
@ -114,18 +104,20 @@ export function NetscriptCodingContract(
return data;
}
},
getDescription: function (_filename: unknown, _hostname: unknown = workerScript.hostname): string {
updateRam("getDescription");
const filename = helper.string("getDescription", "filename", _filename);
const hostname = helper.string("getDescription", "hostname", _hostname);
const contract = getCodingContract("getDescription", hostname, filename);
getDescription:
(ctx: NetscriptContext) =>
(_filename: unknown, _hostname: unknown = workerScript.hostname): string => {
const filename = ctx.helper.string("filename", _filename);
const hostname = ctx.helper.string("hostname", _hostname);
const contract = getCodingContract(ctx, "getDescription", hostname, filename);
return contract.getDescription();
},
getNumTriesRemaining: function (_filename: unknown, _hostname: unknown = workerScript.hostname): number {
updateRam("getNumTriesRemaining");
const filename = helper.string("getNumTriesRemaining", "filename", _filename);
const hostname = helper.string("getNumTriesRemaining", "hostname", _hostname);
const contract = getCodingContract("getNumTriesRemaining", hostname, filename);
getNumTriesRemaining:
(ctx: NetscriptContext) =>
(_filename: unknown, _hostname: unknown = workerScript.hostname): number => {
const filename = ctx.helper.string("filename", _filename);
const hostname = ctx.helper.string("hostname", _hostname);
const contract = getCodingContract(ctx, "getNumTriesRemaining", hostname, filename);
return contract.getMaxNumTries() - contract.tries;
},
};

File diff suppressed because it is too large Load Diff

@ -1,8 +1,6 @@
import { FactionNames } from "../Faction/data/FactionNames";
import { GangConstants } from "../Gang/data/Constants";
import { INetscriptHelper } from "./INetscriptHelper";
import { IPlayer } from "../PersonObjects/IPlayer";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { Gang } from "../Gang/Gang";
import { AllGangs } from "../Gang/AllGangs";
import { GangMemberTasks } from "../Gang/GangMemberTasks";
@ -20,39 +18,39 @@ import {
EquipmentStats,
GangTaskStats,
} from "../ScriptEditor/NetscriptDefinitions";
import { InternalAPI, NetscriptContext } from "../Netscript/APIWrapper";
export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): IGang {
const checkGangApiAccess = function (func: string): void {
export function NetscriptGang(player: IPlayer, workerScript: WorkerScript): InternalAPI<IGang> {
const checkGangApiAccess = function (ctx: NetscriptContext): void {
const gang = player.gang;
if (gang === null) throw new Error("Must have joined gang");
const hasAccess = gang instanceof Gang;
if (!hasAccess) {
throw helper.makeRuntimeErrorMsg(`gang.${func}`, `You do not currently have a Gang`);
throw ctx.makeRuntimeErrorMsg(`You do not currently have a Gang`);
}
};
const getGangMember = function (func: string, name: string): GangMember {
const getGangMember = function (ctx: NetscriptContext, name: string): GangMember {
const gang = player.gang;
if (gang === null) throw new Error("Must have joined gang");
for (const member of gang.members) if (member.name === name) return member;
throw helper.makeRuntimeErrorMsg(`gang.${func}`, `Invalid gang member: '${name}'`);
throw ctx.makeRuntimeErrorMsg(`Invalid gang member: '${name}'`);
};
const getGangTask = function (func: string, name: string): GangMemberTask {
const getGangTask = function (ctx: NetscriptContext, name: string): GangMemberTask {
const task = GangMemberTasks[name];
if (!task) {
throw helper.makeRuntimeErrorMsg(`gang.${func}`, `Invalid task: '${name}'`);
throw ctx.makeRuntimeErrorMsg(`Invalid task: '${name}'`);
}
return task;
};
const updateRam = (funcName: string): void => helper.updateDynamicRam(funcName, getRamCost(player, "gang", funcName));
return {
createGang: function (_faction: unknown): boolean {
updateRam("createGang");
const faction = helper.string("createGang", "faction", _faction);
createGang:
(ctx: NetscriptContext) =>
(_faction: unknown): boolean => {
const faction = ctx.helper.string("faction", _faction);
// this list is copied from Faction/ui/Root.tsx
if (!player.canAccessGang() || !GangConstants.Names.includes(faction)) return false;
@ -63,20 +61,17 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
player.startGang(faction, isHacking);
return true;
},
inGang: function (): boolean {
updateRam("inGang");
inGang: () => (): boolean => {
return player.inGang();
},
getMemberNames: function (): string[] {
updateRam("getMemberNames");
checkGangApiAccess("getMemberNames");
getMemberNames: (ctx: NetscriptContext) => (): string[] => {
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
return gang.members.map((member) => member.name);
},
getGangInformation: function (): GangGenInfo {
updateRam("getGangInformation");
checkGangApiAccess("getGangInformation");
getGangInformation: (ctx: NetscriptContext) => (): GangGenInfo => {
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
return {
@ -94,9 +89,8 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
wantedPenalty: gang.getWantedPenalty(),
};
},
getOtherGangInformation: function (): GangOtherInfo {
updateRam("getOtherGangInformation");
checkGangApiAccess("getOtherGangInformation");
getOtherGangInformation: (ctx: NetscriptContext) => (): GangOtherInfo => {
checkGangApiAccess(ctx);
const cpy: any = {};
for (const gang of Object.keys(AllGangs)) {
cpy[gang] = Object.assign({}, AllGangs[gang]);
@ -104,13 +98,14 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
return cpy;
},
getMemberInformation: function (_memberName: unknown): GangMemberInfo {
updateRam("getMemberInformation");
const memberName = helper.string("getMemberInformation", "memberName", _memberName);
checkGangApiAccess("getMemberInformation");
getMemberInformation:
(ctx: NetscriptContext) =>
(_memberName: unknown): GangMemberInfo => {
const memberName = ctx.helper.string("memberName", _memberName);
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const member = getGangMember("getMemberInformation", memberName);
const member = getGangMember(ctx, memberName);
return {
name: member.name,
task: member.task,
@ -158,17 +153,17 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
moneyGain: member.calculateMoneyGain(gang),
};
},
canRecruitMember: function (): boolean {
updateRam("canRecruitMember");
checkGangApiAccess("canRecruitMember");
canRecruitMember: (ctx: NetscriptContext) => (): boolean => {
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
return gang.canRecruitMember();
},
recruitMember: function (_memberName: unknown): boolean {
updateRam("recruitMember");
const memberName = helper.string("recruitMember", "memberName", _memberName);
checkGangApiAccess("recruitMember");
recruitMember:
(ctx: NetscriptContext) =>
(_memberName: unknown): boolean => {
const memberName = ctx.helper.string("memberName", _memberName);
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const recruited = gang.recruitMember(memberName);
@ -180,21 +175,21 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
return recruited;
},
getTaskNames: function (): string[] {
updateRam("getTaskNames");
checkGangApiAccess("getTaskNames");
getTaskNames: (ctx: NetscriptContext) => (): string[] => {
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const tasks = gang.getAllTaskNames();
tasks.unshift("Unassigned");
return tasks;
},
setMemberTask: function (_memberName: unknown, _taskName: unknown): boolean {
updateRam("setMemberTask");
const memberName = helper.string("setMemberTask", "memberName", _memberName);
const taskName = helper.string("setMemberTask", "taskName", _taskName);
checkGangApiAccess("setMemberTask");
const member = getGangMember("setMemberTask", memberName);
setMemberTask:
(ctx: NetscriptContext) =>
(_memberName: unknown, _taskName: unknown): boolean => {
const memberName = ctx.helper.string("memberName", _memberName);
const taskName = ctx.helper.string("taskName", _taskName);
checkGangApiAccess(ctx);
const member = getGangMember(ctx, memberName);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
if (!gang.getAllTaskNames().includes(taskName)) {
@ -214,63 +209,68 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
} else {
workerScript.log(
"gang.setMemberTask",
() => `Failed to assign Gang Member '${memberName}' to '${taskName}' task. '${memberName}' is now Unassigned`,
() =>
`Failed to assign Gang Member '${memberName}' to '${taskName}' task. '${memberName}' is now Unassigned`,
);
}
return success;
},
getTaskStats: function (_taskName: unknown): GangTaskStats {
updateRam("getTaskStats");
const taskName = helper.string("getTaskStats", "taskName", _taskName);
checkGangApiAccess("getTaskStats");
const task = getGangTask("getTaskStats", taskName);
getTaskStats:
(ctx: NetscriptContext) =>
(_taskName: unknown): GangTaskStats => {
const taskName = ctx.helper.string("taskName", _taskName);
checkGangApiAccess(ctx);
const task = getGangTask(ctx, taskName);
const copy = Object.assign({}, task);
copy.territory = Object.assign({}, task.territory);
return copy;
},
getEquipmentNames: function (): string[] {
updateRam("getEquipmentNames");
checkGangApiAccess("getEquipmentNames");
getEquipmentNames: (ctx: NetscriptContext) => (): string[] => {
checkGangApiAccess(ctx);
return Object.keys(GangMemberUpgrades);
},
getEquipmentCost: function (_equipName: any): number {
updateRam("getEquipmentCost");
const equipName = helper.string("getEquipmentCost", "equipName", _equipName);
checkGangApiAccess("getEquipmentCost");
getEquipmentCost:
(ctx: NetscriptContext) =>
(_equipName: any): number => {
const equipName = ctx.helper.string("equipName", _equipName);
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const upg = GangMemberUpgrades[equipName];
if (upg === null) return Infinity;
return gang.getUpgradeCost(upg);
},
getEquipmentType: function (_equipName: unknown): string {
updateRam("getEquipmentType");
const equipName = helper.string("getEquipmentType", "equipName", _equipName);
checkGangApiAccess("getEquipmentType");
getEquipmentType:
(ctx: NetscriptContext) =>
(_equipName: unknown): string => {
const equipName = ctx.helper.string("equipName", _equipName);
checkGangApiAccess(ctx);
const upg = GangMemberUpgrades[equipName];
if (upg == null) return "";
return upg.getType();
},
getEquipmentStats: function (_equipName: unknown): EquipmentStats {
updateRam("getEquipmentStats");
const equipName = helper.string("getEquipmentStats", "equipName", _equipName);
checkGangApiAccess("getEquipmentStats");
getEquipmentStats:
(ctx: NetscriptContext) =>
(_equipName: unknown): EquipmentStats => {
const equipName = ctx.helper.string("equipName", _equipName);
checkGangApiAccess(ctx);
const equipment = GangMemberUpgrades[equipName];
if (!equipment) {
throw helper.makeRuntimeErrorMsg("getEquipmentStats", `Invalid equipment: ${equipName}`);
throw ctx.makeRuntimeErrorMsg(`Invalid equipment: ${equipName}`);
}
const typecheck: EquipmentStats = equipment.mults;
return Object.assign({}, typecheck) as any;
},
purchaseEquipment: function (_memberName: unknown, _equipName: unknown): boolean {
updateRam("purchaseEquipment");
const memberName = helper.string("purchaseEquipment", "memberName", _memberName);
const equipName = helper.string("purchaseEquipment", "equipName", _equipName);
checkGangApiAccess("purchaseEquipment");
purchaseEquipment:
(ctx: NetscriptContext) =>
(_memberName: unknown, _equipName: unknown): boolean => {
const memberName = ctx.helper.string("memberName", _memberName);
const equipName = ctx.helper.string("equipName", _equipName);
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const member = getGangMember("purchaseEquipment", memberName);
const member = getGangMember(ctx, memberName);
const equipment = GangMemberUpgrades[equipName];
if (!equipment) return false;
const res = member.buyUpgrade(equipment, player, gang);
@ -285,33 +285,36 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
return res;
},
ascendMember: function (_memberName: unknown): GangMemberAscension | undefined {
updateRam("ascendMember");
const memberName = helper.string("ascendMember", "memberName", _memberName);
checkGangApiAccess("ascendMember");
ascendMember:
(ctx: NetscriptContext) =>
(_memberName: unknown): GangMemberAscension | undefined => {
const memberName = ctx.helper.string("memberName", _memberName);
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const member = getGangMember("ascendMember", memberName);
const member = getGangMember(ctx, memberName);
if (!member.canAscend()) return;
return gang.ascendMember(member, workerScript);
},
getAscensionResult: function (_memberName: unknown): GangMemberAscension | undefined {
updateRam("getAscensionResult");
const memberName = helper.string("getAscensionResult", "memberName", _memberName);
checkGangApiAccess("getAscensionResult");
getAscensionResult:
(ctx: NetscriptContext) =>
(_memberName: unknown): GangMemberAscension | undefined => {
const memberName = ctx.helper.string("memberName", _memberName);
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const member = getGangMember("getAscensionResult", memberName);
const member = getGangMember(ctx, memberName);
if (!member.canAscend()) return;
return {
respect: member.earnedRespect,
...member.getAscensionResults(),
};
},
setTerritoryWarfare: function (_engage: unknown): void {
updateRam("setTerritoryWarfare");
const engage = helper.boolean(_engage);
checkGangApiAccess("setTerritoryWarfare");
setTerritoryWarfare:
(ctx: NetscriptContext) =>
(_engage: unknown): void => {
const engage = ctx.helper.boolean(_engage);
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
if (engage) {
@ -322,14 +325,15 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
workerScript.log("gang.setTerritoryWarfare", () => "Disengaging in Gang Territory Warfare");
}
},
getChanceToWinClash: function (_otherGang: unknown): number {
updateRam("getChanceToWinClash");
const otherGang = helper.string("getChanceToWinClash", "otherGang", _otherGang);
checkGangApiAccess("getChanceToWinClash");
getChanceToWinClash:
(ctx: NetscriptContext) =>
(_otherGang: unknown): number => {
const otherGang = ctx.helper.string("otherGang", _otherGang);
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
if (AllGangs[otherGang] == null) {
throw helper.makeRuntimeErrorMsg(`gang.getChanceToWinClash`, `Invalid gang: ${otherGang}`);
throw ctx.makeRuntimeErrorMsg(`Invalid gang: ${otherGang}`);
}
const playerPower = AllGangs[gang.facName].power;
@ -337,9 +341,8 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
return playerPower / (otherPower + playerPower);
},
getBonusTime: function (): number {
updateRam("getBonusTime");
checkGangApiAccess("getBonusTime");
getBonusTime: (ctx: NetscriptContext) => (): number => {
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
return Math.round(gang.storedCycles / 5) * 1000;

@ -1,88 +1,81 @@
import { InternalAPI, NetscriptContext } from "../Netscript/APIWrapper";
import { StaticAugmentations } from "../Augmentation/StaticAugmentations";
import { hasAugmentationPrereqs } from "../Faction/FactionHelpers";
import { CityName } from "../Locations/data/CityNames";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { WorkerScript } from "../Netscript/WorkerScript";
import { GraftableAugmentation } from "../PersonObjects/Grafting/GraftableAugmentation";
import { getGraftingAvailableAugs, calculateGraftingTimeWithBonus } from "../PersonObjects/Grafting/GraftingHelpers";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Grafting as IGrafting } from "../ScriptEditor/NetscriptDefinitions";
import { Router } from "../ui/GameRoot";
import { INetscriptHelper } from "./INetscriptHelper";
export function NetscriptGrafting(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): IGrafting {
const checkGraftingAPIAccess = (func: string): void => {
export function NetscriptGrafting(player: IPlayer): InternalAPI<IGrafting> {
const checkGraftingAPIAccess = (ctx: NetscriptContext): void => {
if (!player.canAccessGrafting()) {
throw helper.makeRuntimeErrorMsg(
`grafting.${func}`,
throw ctx.makeRuntimeErrorMsg(
"You do not currently have access to the Grafting API. This is either because you are not in BitNode 10 or because you do not have Source-File 10",
);
}
};
const updateRam = (funcName: string): void =>
helper.updateDynamicRam(funcName, getRamCost(player, "grafting", funcName));
return {
getAugmentationGraftPrice: (_augName: unknown): number => {
updateRam("getAugmentationGraftPrice");
const augName = helper.string("getAugmentationGraftPrice", "augName", _augName);
checkGraftingAPIAccess("getAugmentationGraftPrice");
getAugmentationGraftPrice:
(ctx: NetscriptContext) =>
(_augName: unknown): number => {
const augName = ctx.helper.string("augName", _augName);
checkGraftingAPIAccess(ctx);
if (!getGraftingAvailableAugs(player).includes(augName) || !StaticAugmentations.hasOwnProperty(augName)) {
throw helper.makeRuntimeErrorMsg("grafting.getAugmentationGraftPrice", `Invalid aug: ${augName}`);
throw ctx.makeRuntimeErrorMsg(`Invalid aug: ${augName}`);
}
const graftableAug = new GraftableAugmentation(StaticAugmentations[augName]);
return graftableAug.cost;
},
getAugmentationGraftTime: (_augName: string): number => {
updateRam("getAugmentationGraftTime");
const augName = helper.string("getAugmentationGraftTime", "augName", _augName);
checkGraftingAPIAccess("getAugmentationGraftTime");
getAugmentationGraftTime:
(ctx: NetscriptContext) =>
(_augName: string): number => {
const augName = ctx.helper.string("augName", _augName);
checkGraftingAPIAccess(ctx);
if (!getGraftingAvailableAugs(player).includes(augName) || !StaticAugmentations.hasOwnProperty(augName)) {
throw helper.makeRuntimeErrorMsg("grafting.getAugmentationGraftTime", `Invalid aug: ${augName}`);
throw ctx.makeRuntimeErrorMsg(`Invalid aug: ${augName}`);
}
const graftableAug = new GraftableAugmentation(StaticAugmentations[augName]);
return calculateGraftingTimeWithBonus(player, graftableAug);
},
getGraftableAugmentations: (): string[] => {
updateRam("getGraftableAugmentations");
checkGraftingAPIAccess("getGraftableAugmentations");
getGraftableAugmentations: (ctx: NetscriptContext) => (): string[] => {
checkGraftingAPIAccess(ctx);
const graftableAugs = getGraftingAvailableAugs(player);
return graftableAugs;
},
graftAugmentation: (_augName: string, _focus: unknown = true): boolean => {
updateRam("graftAugmentation");
const augName = helper.string("graftAugmentation", "augName", _augName);
const focus = helper.boolean(_focus);
checkGraftingAPIAccess("graftAugmentation");
graftAugmentation:
(ctx: NetscriptContext) =>
(_augName: string, _focus: unknown = true): boolean => {
const augName = ctx.helper.string("augName", _augName);
const focus = ctx.helper.boolean(_focus);
checkGraftingAPIAccess(ctx);
if (player.city !== CityName.NewTokyo) {
throw helper.makeRuntimeErrorMsg(
"grafting.graftAugmentation",
"You must be in New Tokyo to begin grafting an Augmentation.",
);
throw ctx.makeRuntimeErrorMsg("You must be in New Tokyo to begin grafting an Augmentation.");
}
if (!getGraftingAvailableAugs(player).includes(augName) || !StaticAugmentations.hasOwnProperty(augName)) {
workerScript.log("grafting.graftAugmentation", () => `Invalid aug: ${augName}`);
ctx.log(() => `Invalid aug: ${augName}`);
return false;
}
const wasFocusing = player.focus;
if (player.isWorking) {
const txt = player.singularityStopWork();
workerScript.log("graftAugmentation", () => txt);
ctx.log(() => txt);
}
const craftableAug = new GraftableAugmentation(StaticAugmentations[augName]);
if (player.money < craftableAug.cost) {
workerScript.log("grafting.graftAugmentation", () => `You don't have enough money to craft ${augName}`);
ctx.log(() => `You don't have enough money to craft ${augName}`);
return false;
}
if (!hasAugmentationPrereqs(craftableAug.augmentation)) {
workerScript.log("grafting.graftAugmentation", () => `You don't have the pre-requisites for ${augName}`);
ctx.log(() => `You don't have the pre-requisites for ${augName}`);
return false;
}
@ -97,7 +90,7 @@ export function NetscriptGrafting(player: IPlayer, workerScript: WorkerScript, h
Router.toTerminal();
}
workerScript.log("grafting.graftAugmentation", () => `Began grafting Augmentation ${augName}.`);
ctx.log(() => `Began grafting Augmentation ${augName}.`);
return true;
},
};

@ -1,4 +1,3 @@
import { INetscriptHelper } from "./INetscriptHelper";
import { IPlayer } from "../PersonObjects/IPlayer";
import { WorkerScript } from "../Netscript/WorkerScript";
import { HacknetServerConstants } from "../Hacknet/data/Constants";
@ -21,12 +20,13 @@ import { HashUpgrade } from "../Hacknet/HashUpgrade";
import { GetServer } from "../Server/AllServers";
import { Hacknet as IHacknet, NodeStats } from "../ScriptEditor/NetscriptDefinitions";
import { InternalAPI, NetscriptContext } from "../Netscript/APIWrapper";
export function NetscriptHacknet(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): IHacknet {
export function NetscriptHacknet(player: IPlayer, workerScript: WorkerScript): InternalAPI<IHacknet> {
// Utility function to get Hacknet Node object
const getHacknetNode = function (i: number, callingFn = ""): HacknetNode | HacknetServer {
const getHacknetNode = function (ctx: NetscriptContext, i: number): HacknetNode | HacknetServer {
if (i < 0 || i >= player.hacknetNodes.length) {
throw helper.makeRuntimeErrorMsg(callingFn, "Index specified for Hacknet Node is out-of-bounds: " + i);
throw ctx.makeRuntimeErrorMsg("Index specified for Hacknet Node is out-of-bounds: " + i);
}
if (hasHacknetServers(player)) {
@ -35,8 +35,7 @@ export function NetscriptHacknet(player: IPlayer, workerScript: WorkerScript, he
const hserver = GetServer(hi);
if (!(hserver instanceof HacknetServer)) throw new Error("hacknet server was not actually hacknet server");
if (hserver == null) {
throw helper.makeRuntimeErrorMsg(
callingFn,
throw ctx.makeRuntimeErrorMsg(
`Could not get Hacknet Server for index ${i}. This is probably a bug, please report to game dev`,
);
}
@ -50,28 +49,30 @@ export function NetscriptHacknet(player: IPlayer, workerScript: WorkerScript, he
};
return {
numNodes: function (): number {
numNodes: () => (): number => {
return player.hacknetNodes.length;
},
maxNumNodes: function (): number {
maxNumNodes: () => (): number => {
if (hasHacknetServers(player)) {
return HacknetServerConstants.MaxServers;
}
return Infinity;
},
purchaseNode: function (): number {
purchaseNode: () => (): number => {
return purchaseHacknet(player);
},
getPurchaseNodeCost: function (): number {
getPurchaseNodeCost: () => (): number => {
if (hasHacknetServers(player)) {
return getCostOfNextHacknetServer(player);
} else {
return getCostOfNextHacknetNode(player);
}
},
getNodeStats: function (_i: unknown): NodeStats {
const i = helper.number("getNodeStats", "i", _i);
const node = getHacknetNode(i, "getNodeStats");
getNodeStats:
(ctx: NetscriptContext) =>
(_i: unknown): NodeStats => {
const i = ctx.helper.number("i", _i);
const node = getHacknetNode(ctx, i);
const hasUpgraded = hasHacknetServers(player);
const res: any = {
name: node instanceof HacknetServer ? node.hostname : node.name,
@ -91,31 +92,39 @@ export function NetscriptHacknet(player: IPlayer, workerScript: WorkerScript, he
return res;
},
upgradeLevel: function (_i: unknown, _n: unknown = 1): boolean {
const i = helper.number("upgradeLevel", "i", _i);
const n = helper.number("upgradeLevel", "n", _n);
const node = getHacknetNode(i, "upgradeLevel");
upgradeLevel:
(ctx: NetscriptContext) =>
(_i: unknown, _n: unknown = 1): boolean => {
const i = ctx.helper.number("i", _i);
const n = ctx.helper.number("n", _n);
const node = getHacknetNode(ctx, i);
return purchaseLevelUpgrade(player, node, n);
},
upgradeRam: function (_i: unknown, _n: unknown = 1): boolean {
const i = helper.number("upgradeRam", "i", _i);
const n = helper.number("upgradeRam", "n", _n);
const node = getHacknetNode(i, "upgradeRam");
upgradeRam:
(ctx: NetscriptContext) =>
(_i: unknown, _n: unknown = 1): boolean => {
const i = ctx.helper.number("i", _i);
const n = ctx.helper.number("n", _n);
const node = getHacknetNode(ctx, i);
return purchaseRamUpgrade(player, node, n);
},
upgradeCore: function (_i: unknown, _n: unknown = 1): boolean {
const i = helper.number("upgradeCore", "i", _i);
const n = helper.number("upgradeCore", "n", _n);
const node = getHacknetNode(i, "upgradeCore");
upgradeCore:
(ctx: NetscriptContext) =>
(_i: unknown, _n: unknown = 1): boolean => {
const i = ctx.helper.number("i", _i);
const n = ctx.helper.number("n", _n);
const node = getHacknetNode(ctx, i);
return purchaseCoreUpgrade(player, node, n);
},
upgradeCache: function (_i: unknown, _n: unknown = 1): boolean {
const i = helper.number("upgradeCache", "i", _i);
const n = helper.number("upgradeCache", "n", _n);
upgradeCache:
(ctx: NetscriptContext) =>
(_i: unknown, _n: unknown = 1): boolean => {
const i = ctx.helper.number("i", _i);
const n = ctx.helper.number("n", _n);
if (!hasHacknetServers(player)) {
return false;
}
const node = getHacknetNode(i, "upgradeCache");
const node = getHacknetNode(ctx, i);
if (!(node instanceof HacknetServer)) {
workerScript.log("hacknet.upgradeCache", () => "Can only be called on hacknet servers");
return false;
@ -126,86 +135,100 @@ export function NetscriptHacknet(player: IPlayer, workerScript: WorkerScript, he
}
return res;
},
getLevelUpgradeCost: function (_i: unknown, _n: unknown = 1): number {
const i = helper.number("getLevelUpgradeCost", "i", _i);
const n = helper.number("getLevelUpgradeCost", "n", _n);
const node = getHacknetNode(i, "upgradeLevel");
getLevelUpgradeCost:
(ctx: NetscriptContext) =>
(_i: unknown, _n: unknown = 1): number => {
const i = ctx.helper.number("i", _i);
const n = ctx.helper.number("n", _n);
const node = getHacknetNode(ctx, i);
return node.calculateLevelUpgradeCost(n, player.hacknet_node_level_cost_mult);
},
getRamUpgradeCost: function (_i: unknown, _n: unknown = 1): number {
const i = helper.number("getRamUpgradeCost", "i", _i);
const n = helper.number("getRamUpgradeCost", "n", _n);
const node = getHacknetNode(i, "upgradeRam");
getRamUpgradeCost:
(ctx: NetscriptContext) =>
(_i: unknown, _n: unknown = 1): number => {
const i = ctx.helper.number("i", _i);
const n = ctx.helper.number("n", _n);
const node = getHacknetNode(ctx, i);
return node.calculateRamUpgradeCost(n, player.hacknet_node_ram_cost_mult);
},
getCoreUpgradeCost: function (_i: unknown, _n: unknown = 1): number {
const i = helper.number("getCoreUpgradeCost", "i", _i);
const n = helper.number("getCoreUpgradeCost", "n", _n);
const node = getHacknetNode(i, "upgradeCore");
getCoreUpgradeCost:
(ctx: NetscriptContext) =>
(_i: unknown, _n: unknown = 1): number => {
const i = ctx.helper.number("i", _i);
const n = ctx.helper.number("n", _n);
const node = getHacknetNode(ctx, i);
return node.calculateCoreUpgradeCost(n, player.hacknet_node_core_cost_mult);
},
getCacheUpgradeCost: function (_i: unknown, _n: unknown = 1): number {
const i = helper.number("getCacheUpgradeCost", "i", _i);
const n = helper.number("getCacheUpgradeCost", "n", _n);
getCacheUpgradeCost:
(ctx: NetscriptContext) =>
(_i: unknown, _n: unknown = 1): number => {
const i = ctx.helper.number("i", _i);
const n = ctx.helper.number("n", _n);
if (!hasHacknetServers(player)) {
return Infinity;
}
const node = getHacknetNode(i, "upgradeCache");
const node = getHacknetNode(ctx, i);
if (!(node instanceof HacknetServer)) {
workerScript.log("hacknet.getCacheUpgradeCost", () => "Can only be called on hacknet servers");
return -1;
}
return node.calculateCacheUpgradeCost(n);
},
numHashes: function (): number {
numHashes: () => (): number => {
if (!hasHacknetServers(player)) {
return 0;
}
return player.hashManager.hashes;
},
hashCapacity: function (): number {
hashCapacity: () => (): number => {
if (!hasHacknetServers(player)) {
return 0;
}
return player.hashManager.capacity;
},
hashCost: function (_upgName: unknown): number {
const upgName = helper.string("hashCost", "upgName", _upgName);
hashCost:
(ctx: NetscriptContext) =>
(_upgName: unknown): number => {
const upgName = ctx.helper.string("upgName", _upgName);
if (!hasHacknetServers(player)) {
return Infinity;
}
return player.hashManager.getUpgradeCost(upgName);
},
spendHashes: function (_upgName: unknown, _upgTarget: unknown = ""): boolean {
const upgName = helper.string("spendHashes", "upgName", _upgName);
const upgTarget = helper.string("spendHashes", "upgTarget", _upgTarget);
spendHashes:
(ctx: NetscriptContext) =>
(_upgName: unknown, _upgTarget: unknown = ""): boolean => {
const upgName = ctx.helper.string("upgName", _upgName);
const upgTarget = ctx.helper.string("upgTarget", _upgTarget);
if (!hasHacknetServers(player)) {
return false;
}
return purchaseHashUpgrade(player, upgName, upgTarget);
},
getHashUpgrades: function (): string[] {
getHashUpgrades: () => (): string[] => {
if (!hasHacknetServers(player)) {
return [];
}
return Object.values(HashUpgrades).map((upgrade: HashUpgrade) => upgrade.name);
},
getHashUpgradeLevel: function (_upgName: unknown): number {
const upgName = helper.string("getHashUpgradeLevel", "upgName", _upgName);
getHashUpgradeLevel:
(ctx: NetscriptContext) =>
(_upgName: unknown): number => {
const upgName = ctx.helper.string("upgName", _upgName);
const level = player.hashManager.upgrades[upgName];
if (level === undefined) {
throw helper.makeRuntimeErrorMsg("hacknet.hashUpgradeLevel", `Invalid Hash Upgrade: ${upgName}`);
throw ctx.makeRuntimeErrorMsg(`Invalid Hash Upgrade: ${upgName}`);
}
return level;
},
getStudyMult: function (): number {
getStudyMult: () => (): number => {
if (!hasHacknetServers(player)) {
return 1;
}
return player.hashManager.getStudyMult();
},
getTrainingMult: function (): number {
getTrainingMult: () => (): number => {
if (!hasHacknetServers(player)) {
return 1;
}

@ -84,7 +84,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
if (script.filename === cbScript) {
const ramUsage = script.ramUsage;
const ramAvailable = home.maxRam - home.ramUsed;
if (ramUsage > ramAvailable) {
if (ramUsage > ramAvailable + 0.001) {
return; // Not enough RAM
}
const runningScriptObj = new RunningScript(script, []); // No args
@ -123,7 +123,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
_ctx.helper.checkSingularityAccess();
const augName = _ctx.helper.string("augName", _augName);
const aug = getAugmentation(_ctx, augName);
return [aug.getCost(player).moneyCost, aug.getCost(player).repCost];
const costs = aug.getCost(player);
return [costs.repCost, costs.moneyCost];
},
getAugmentationPrereq: (_ctx: NetscriptContext) =>
function (_augName: unknown): string[] {

@ -1,9 +1,6 @@
import { INetscriptHelper } from "./INetscriptHelper";
import { IPlayer } from "../PersonObjects/IPlayer";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { FactionWorkType } from "../Faction/FactionWorkTypeEnum";
import { SleeveTaskType } from "../PersonObjects/Sleeve/SleeveTaskTypesEnum";
import { WorkerScript } from "../Netscript/WorkerScript";
import { findSleevePurchasableAugs } from "../PersonObjects/Sleeve/SleeveHelpers";
import { StaticAugmentations } from "../Augmentation/StaticAugmentations";
import { CityName } from "../Locations/data/CityNames";
@ -17,22 +14,22 @@ import {
SleeveTask,
} from "../ScriptEditor/NetscriptDefinitions";
import { checkEnum } from "../utils/helpers/checkEnum";
import { InternalAPI, NetscriptContext } from "../Netscript/APIWrapper";
export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): ISleeve {
const checkSleeveAPIAccess = function (func: string): void {
export function NetscriptSleeve(player: IPlayer): InternalAPI<ISleeve> {
const checkSleeveAPIAccess = function (ctx: NetscriptContext): void {
if (player.bitNodeN !== 10 && !player.sourceFileLvl(10)) {
throw helper.makeRuntimeErrorMsg(
`sleeve.${func}`,
throw ctx.makeRuntimeErrorMsg(
"You do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10",
);
}
};
const checkSleeveNumber = function (func: string, sleeveNumber: number): void {
const checkSleeveNumber = function (ctx: NetscriptContext, sleeveNumber: number): void {
if (sleeveNumber >= player.sleeves.length || sleeveNumber < 0) {
const msg = `Invalid sleeve number: ${sleeveNumber}`;
workerScript.log(func, () => msg);
throw helper.makeRuntimeErrorMsg(`sleeve.${func}`, msg);
ctx.log(() => msg);
throw ctx.makeRuntimeErrorMsg(msg);
}
};
@ -50,68 +47,70 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
};
};
const updateRam = (funcName: string): void =>
helper.updateDynamicRam(funcName, getRamCost(player, "sleeve", funcName));
return {
getNumSleeves: function (): number {
updateRam("getNumSleeves");
checkSleeveAPIAccess("getNumSleeves");
getNumSleeves: (ctx: NetscriptContext) => (): number => {
checkSleeveAPIAccess(ctx);
return player.sleeves.length;
},
setToShockRecovery: function (_sleeveNumber: unknown): boolean {
updateRam("setToShockRecovery");
const sleeveNumber = helper.number("setToShockRecovery", "sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess("setToShockRecovery");
checkSleeveNumber("setToShockRecovery", sleeveNumber);
setToShockRecovery:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown): boolean => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
return player.sleeves[sleeveNumber].shockRecovery(player);
},
setToSynchronize: function (_sleeveNumber: unknown): boolean {
updateRam("setToSynchronize");
const sleeveNumber = helper.number("setToSynchronize", "sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess("setToSynchronize");
checkSleeveNumber("setToSynchronize", sleeveNumber);
setToSynchronize:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown): boolean => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
return player.sleeves[sleeveNumber].synchronize(player);
},
setToCommitCrime: function (_sleeveNumber: unknown, _crimeRoughName: unknown): boolean {
updateRam("setToCommitCrime");
const sleeveNumber = helper.number("setToCommitCrime", "sleeveNumber", _sleeveNumber);
const crimeRoughName = helper.string("setToCommitCrime", "crimeName", _crimeRoughName);
checkSleeveAPIAccess("setToCommitCrime");
checkSleeveNumber("setToCommitCrime", sleeveNumber);
setToCommitCrime:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown, _crimeRoughName: unknown): boolean => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
const crimeRoughName = ctx.helper.string("crimeName", _crimeRoughName);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
const crime = findCrime(crimeRoughName);
if (crime === null) {
return false;
}
return player.sleeves[sleeveNumber].commitCrime(player, crime.name);
},
setToUniversityCourse: function (_sleeveNumber: unknown, _universityName: unknown, _className: unknown): boolean {
updateRam("setToUniversityCourse");
const sleeveNumber = helper.number("setToUniversityCourse", "sleeveNumber", _sleeveNumber);
const universityName = helper.string("setToUniversityCourse", "universityName", _universityName);
const className = helper.string("setToUniversityCourse", "className", _className);
checkSleeveAPIAccess("setToUniversityCourse");
checkSleeveNumber("setToUniversityCourse", sleeveNumber);
setToUniversityCourse:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown, _universityName: unknown, _className: unknown): boolean => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
const universityName = ctx.helper.string("universityName", _universityName);
const className = ctx.helper.string("className", _className);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
return player.sleeves[sleeveNumber].takeUniversityCourse(player, universityName, className);
},
travel: function (_sleeveNumber: unknown, _cityName: unknown): boolean {
updateRam("travel");
const sleeveNumber = helper.number("travel", "sleeveNumber", _sleeveNumber);
const cityName = helper.string("travel", "cityName", _cityName);
checkSleeveAPIAccess("travel");
checkSleeveNumber("travel", sleeveNumber);
travel:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown, _cityName: unknown): boolean => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
const cityName = ctx.helper.string("cityName", _cityName);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
if (checkEnum(CityName, cityName)) {
return player.sleeves[sleeveNumber].travel(player, cityName);
} else {
throw helper.makeRuntimeErrorMsg("sleeve.setToCompanyWork", `Invalid city name: '${cityName}'.`);
throw ctx.makeRuntimeErrorMsg(`Invalid city name: '${cityName}'.`);
}
},
setToCompanyWork: function (_sleeveNumber: unknown, acompanyName: unknown): boolean {
updateRam("setToCompanyWork");
const sleeveNumber = helper.number("setToCompanyWork", "sleeveNumber", _sleeveNumber);
const companyName = helper.string("setToCompanyWork", "companyName", acompanyName);
checkSleeveAPIAccess("setToCompanyWork");
checkSleeveNumber("setToCompanyWork", sleeveNumber);
setToCompanyWork:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown, acompanyName: unknown): boolean => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
const companyName = ctx.helper.string("companyName", acompanyName);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
// Cannot work at the same company that another sleeve is working at
for (let i = 0; i < player.sleeves.length; ++i) {
@ -120,8 +119,7 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
}
const other = player.sleeves[i];
if (other.currentTask === SleeveTaskType.Company && other.currentTaskLocation === companyName) {
throw helper.makeRuntimeErrorMsg(
"sleeve.setToCompanyWork",
throw ctx.makeRuntimeErrorMsg(
`Sleeve ${sleeveNumber} cannot work for company ${companyName} because Sleeve ${i} is already working for them.`,
);
}
@ -129,17 +127,14 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
return player.sleeves[sleeveNumber].workForCompany(player, companyName);
},
setToFactionWork: function (
_sleeveNumber: unknown,
_factionName: unknown,
_workType: unknown,
): boolean | undefined {
updateRam("setToFactionWork");
const sleeveNumber = helper.number("setToFactionWork", "sleeveNumber", _sleeveNumber);
const factionName = helper.string("setToFactionWork", "factionName", _factionName);
const workType = helper.string("setToFactionWork", "workType", _workType);
checkSleeveAPIAccess("setToFactionWork");
checkSleeveNumber("setToFactionWork", sleeveNumber);
setToFactionWork:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown, _factionName: unknown, _workType: unknown): boolean | undefined => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
const factionName = ctx.helper.string("factionName", _factionName);
const workType = ctx.helper.string("workType", _workType);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
// Cannot work at the same faction that another sleeve is working at
for (let i = 0; i < player.sleeves.length; ++i) {
@ -148,44 +143,45 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
}
const other = player.sleeves[i];
if (other.currentTask === SleeveTaskType.Faction && other.currentTaskLocation === factionName) {
throw helper.makeRuntimeErrorMsg(
"sleeve.setToFactionWork",
throw ctx.makeRuntimeErrorMsg(
`Sleeve ${sleeveNumber} cannot work for faction ${factionName} because Sleeve ${i} is already working for them.`,
);
}
}
if (player.gang && player.gang.facName == factionName) {
throw helper.makeRuntimeErrorMsg(
"sleeve.setToFactionWork",
throw ctx.makeRuntimeErrorMsg(
`Sleeve ${sleeveNumber} cannot work for faction ${factionName} because you have started a gang with them.`,
);
}
return player.sleeves[sleeveNumber].workForFaction(player, factionName, workType);
},
setToGymWorkout: function (_sleeveNumber: unknown, _gymName: unknown, _stat: unknown): boolean {
updateRam("setToGymWorkout");
const sleeveNumber = helper.number("setToGymWorkout", "sleeveNumber", _sleeveNumber);
const gymName = helper.string("setToGymWorkout", "gymName", _gymName);
const stat = helper.string("setToGymWorkout", "stat", _stat);
checkSleeveAPIAccess("setToGymWorkout");
checkSleeveNumber("setToGymWorkout", sleeveNumber);
setToGymWorkout:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown, _gymName: unknown, _stat: unknown): boolean => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
const gymName = ctx.helper.string("gymName", _gymName);
const stat = ctx.helper.string("stat", _stat);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
return player.sleeves[sleeveNumber].workoutAtGym(player, gymName, stat);
},
getSleeveStats: function (_sleeveNumber: unknown): SleeveSkills {
updateRam("getSleeveStats");
const sleeveNumber = helper.number("getSleeveStats", "sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess("getSleeveStats");
checkSleeveNumber("getSleeveStats", sleeveNumber);
getSleeveStats:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown): SleeveSkills => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
return getSleeveStats(sleeveNumber);
},
getTask: function (_sleeveNumber: unknown): SleeveTask {
updateRam("getTask");
const sleeveNumber = helper.number("getTask", "sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess("getTask");
checkSleeveNumber("getTask", sleeveNumber);
getTask:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown): SleeveTask => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
const sl = player.sleeves[sleeveNumber];
return {
@ -196,11 +192,12 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
factionWorkType: FactionWorkType[sl.factionWorkType],
};
},
getInformation: function (_sleeveNumber: unknown): SleeveInformation {
updateRam("getInformation");
const sleeveNumber = helper.number("getInformation", "sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess("getInformation");
checkSleeveNumber("getInformation", sleeveNumber);
getInformation:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown): SleeveInformation => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
const sl = player.sleeves[sleeveNumber];
return {
@ -262,11 +259,12 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
workRepGain: sl.getRepGain(player),
};
},
getSleeveAugmentations: function (_sleeveNumber: unknown): string[] {
updateRam("getSleeveAugmentations");
const sleeveNumber = helper.number("getSleeveAugmentations", "sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess("getSleeveAugmentations");
checkSleeveNumber("getSleeveAugmentations", sleeveNumber);
getSleeveAugmentations:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown): string[] => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
const augs = [];
for (let i = 0; i < player.sleeves[sleeveNumber].augmentations.length; i++) {
@ -274,11 +272,12 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
}
return augs;
},
getSleevePurchasableAugs: function (_sleeveNumber: unknown): AugmentPair[] {
updateRam("getSleevePurchasableAugs");
const sleeveNumber = helper.number("getSleevePurchasableAugs", "sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess("getSleevePurchasableAugs");
checkSleeveNumber("getSleevePurchasableAugs", sleeveNumber);
getSleevePurchasableAugs:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown): AugmentPair[] => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
const purchasableAugs = findSleevePurchasableAugs(player.sleeves[sleeveNumber], player);
const augs = [];
@ -292,36 +291,38 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
return augs;
},
purchaseSleeveAug: function (_sleeveNumber: unknown, _augName: unknown): boolean {
updateRam("purchaseSleeveAug");
const sleeveNumber = helper.number("purchaseSleeveAug", "sleeveNumber", _sleeveNumber);
const augName = helper.string("purchaseSleeveAug", "augName", _augName);
checkSleeveAPIAccess("purchaseSleeveAug");
checkSleeveNumber("purchaseSleeveAug", sleeveNumber);
purchaseSleeveAug:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown, _augName: unknown): boolean => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
const augName = ctx.helper.string("augName", _augName);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
if (getSleeveStats(sleeveNumber).shock > 0) {
throw helper.makeRuntimeErrorMsg("sleeve.purchaseSleeveAug", `Sleeve shock too high: Sleeve ${sleeveNumber}`);
throw ctx.makeRuntimeErrorMsg(`Sleeve shock too high: Sleeve ${sleeveNumber}`);
}
const aug = StaticAugmentations[augName];
if (!aug) {
throw helper.makeRuntimeErrorMsg("sleeve.purchaseSleeveAug", `Invalid aug: ${augName}`);
throw ctx.makeRuntimeErrorMsg(`Invalid aug: ${augName}`);
}
return player.sleeves[sleeveNumber].tryBuyAugmentation(player, aug);
},
setToBladeburnerAction: function (_sleeveNumber: unknown, _action: unknown, _contract?: unknown): boolean {
updateRam("setToBladeburnerAction");
const sleeveNumber = helper.number("setToBladeburnerAction", "sleeveNumber", _sleeveNumber);
const action = helper.string("setToBladeburnerAction", "action", _action);
setToBladeburnerAction:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown, _action: unknown, _contract?: unknown): boolean => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
const action = ctx.helper.string("action", _action);
let contract: string;
if (typeof _contract === "undefined") {
contract = "------";
} else {
contract = helper.string("setToBladeburnerAction", "contract", _contract);
contract = ctx.helper.string("contract", _contract);
}
checkSleeveAPIAccess("setToBladeburnerAction");
checkSleeveNumber("setToBladeburnerAction", sleeveNumber);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
// Cannot Take on Contracts if another sleeve is performing that action
if (action === "Take on contracts") {
@ -331,8 +332,7 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
}
const other = player.sleeves[i];
if (other.currentTask === SleeveTaskType.Bladeburner && other.bbAction === action) {
throw helper.makeRuntimeErrorMsg(
"sleeve.setToBladeburnerAction",
throw ctx.helper.makeRuntimeErrorMsg(
`Sleeve ${sleeveNumber} cannot take of contracts because Sleeve ${i} is already performing that action.`,
);
}

@ -5,6 +5,7 @@ import { netscriptDelay } from "../NetscriptEvaluator";
import { staneksGift } from "../CotMG/Helper";
import { Fragments, FragmentById } from "../CotMG/Fragment";
import { FragmentType } from "../CotMG/FragmentType";
import {
Fragment as IFragment,
@ -25,7 +26,7 @@ export function NetscriptStanek(
): InternalAPI<IStanek> {
function checkStanekAPIAccess(func: string): void {
if (!player.hasAugmentation(AugmentationNames.StaneksGift1, true)) {
helper.makeRuntimeErrorMsg(func, "Requires Stanek's Gift installed.");
throw helper.makeRuntimeErrorMsg(func, "Stanek's Gift is not installed");
}
}
@ -42,15 +43,23 @@ export function NetscriptStanek(
},
chargeFragment: (_ctx: NetscriptContext) =>
function (_rootX: unknown, _rootY: unknown): Promise<void> {
//Get the fragment object using the given coordinates
const rootX = _ctx.helper.number("rootX", _rootX);
const rootY = _ctx.helper.number("rootY", _rootY);
checkStanekAPIAccess("chargeFragment");
const fragment = staneksGift.findFragment(rootX, rootY);
//Check whether the selected fragment can ge charged
if (!fragment) throw _ctx.makeRuntimeErrorMsg(`No fragment with root (${rootX}, ${rootY}).`);
if (fragment.fragment().type == FragmentType.Booster) {
throw _ctx.makeRuntimeErrorMsg(
`The fragment with root (${rootX}, ${rootY}) is a Booster Fragment and thus cannot be charged.`,
);
}
//Charge the fragment
const time = staneksGift.inBonus() ? 200 : 1000;
return netscriptDelay(time, workerScript).then(function () {
const charge = staneksGift.charge(player, fragment, workerScript.scriptRef.threads);
_ctx.log(() => `Charged fragment for ${charge} charge.`);
staneksGift.charge(player, fragment, workerScript.scriptRef.threads);
_ctx.log(() => `Charged fragment with ${_ctx.workerScript.scriptRef.threads} threads.`);
return Promise.resolve();
});
},

@ -1,7 +1,5 @@
import { INetscriptHelper } from "./INetscriptHelper";
import { WorkerScript } from "../Netscript/WorkerScript";
import { IPlayer } from "../PersonObjects/IPlayer";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { buyStock, sellStock, shortStock, sellShort } from "../StockMarket/BuyingAndSelling";
import { StockMarket, SymbolToStockMap, placeOrder, cancelOrder, initStockMarketFn } from "../StockMarket/StockMarket";
import { getBuyTransactionCost, getSellTransactionGain } from "../StockMarket/StockMarketHelpers";
@ -16,87 +14,90 @@ import {
} from "../StockMarket/StockMarketCosts";
import { Stock } from "../StockMarket/Stock";
import { TIX } from "../ScriptEditor/NetscriptDefinitions";
import { InternalAPI, NetscriptContext } from "src/Netscript/APIWrapper";
export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): TIX {
export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript): InternalAPI<TIX> {
/**
* Checks if the player has TIX API access. Throws an error if the player does not
*/
const checkTixApiAccess = function (callingFn: string): void {
const checkTixApiAccess = function (ctx: NetscriptContext): void {
if (!player.hasWseAccount) {
throw helper.makeRuntimeErrorMsg(callingFn, `You don't have WSE Access! Cannot use ${callingFn}()`);
throw ctx.makeRuntimeErrorMsg(`You don't have WSE Access! Cannot use ${ctx.function}()`);
}
if (!player.hasTixApiAccess) {
throw helper.makeRuntimeErrorMsg(callingFn, `You don't have TIX API Access! Cannot use ${callingFn}()`);
throw ctx.makeRuntimeErrorMsg(`You don't have TIX API Access! Cannot use ${ctx.function}()`);
}
};
const getStockFromSymbol = function (symbol: string, callingFn: string): Stock {
const getStockFromSymbol = function (ctx: NetscriptContext, symbol: string): Stock {
const stock = SymbolToStockMap[symbol];
if (stock == null) {
throw helper.makeRuntimeErrorMsg(callingFn, `Invalid stock symbol: '${symbol}'`);
throw ctx.makeRuntimeErrorMsg(`Invalid stock symbol: '${symbol}'`);
}
return stock;
};
const updateRam = (funcName: string): void =>
helper.updateDynamicRam(funcName, getRamCost(player, "stock", funcName));
return {
getSymbols: function (): string[] {
updateRam("getSymbols");
checkTixApiAccess("getSymbols");
getSymbols: (ctx: NetscriptContext) => (): string[] => {
checkTixApiAccess(ctx);
return Object.values(StockSymbols);
},
getPrice: function (_symbol: unknown): number {
updateRam("getPrice");
const symbol = helper.string("getPrice", "symbol", _symbol);
checkTixApiAccess("getPrice");
const stock = getStockFromSymbol(symbol, "getPrice");
getPrice:
(ctx: NetscriptContext) =>
(_symbol: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
return stock.price;
},
getAskPrice: function (_symbol: unknown): number {
updateRam("getAskPrice");
const symbol = helper.string("getAskPrice", "symbol", _symbol);
checkTixApiAccess("getAskPrice");
const stock = getStockFromSymbol(symbol, "getAskPrice");
getAskPrice:
(ctx: NetscriptContext) =>
(_symbol: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
return stock.getAskPrice();
},
getBidPrice: function (_symbol: unknown): number {
updateRam("getBidPrice");
const symbol = helper.string("getBidPrice", "symbol", _symbol);
checkTixApiAccess("getBidPrice");
const stock = getStockFromSymbol(symbol, "getBidPrice");
getBidPrice:
(ctx: NetscriptContext) =>
(_symbol: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
return stock.getBidPrice();
},
getPosition: function (_symbol: unknown): [number, number, number, number] {
updateRam("getPosition");
const symbol = helper.string("getPosition", "symbol", _symbol);
checkTixApiAccess("getPosition");
getPosition:
(ctx: NetscriptContext) =>
(_symbol: unknown): [number, number, number, number] => {
const symbol = ctx.helper.string("symbol", _symbol);
checkTixApiAccess(ctx);
const stock = SymbolToStockMap[symbol];
if (stock == null) {
throw helper.makeRuntimeErrorMsg("getPosition", `Invalid stock symbol: ${symbol}`);
throw ctx.makeRuntimeErrorMsg(`Invalid stock symbol: ${symbol}`);
}
return [stock.playerShares, stock.playerAvgPx, stock.playerShortShares, stock.playerAvgShortPx];
},
getMaxShares: function (_symbol: unknown): number {
updateRam("getMaxShares");
const symbol = helper.string("getMaxShares", "symbol", _symbol);
checkTixApiAccess("getMaxShares");
const stock = getStockFromSymbol(symbol, "getMaxShares");
getMaxShares:
(ctx: NetscriptContext) =>
(_symbol: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
return stock.maxShares;
},
getPurchaseCost: function (_symbol: unknown, _shares: unknown, _posType: unknown): number {
updateRam("getPurchaseCost");
const symbol = helper.string("getPurchaseCost", "symbol", _symbol);
let shares = helper.number("getPurchaseCost", "shares", _shares);
const posType = helper.string("getPurchaseCost", "posType", _posType);
checkTixApiAccess("getPurchaseCost");
const stock = getStockFromSymbol(symbol, "getPurchaseCost");
getPurchaseCost:
(ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown, _posType: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
let shares = ctx.helper.number("shares", _shares);
const posType = ctx.helper.string("posType", _posType);
checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
shares = Math.round(shares);
let pos;
@ -116,13 +117,14 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
return res;
},
getSaleGain: function (_symbol: unknown, _shares: unknown, _posType: unknown): number {
updateRam("getSaleGain");
const symbol = helper.string("getSaleGain", "symbol", _symbol);
let shares = helper.number("getSaleGain", "shares", _shares);
const posType = helper.string("getSaleGain", "posType", _posType);
checkTixApiAccess("getSaleGain");
const stock = getStockFromSymbol(symbol, "getSaleGain");
getSaleGain:
(ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown, _posType: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
let shares = ctx.helper.number("shares", _shares);
const posType = ctx.helper.string("posType", _posType);
checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
shares = Math.round(shares);
let pos;
@ -142,78 +144,74 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
return res;
},
buy: function (_symbol: unknown, _shares: unknown): number {
updateRam("buy");
const symbol = helper.string("buy", "symbol", _symbol);
const shares = helper.number("buy", "shares", _shares);
checkTixApiAccess("buy");
const stock = getStockFromSymbol(symbol, "buy");
buy:
(ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
const shares = ctx.helper.number("shares", _shares);
checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
const res = buyStock(stock, shares, workerScript, {});
return res ? stock.getAskPrice() : 0;
},
sell: function (_symbol: unknown, _shares: unknown): number {
updateRam("sell");
const symbol = helper.string("sell", "symbol", _symbol);
const shares = helper.number("sell", "shares", _shares);
checkTixApiAccess("sell");
const stock = getStockFromSymbol(symbol, "sell");
sell:
(ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
const shares = ctx.helper.number("shares", _shares);
checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
const res = sellStock(stock, shares, workerScript, {});
return res ? stock.getBidPrice() : 0;
},
short: function (_symbol: unknown, _shares: unknown): number {
updateRam("short");
const symbol = helper.string("short", "symbol", _symbol);
const shares = helper.number("short", "shares", _shares);
checkTixApiAccess("short");
short:
(ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
const shares = ctx.helper.number("shares", _shares);
checkTixApiAccess(ctx);
if (player.bitNodeN !== 8) {
if (player.sourceFileLvl(8) <= 1) {
throw helper.makeRuntimeErrorMsg(
"short",
"You must either be in BitNode-8 or you must have Source-File 8 Level 2.",
);
throw ctx.makeRuntimeErrorMsg("You must either be in BitNode-8 or you must have Source-File 8 Level 2.");
}
}
const stock = getStockFromSymbol(symbol, "short");
const stock = getStockFromSymbol(ctx, symbol);
const res = shortStock(stock, shares, workerScript, {});
return res ? stock.getBidPrice() : 0;
},
sellShort: function (_symbol: unknown, _shares: unknown): number {
updateRam("sellShort");
const symbol = helper.string("sellShort", "symbol", _symbol);
const shares = helper.number("sellShort", "shares", _shares);
checkTixApiAccess("sellShort");
sellShort:
(ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
const shares = ctx.helper.number("shares", _shares);
checkTixApiAccess(ctx);
if (player.bitNodeN !== 8) {
if (player.sourceFileLvl(8) <= 1) {
throw helper.makeRuntimeErrorMsg(
"sellShort",
"You must either be in BitNode-8 or you must have Source-File 8 Level 2.",
);
throw ctx.makeRuntimeErrorMsg("You must either be in BitNode-8 or you must have Source-File 8 Level 2.");
}
}
const stock = getStockFromSymbol(symbol, "sellShort");
const stock = getStockFromSymbol(ctx, symbol);
const res = sellShort(stock, shares, workerScript, {});
return res ? stock.getAskPrice() : 0;
},
placeOrder: function (_symbol: unknown, _shares: unknown, _price: unknown, _type: unknown, _pos: unknown): boolean {
updateRam("placeOrder");
const symbol = helper.string("placeOrder", "symbol", _symbol);
const shares = helper.number("placeOrder", "shares", _shares);
const price = helper.number("placeOrder", "price", _price);
const type = helper.string("placeOrder", "type", _type);
const pos = helper.string("placeOrder", "pos", _pos);
checkTixApiAccess("placeOrder");
placeOrder:
(ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown, _price: unknown, _type: unknown, _pos: unknown): boolean => {
const symbol = ctx.helper.string("symbol", _symbol);
const shares = ctx.helper.number("shares", _shares);
const price = ctx.helper.number("price", _price);
const type = ctx.helper.string("type", _type);
const pos = ctx.helper.string("pos", _pos);
checkTixApiAccess(ctx);
if (player.bitNodeN !== 8) {
if (player.sourceFileLvl(8) <= 2) {
throw helper.makeRuntimeErrorMsg(
"placeOrder",
"You must either be in BitNode-8 or you must have Source-File 8 Level 3.",
);
throw ctx.makeRuntimeErrorMsg("You must either be in BitNode-8 or you must have Source-File 8 Level 3.");
}
}
const stock = getStockFromSymbol(symbol, "placeOrder");
const stock = getStockFromSymbol(ctx, symbol);
let orderType;
let orderPos;
@ -227,7 +225,7 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
} else if (ltype.includes("stop") && ltype.includes("sell")) {
orderType = OrderTypes.StopSell;
} else {
throw helper.makeRuntimeErrorMsg("placeOrder", `Invalid order type: ${type}`);
throw ctx.makeRuntimeErrorMsg(`Invalid order type: ${type}`);
}
const lpos = pos.toLowerCase();
@ -236,39 +234,28 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
} else if (lpos.includes("s")) {
orderPos = PositionTypes.Short;
} else {
throw helper.makeRuntimeErrorMsg("placeOrder", `Invalid position type: ${pos}`);
throw ctx.makeRuntimeErrorMsg(`Invalid position type: ${pos}`);
}
return placeOrder(stock, shares, price, orderType, orderPos, workerScript);
},
cancelOrder: function (
_symbol: unknown,
_shares: unknown,
_price: unknown,
_type: unknown,
_pos: unknown,
): boolean {
updateRam("cancelOrder");
const symbol = helper.string("cancelOrder", "symbol", _symbol);
const shares = helper.number("cancelOrder", "shares", _shares);
const price = helper.number("cancelOrder", "price", _price);
const type = helper.string("cancelOrder", "type", _type);
const pos = helper.string("cancelOrder", "pos", _pos);
checkTixApiAccess("cancelOrder");
cancelOrder:
(ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown, _price: unknown, _type: unknown, _pos: unknown): boolean => {
const symbol = ctx.helper.string("symbol", _symbol);
const shares = ctx.helper.number("shares", _shares);
const price = ctx.helper.number("price", _price);
const type = ctx.helper.string("type", _type);
const pos = ctx.helper.string("pos", _pos);
checkTixApiAccess(ctx);
if (player.bitNodeN !== 8) {
if (player.sourceFileLvl(8) <= 2) {
throw helper.makeRuntimeErrorMsg(
"cancelOrder",
"You must either be in BitNode-8 or you must have Source-File 8 Level 3.",
);
throw ctx.makeRuntimeErrorMsg("You must either be in BitNode-8 or you must have Source-File 8 Level 3.");
}
}
const stock = getStockFromSymbol(symbol, "cancelOrder");
const stock = getStockFromSymbol(ctx, symbol);
if (isNaN(shares) || isNaN(price)) {
throw helper.makeRuntimeErrorMsg(
"cancelOrder",
`Invalid shares or price. Must be numeric. shares=${shares}, price=${price}`,
);
throw ctx.makeRuntimeErrorMsg(`Invalid shares or price. Must be numeric. shares=${shares}, price=${price}`);
}
let orderType;
let orderPos;
@ -282,7 +269,7 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
} else if (ltype.includes("stop") && ltype.includes("sell")) {
orderType = OrderTypes.StopSell;
} else {
throw helper.makeRuntimeErrorMsg("cancelOrder", `Invalid order type: ${type}`);
throw ctx.makeRuntimeErrorMsg(`Invalid order type: ${type}`);
}
const lpos = pos.toLowerCase();
@ -291,7 +278,7 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
} else if (lpos.includes("s")) {
orderPos = PositionTypes.Short;
} else {
throw helper.makeRuntimeErrorMsg("cancelOrder", `Invalid position type: ${pos}`);
throw ctx.makeRuntimeErrorMsg(`Invalid position type: ${pos}`);
}
const params = {
stock: stock,
@ -302,15 +289,11 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
};
return cancelOrder(params, workerScript);
},
getOrders: function (): any {
updateRam("getOrders");
checkTixApiAccess("getOrders");
getOrders: (ctx: NetscriptContext) => (): any => {
checkTixApiAccess(ctx);
if (player.bitNodeN !== 8) {
if (player.sourceFileLvl(8) <= 2) {
throw helper.makeRuntimeErrorMsg(
"getOrders",
"You must either be in BitNode-8 or have Source-File 8 Level 3.",
);
throw ctx.makeRuntimeErrorMsg("You must either be in BitNode-8 or have Source-File 8 Level 3.");
}
}
@ -334,103 +317,95 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
return orders;
},
getVolatility: function (_symbol: unknown): number {
updateRam("getVolatility");
const symbol = helper.string("getVolatility", "symbol", _symbol);
getVolatility:
(ctx: NetscriptContext) =>
(_symbol: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
if (!player.has4SDataTixApi) {
throw helper.makeRuntimeErrorMsg("getVolatility", "You don't have 4S Market Data TIX API Access!");
throw ctx.makeRuntimeErrorMsg("You don't have 4S Market Data TIX API Access!");
}
const stock = getStockFromSymbol(symbol, "getVolatility");
const stock = getStockFromSymbol(ctx, symbol);
return stock.mv / 100; // Convert from percentage to decimal
},
getForecast: function (_symbol: unknown): number {
updateRam("getForecast");
const symbol = helper.string("getForecast", "symbol", _symbol);
getForecast:
(ctx: NetscriptContext) =>
(_symbol: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
if (!player.has4SDataTixApi) {
throw helper.makeRuntimeErrorMsg("getForecast", "You don't have 4S Market Data TIX API Access!");
throw ctx.makeRuntimeErrorMsg("You don't have 4S Market Data TIX API Access!");
}
const stock = getStockFromSymbol(symbol, "getForecast");
const stock = getStockFromSymbol(ctx, symbol);
let forecast = 50;
stock.b ? (forecast += stock.otlkMag) : (forecast -= stock.otlkMag);
return forecast / 100; // Convert from percentage to decimal
},
purchase4SMarketData: function (): boolean {
updateRam("purchase4SMarketData");
purchase4SMarketData: (ctx: NetscriptContext) => (): boolean => {
if (player.has4SData) {
workerScript.log("stock.purchase4SMarketData", () => "Already purchased 4S Market Data.");
ctx.log(() => "Already purchased 4S Market Data.");
return true;
}
if (player.money < getStockMarket4SDataCost()) {
workerScript.log("stock.purchase4SMarketData", () => "Not enough money to purchase 4S Market Data.");
ctx.log(() => "Not enough money to purchase 4S Market Data.");
return false;
}
player.has4SData = true;
player.loseMoney(getStockMarket4SDataCost(), "stock");
workerScript.log("stock.purchase4SMarketData", () => "Purchased 4S Market Data");
ctx.log(() => "Purchased 4S Market Data");
return true;
},
purchase4SMarketDataTixApi: function (): boolean {
updateRam("purchase4SMarketDataTixApi");
checkTixApiAccess("purchase4SMarketDataTixApi");
purchase4SMarketDataTixApi: (ctx: NetscriptContext) => (): boolean => {
checkTixApiAccess(ctx);
if (player.has4SDataTixApi) {
workerScript.log("stock.purchase4SMarketDataTixApi", () => "Already purchased 4S Market Data TIX API");
ctx.log(() => "Already purchased 4S Market Data TIX API");
return true;
}
if (player.money < getStockMarket4STixApiCost()) {
workerScript.log(
"stock.purchase4SMarketDataTixApi",
() => "Not enough money to purchase 4S Market Data TIX API",
);
ctx.log(() => "Not enough money to purchase 4S Market Data TIX API");
return false;
}
player.has4SDataTixApi = true;
player.loseMoney(getStockMarket4STixApiCost(), "stock");
workerScript.log("stock.purchase4SMarketDataTixApi", () => "Purchased 4S Market Data TIX API");
ctx.log(() => "Purchased 4S Market Data TIX API");
return true;
},
purchaseWseAccount: function (): boolean {
updateRam("PurchaseWseAccount");
purchaseWseAccount: (ctx: NetscriptContext) => (): boolean => {
if (player.hasWseAccount) {
workerScript.log("stock.purchaseWseAccount", () => "Already purchased WSE Account");
ctx.log(() => "Already purchased WSE Account");
return true;
}
if (player.money < getStockMarketWseCost()) {
workerScript.log("stock.purchaseWseAccount", () => "Not enough money to purchase WSE Account Access");
ctx.log(() => "Not enough money to purchase WSE Account Access");
return false;
}
player.hasWseAccount = true;
initStockMarketFn();
player.loseMoney(getStockMarketWseCost(), "stock");
workerScript.log("stock.purchaseWseAccount", () => "Purchased WSE Account Access");
ctx.log(() => "Purchased WSE Account Access");
return true;
},
purchaseTixApi: function (): boolean {
updateRam("purchaseTixApi");
purchaseTixApi: (ctx: NetscriptContext) => (): boolean => {
if (player.hasTixApiAccess) {
workerScript.log("stock.purchaseTixApi", () => "Already purchased TIX API");
ctx.log(() => "Already purchased TIX API");
return true;
}
if (player.money < getStockMarketTixApiCost()) {
workerScript.log("stock.purchaseTixApi", () => "Not enough money to purchase TIX API Access");
ctx.log(() => "Not enough money to purchase TIX API Access");
return false;
}
player.hasTixApiAccess = true;
player.loseMoney(getStockMarketTixApiCost(), "stock");
workerScript.log("stock.purchaseTixApi", () => "Purchased TIX API");
ctx.log(() => "Purchased TIX API");
return true;
},
};

@ -1,7 +1,3 @@
import { INetscriptHelper } from "./INetscriptHelper";
import { WorkerScript } from "../Netscript/WorkerScript";
import { IPlayer } from "../PersonObjects/IPlayer";
import { getRamCost } from "../Netscript/RamCostGenerator";
import {
GameInfo,
IStyleSettings,
@ -14,26 +10,22 @@ import { defaultTheme } from "../Themes/Themes";
import { defaultStyles } from "../Themes/Styles";
import { CONSTANTS } from "../Constants";
import { hash } from "../hash/hash";
import { InternalAPI, NetscriptContext } from "src/Netscript/APIWrapper";
import { Terminal } from "../../src/Terminal";
export function NetscriptUserInterface(
player: IPlayer,
workerScript: WorkerScript,
helper: INetscriptHelper,
): IUserInterface {
const updateRam = (funcName: string): void => helper.updateDynamicRam(funcName, getRamCost(player, "ui", funcName));
export function NetscriptUserInterface(): InternalAPI<IUserInterface> {
return {
getTheme: function (): UserInterfaceTheme {
updateRam("getTheme");
getTheme: () => (): UserInterfaceTheme => {
return { ...Settings.theme };
},
getStyles: function (): IStyleSettings {
updateRam("getStyles");
getStyles: () => (): IStyleSettings => {
return { ...Settings.styles };
},
setTheme: function (newTheme: UserInterfaceTheme): void {
updateRam("setTheme");
setTheme:
(ctx: NetscriptContext) =>
(newTheme: UserInterfaceTheme): void => {
const hex = /^(#)((?:[A-Fa-f0-9]{2}){3,4}|(?:[A-Fa-f0-9]{3}))$/;
const currentTheme = { ...Settings.theme };
const errors: string[] = [];
@ -51,15 +43,15 @@ export function NetscriptUserInterface(
if (errors.length === 0) {
Object.assign(Settings.theme, currentTheme);
ThemeEvents.emit();
workerScript.log("ui.setTheme", () => `Successfully set theme`);
ctx.log(() => `Successfully set theme`);
} else {
workerScript.log("ui.setTheme", () => `Failed to set theme. Errors: ${errors.join(", ")}`);
ctx.log(() => `Failed to set theme. Errors: ${errors.join(", ")}`);
}
},
setStyles: function (newStyles: IStyleSettings): void {
updateRam("setStyles");
setStyles:
(ctx: NetscriptContext) =>
(newStyles: IStyleSettings): void => {
const currentStyles = { ...Settings.styles };
const errors: string[] = [];
for (const key of Object.keys(newStyles)) {
@ -74,28 +66,25 @@ export function NetscriptUserInterface(
if (errors.length === 0) {
Object.assign(Settings.styles, currentStyles);
ThemeEvents.emit();
workerScript.log("ui.setStyles", () => `Successfully set styles`);
ctx.log(() => `Successfully set styles`);
} else {
workerScript.log("ui.setStyles", () => `Failed to set styles. Errors: ${errors.join(", ")}`);
ctx.log(() => `Failed to set styles. Errors: ${errors.join(", ")}`);
}
},
resetTheme: function (): void {
updateRam("resetTheme");
resetTheme: (ctx: NetscriptContext) => (): void => {
Settings.theme = { ...defaultTheme };
ThemeEvents.emit();
workerScript.log("ui.resetTheme", () => `Reinitialized theme to default`);
ctx.log(() => `Reinitialized theme to default`);
},
resetStyles: function (): void {
updateRam("resetStyles");
resetStyles: (ctx: NetscriptContext) => (): void => {
Settings.styles = { ...defaultStyles };
ThemeEvents.emit();
workerScript.log("ui.resetStyles", () => `Reinitialized styles to default`);
ctx.log(() => `Reinitialized styles to default`);
},
getGameInfo: function (): GameInfo {
updateRam("getGameInfo");
getGameInfo: () => (): GameInfo => {
const version = CONSTANTS.VersionString;
const commit = hash();
const platform = navigator.userAgent.toLowerCase().indexOf(" electron/") > -1 ? "Steam" : "Browser";
@ -108,5 +97,10 @@ export function NetscriptUserInterface(
return gameInfo;
},
clearTerminal: (ctx: NetscriptContext) => (): void => {
ctx.log(() => `Clearing terminal`);
Terminal.clear();
},
};
}

@ -532,7 +532,7 @@ function createAndAddWorkerScript(
const oneRamUsage = getRamUsageFromRunningScript(runningScriptObj);
const ramUsage = roundToTwo(oneRamUsage * threads);
const ramAvailable = server.maxRam - server.ramUsed;
if (ramUsage > ramAvailable) {
if (ramUsage > ramAvailable + 0.001) {
dialogBoxCreate(
`Not enough RAM to run script ${runningScriptObj.filename} with args ` +
`${arrayToString(runningScriptObj.args)}. This likely occurred because you re-loaded ` +
@ -750,7 +750,7 @@ export function runScriptFromScript(
if (server.hasAdminRights == false) {
workerScript.log(caller, () => `You do not have root access on '${server.hostname}'`);
return 0;
} else if (ramUsage > ramAvailable) {
} else if (ramUsage > ramAvailable + 0.001) {
workerScript.log(
caller,
() =>

@ -1,13 +1,14 @@
import { Construction, CheckBox, CheckBoxOutlineBlank } from "@mui/icons-material";
import { CheckBox, CheckBoxOutlineBlank, Construction } from "@mui/icons-material";
import { Box, Button, Container, List, ListItemButton, Paper, Typography } from "@mui/material";
import React, { useState, useEffect } from "react";
import React, { useEffect, useState } from "react";
import { Augmentation } from "../../../Augmentation/Augmentation";
import { StaticAugmentations } from "../../../Augmentation/StaticAugmentations";
import { AugmentationNames } from "../../../Augmentation/data/AugmentationNames";
import { StaticAugmentations } from "../../../Augmentation/StaticAugmentations";
import { CONSTANTS } from "../../../Constants";
import { hasAugmentationPrereqs } from "../../../Faction/FactionHelpers";
import { LocationName } from "../../../Locations/data/LocationNames";
import { Locations } from "../../../Locations/Locations";
import { PurchaseAugmentationsOrderSetting } from "../../../Settings/SettingEnums";
import { Settings } from "../../../Settings/Settings";
import { IMap } from "../../../types";
import { use } from "../../../ui/Context";
@ -15,8 +16,8 @@ import { ConfirmationModal } from "../../../ui/React/ConfirmationModal";
import { Money } from "../../../ui/React/Money";
import { convertTimeMsToTimeElapsedString, formatNumber } from "../../../utils/StringHelperFunctions";
import { IPlayer } from "../../IPlayer";
import { getGraftingAvailableAugs, calculateGraftingTimeWithBonus } from "../GraftingHelpers";
import { GraftableAugmentation } from "../GraftableAugmentation";
import { calculateGraftingTimeWithBonus, getGraftingAvailableAugs } from "../GraftingHelpers";
const GraftableAugmentations: IMap<GraftableAugmentation> = {};
@ -69,6 +70,21 @@ export const GraftingRoot = (): React.ReactElement => {
setRerender((old) => !old);
}
const getAugsSorted = (): string[] => {
const augs = getGraftingAvailableAugs(player);
switch (Settings.PurchaseAugmentationsOrder) {
case PurchaseAugmentationsOrderSetting.Cost:
return augs.sort((a, b) => GraftableAugmentations[a].cost - GraftableAugmentations[b].cost);
default:
return augs;
}
};
const switchSortOrder = (newOrder: PurchaseAugmentationsOrderSetting): void => {
Settings.PurchaseAugmentationsOrder = newOrder;
rerender();
};
useEffect(() => {
const id = setInterval(rerender, 200);
return () => clearInterval(id);
@ -91,13 +107,31 @@ export const GraftingRoot = (): React.ReactElement => {
</Typography>
<Box sx={{ my: 3 }}>
<Paper sx={{ p: 1 }}>
<Typography variant="h5">Graft Augmentations</Typography>
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
<Button sx={{ width: "100%" }} onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)}>
Sort by Cost
</Button>
<Button sx={{ width: "100%" }} onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Default)}>
Sort by Default Order
</Button>
</Box>
</Paper>
{getGraftingAvailableAugs(player).length > 0 ? (
<Paper sx={{ my: 1, width: "fit-content", display: "grid", gridTemplateColumns: "1fr 3fr" }}>
<Paper sx={{ mb: 1, width: "fit-content", display: "grid", gridTemplateColumns: "1fr 3fr" }}>
<List sx={{ height: 400, overflowY: "scroll", borderRight: `1px solid ${Settings.theme.welllight}` }}>
{getGraftingAvailableAugs(player).map((k, i) => (
{getAugsSorted().map((k, i) => (
<ListItemButton key={i + 1} onClick={() => setSelectedAug(k)} selected={selectedAug === k}>
<Typography>{k}</Typography>
<Typography
sx={{
color: canGraft(player, GraftableAugmentations[k])
? Settings.theme.primary
: Settings.theme.disabled,
}}
>
{k}
</Typography>
</ListItemButton>
))}
</List>

@ -13,7 +13,7 @@ import { GetServer, AddToAllServers, createUniqueRandomIp } from "../../Server/A
import { SpecialServers } from "../../Server/data/SpecialServers";
export function hasTorRouter(this: IPlayer): boolean {
return !!GetServer(SpecialServers.DarkWeb);
return this.getHomeComputer().serversOnNetwork.includes(SpecialServers.DarkWeb);
}
export function getCurrentServer(this: IPlayer): BaseServer {

@ -4,7 +4,14 @@ import { CONSTANTS } from "./Constants";
import { Factions, loadFactions } from "./Faction/Factions";
import { loadAllGangs, AllGangs } from "./Gang/AllGangs";
import { Player, loadPlayer } from "./Player";
import { saveAllServers, loadAllServers, GetAllServers } from "./Server/AllServers";
import {
saveAllServers,
loadAllServers,
GetAllServers,
createUniqueRandomIp,
AddToAllServers,
GetServer,
} from "./Server/AllServers";
import { Settings } from "./Settings/Settings";
import { loadStockMarket, StockMarket } from "./StockMarket/StockMarket";
import { staneksGift, loadStaneksGift } from "./CotMG/Helper";
@ -26,6 +33,8 @@ import { pushGameSaved } from "./Electron";
import { defaultMonacoTheme } from "./ScriptEditor/ui/themes";
import { FactionNames } from "./Faction/data/FactionNames";
import { Faction } from "./Faction/Faction";
import { safetlyCreateUniqueServer } from "./Server/ServerHelpers";
import { SpecialServers } from "./Server/data/SpecialServers";
/* SaveObject.js
* Defines the object used to save/load games
@ -434,6 +443,22 @@ function evaluateVersionCompatibility(ver: string | number): void {
Player.reapplyAllAugmentations(true);
Player.reapplyAllSourceFiles();
}
if (ver < 18) {
// Create the darkweb for everyone but it won't be linked
const dw = GetServer(SpecialServers.DarkWeb);
if (!dw) {
const darkweb = safetlyCreateUniqueServer({
ip: createUniqueRandomIp(),
hostname: SpecialServers.DarkWeb,
organizationName: "",
isConnectedTo: false,
adminRights: false,
purchasedByPlayer: false,
maxRam: 1,
});
AddToAllServers(darkweb);
}
}
}
}

@ -560,7 +560,7 @@ export interface BitNodeMultipliers {
/** Influences how much money the player earns when completing working their job. */
CompanyWorkMoney: number;
/** Influences the money gain from dividends of corporations created by the player. */
CorporationSoftCap: number;
CorporationSoftcap: number;
/** Influences the valuation of corporations created by the player. */
CorporationValuation: number;
/** Influences the base experience gained for each ability when the player commits a crime. */
@ -4405,6 +4405,13 @@ interface UserInterface {
* RAM cost: 0 GB
*/
getGameInfo(): GameInfo;
/**
* Clear the Terminal window, as if the player ran `clear` in the terminal
* @remarks
* RAM cost: 0.2 GB
*/
clearTerminal(): void;
}
/**
@ -4988,6 +4995,21 @@ export interface NS {
*/
tail(fn?: FilenameOrPID, host?: string, ...args: any[]): void;
/**
* Close the tail window of a script.
* @remarks
* RAM cost: 0 GB
*
* Closes a scripts logs. This is functionally the same pressing the "Close" button on the tail window.
*
* If the function is called with no arguments, it will close the current scripts logs.
*
* Otherwise, the pid argument can be used to close the logs from another script.
*
* @param pid - Optional. PID of the script having its tail closed. If omitted, the current script is used.
*/
closeTail(pid?: number): void;
/**
* Get the list of servers connected to a server.
* @remarks

@ -1565,4 +1565,12 @@ export const serverMetadata: IServerMetadata[] = [
serverGrowth: 0,
specialName: SpecialServers.WorldDaemon,
},
{
hostname: SpecialServers.DarkWeb,
moneyAvailable: 0,
numOpenPortsRequired: 5,
organizationName: SpecialServers.DarkWeb,
requiredHackingSkill: 1,
specialName: SpecialServers.DarkWeb,
},
];

@ -54,6 +54,11 @@ interface IDefaultSettings {
*/
EnableBashHotkeys: boolean;
/**
* Infinite loop safety net
*/
InfinityLoopSafety: boolean;
/**
* Timestamps format
*/
@ -201,6 +206,7 @@ export const defaultSettings: IDefaultSettings = {
DisableOverviewProgressBars: false,
EnableBashHotkeys: false,
TimestampsFormat: "",
InfinityLoopSafety: true,
Locale: "en",
MaxRecentScriptsCapacity: 50,
MaxLogCapacity: 50,
@ -241,6 +247,7 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
DisableOverviewProgressBars: defaultSettings.DisableOverviewProgressBars,
EnableBashHotkeys: defaultSettings.EnableBashHotkeys,
TimestampsFormat: defaultSettings.TimestampsFormat,
InfinityLoopSafety: defaultSettings.InfinityLoopSafety,
Locale: "en",
MaxRecentScriptsCapacity: defaultSettings.MaxRecentScriptsCapacity,
MaxLogCapacity: defaultSettings.MaxLogCapacity,

@ -67,8 +67,18 @@ SourceFiles["SourceFile5"] = new SourceFile(
This Source-File grants a special new stat called Intelligence. Intelligence is unique because it is permanent and
persistent (it never gets reset back to 1). However, gaining Intelligence experience is much slower than other
stats. Higher Intelligence levels will boost your production for many actions in the game. In addition, this
Source-File will unlock the getBitNodeMultipliers() Netscript function and let you start with Formulas.exe, and
will raise all of your hacking-related multipliers by:
Source-File will unlock:
<br />
<ul>
<li>
The <code>getBitNodeMultipliers()</code> Netscript function
</li>
<li>Permanent access to Formulas.exe</li>
<li>
Access to BitNode multiplier information on the <b>Stats</b> page
</li>
</ul>
It will also raise all of your hacking-related multipliers by:
<br />
<br />
Level 1: 8%

@ -49,9 +49,11 @@ export function isValidFilename(filename: string): boolean {
* not an entire path
*/
export function isValidDirectoryName(name: string): boolean {
// Allows alphanumerics, hyphens, underscores, and percentage signs.
// Name can begin with a single period, but otherwise cannot have any
const regex = /^.?[a-zA-Z0-9_-]+$/;
// A valid directory name:
// Must be at least 1 character long
// Can only include characters in the character set [-.%a-zA-Z0-9_]
// Cannot end with a '.'
const regex = /^(?:[-.%]|\w)*[-%a-zA-Z0-9_]$/;
// match() returns null if no match is found
return name.match(regex) != null;

@ -463,6 +463,12 @@ export class Terminal implements ITerminal {
this.contractOpen = true;
const res = await contract.prompt();
//Check if the contract still exists by the time the promise is fullfilled
if (serv.getContract(contractName) == null) {
this.contractOpen = false;
return this.error("Contract no longer exists (Was it solved by a script?)");
}
switch (res) {
case CodingContractResult.Success:
if (contract.reward !== null) {

@ -66,6 +66,13 @@ export function mv(
if (destFile != null) {
// Already exists, will be overwritten, so we'll delete it
// Command doesnt work if script is running
if (server.isRunning(destPath)) {
terminal.error(`Cannot use 'mv' on a script that is running`);
return;
}
const status = server.removeFile(destPath);
if (!status.res) {
terminal.error(`Something went wrong...please contact game dev (probably a bug)`);

@ -63,13 +63,11 @@ export function runScript(
return;
}
if (ramUsage > ramAvailable) {
if (ramUsage > ramAvailable + 0.001) {
terminal.error(
"This machine does not have enough RAM to run this script with " +
numThreads +
" threads. Script requires " +
numeralWrapper.formatRAM(ramUsage) +
" of RAM",
"This machine does not have enough RAM to run this script" +
(numThreads === 1 ? "" : ` with ${numThreads} threads`) +
`. Script requires ${numeralWrapper.formatRAM(ramUsage)} of RAM`,
);
return;
}

@ -1382,7 +1382,8 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return [n + m, edges];
},
solver: (data: [number, [number, number][]], ans: string): boolean => {
//Case where the player believes there is no solution
//Case where the player believes there is no solution.
//Attempt to construct one to check if this is correct.
if (ans == "[]") {
//Helper function to get neighbourhood of a vertex
function neighbourhood(vertex: number): number[] {

@ -1,8 +1,9 @@
import { Paper, Table, TableBody, Box, IconButton, Typography, Container, Tooltip } from "@mui/material";
import { MoreHoriz, Info } from "@mui/icons-material";
import React, { useEffect, useState } from "react";
import { BitNodes } from "../BitNode/BitNode";
import { BitNodes, defaultMultipliers, getBitNodeMultipliers } from "../BitNode/BitNode";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { BitNodeMultipliersDisplay } from "../BitNode/ui/BitnodeMultipliersDescription";
import { HacknetServerConstants } from "../Hacknet/data/Constants";
import { getPurchaseServerLimit } from "../Server/ServerPurchases";
import { Settings } from "../Settings/Settings";
@ -14,6 +15,7 @@ import { Modal } from "./React/Modal";
import { Money } from "./React/Money";
import { StatsRow } from "./React/StatsRow";
import { StatsTable } from "./React/StatsTable";
import { isEqual } from "lodash";
interface EmployersModalProps {
open: boolean;
@ -36,8 +38,22 @@ const EmployersModal = ({ open, onClose }: EmployersModalProps): React.ReactElem
);
};
interface IMultRow {
// The name of the multiplier
mult: string;
// The player's raw multiplier value
value: number;
// The player's effective multiplier value, affected by BitNode mults
effValue?: number;
// The text color for the row
color?: string;
}
interface MultTableProps {
rows: (string | number)[][];
rows: IMultRow[];
color: string;
noMargin?: boolean;
}
@ -48,29 +64,22 @@ function MultiplierTable(props: MultTableProps): React.ReactElement {
<Table sx={{ display: "table", width: "100%", mb: (props.noMargin ?? false) === true ? 0 : 2 }}>
<TableBody>
{props.rows.map((data) => {
const mult = data[0] as string,
value = data[1] as number,
modded = data[2] as number | null;
const { mult, value, effValue = null, color = props.color } = data;
if (modded && modded !== value && player.sourceFileLvl(5) > 0) {
if (effValue !== null && effValue !== value && player.sourceFileLvl(5) > 0) {
return (
<StatsRow key={mult} name={mult} color={props.color} data={{}}>
<StatsRow key={mult} name={mult} color={color} data={{}}>
<>
<Typography color={props.color}>
<Typography color={color}>
<span style={{ opacity: 0.5 }}>{numeralWrapper.formatPercentage(value)}</span>{" "}
{numeralWrapper.formatPercentage(modded)}
{numeralWrapper.formatPercentage(effValue)}
</Typography>
</>
</StatsRow>
);
}
return (
<StatsRow
key={mult}
name={mult}
color={props.color}
data={{ content: numeralWrapper.formatPercentage(value) }}
/>
<StatsRow key={mult} name={mult} color={color} data={{ content: numeralWrapper.formatPercentage(value) }} />
);
})}
</TableBody>
@ -82,16 +91,14 @@ function CurrentBitNode(): React.ReactElement {
const player = use.Player();
if (player.sourceFiles.length > 0) {
const index = "BitNode" + player.bitNodeN;
const lvl = player.sourceFileLvl(player.bitNodeN) + 1;
const lvl = Math.min(player.sourceFileLvl(player.bitNodeN) + 1, player.bitNodeN === 12 ? Infinity : 3);
return (
<Box>
<Paper sx={{ p: 1 }}>
<Paper sx={{ mb: 1, p: 1 }}>
<Typography variant="h5">
BitNode {player.bitNodeN}: {BitNodes[index].name} (Level {lvl})
</Typography>
<Typography sx={{ whiteSpace: "pre-wrap", overflowWrap: "break-word" }}>{BitNodes[index].info}</Typography>
</Paper>
</Box>
);
}
@ -218,6 +225,14 @@ export function CharacterStats(): React.ReactElement {
}
timeRows.push(["Total", convertTimeMsToTimeElapsedString(player.totalPlaytime)]);
let showBitNodeMults = false;
if (player.sourceFileLvl(5) > 0) {
const n = player.bitNodeN;
const maxSfLevel = n === 12 ? Infinity : 3;
const mults = getBitNodeMultipliers(n, Math.min(player.sourceFileLvl(n) + 1, maxSfLevel));
showBitNodeMults = !isEqual(mults, defaultMultipliers);
}
return (
<Container maxWidth="lg" disableGutters sx={{ mx: 0 }}>
<Typography variant="h4">Stats</Typography>
@ -332,8 +347,8 @@ export function CharacterStats(): React.ReactElement {
</Table>
</Paper>
</Box>
<Box sx={{ mb: 1 }}>
<Paper sx={{ p: 1 }}>
<Paper sx={{ p: 1, mb: 1 }}>
<Typography variant="h5" color="primary" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
Multipliers
{player.sourceFileLvl(5) > 0 && (
@ -347,8 +362,8 @@ export function CharacterStats(): React.ReactElement {
affected by BitNode multipliers.
<br />
<br />
The dim number is the raw multiplier, and the undimmed number is the effective multiplier, as
dictated by the BitNode.
The dim number is the raw multiplier, and the undimmed number is the effective multiplier, as dictated
by the BitNode.
</Typography>
}
>
@ -360,88 +375,109 @@ export function CharacterStats(): React.ReactElement {
<Box>
<MultiplierTable
rows={[
["Hacking Chance", player.hacking_chance_mult],
["Hacking Speed", player.hacking_speed_mult],
[
"Hacking Money",
player.hacking_money_mult,
player.hacking_money_mult * BitNodeMultipliers.ScriptHackMoney,
],
[
"Hacking Growth",
player.hacking_grow_mult,
player.hacking_grow_mult * BitNodeMultipliers.ServerGrowthRate,
],
{
mult: "Hacking Chance",
value: player.hacking_chance_mult,
},
{
mult: "Hacking Speed",
value: player.hacking_speed_mult,
},
{
mult: "Hacking Money",
value: player.hacking_money_mult,
effValue: player.hacking_money_mult * BitNodeMultipliers.ScriptHackMoney,
},
{
mult: "Hacking Growth",
value: player.hacking_grow_mult,
effValue: player.hacking_grow_mult * BitNodeMultipliers.ServerGrowthRate,
},
]}
color={Settings.theme.hack}
/>
<MultiplierTable
rows={[
[
"Hacking Level",
player.hacking_mult,
player.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier,
],
[
"Hacking Experience",
player.hacking_exp_mult,
player.hacking_exp_mult * BitNodeMultipliers.HackExpGain,
],
{
mult: "Hacking Level",
value: player.hacking_mult,
effValue: player.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier,
},
{
mult: "Hacking Experience",
value: player.hacking_exp_mult,
effValue: player.hacking_exp_mult * BitNodeMultipliers.HackExpGain,
},
]}
color={Settings.theme.hack}
/>
<MultiplierTable
rows={[
[
"Strength Level",
player.strength_mult,
player.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier,
],
["Strength Experience", player.strength_exp_mult],
{
mult: "Strength Level",
value: player.strength_mult,
effValue: player.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier,
},
{
mult: "Strength Experience",
value: player.strength_exp_mult,
},
]}
color={Settings.theme.combat}
/>
<MultiplierTable
rows={[
[
"Defense Level",
player.defense_mult,
player.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier,
],
["Defense Experience", player.defense_exp_mult],
{
mult: "Defense Level",
value: player.defense_mult,
effValue: player.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier,
},
{
mult: "Defense Experience",
value: player.defense_exp_mult,
},
]}
color={Settings.theme.combat}
/>
<MultiplierTable
rows={[
[
"Dexterity Level",
player.dexterity_mult,
player.dexterity_mult * BitNodeMultipliers.DexterityLevelMultiplier,
],
["Dexterity Experience", player.dexterity_exp_mult],
{
mult: "Dexterity Level",
value: player.dexterity_mult,
effValue: player.dexterity_mult * BitNodeMultipliers.DexterityLevelMultiplier,
},
{
mult: "Dexterity Experience",
value: player.dexterity_exp_mult,
},
]}
color={Settings.theme.combat}
/>
<MultiplierTable
rows={[
[
"Agility Level",
player.agility_mult,
player.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier,
],
["Agility Experience", player.agility_exp_mult],
{
mult: "Agility Level",
value: player.agility_mult,
effValue: player.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier,
},
{
mult: "Agility Experience",
value: player.agility_exp_mult,
},
]}
color={Settings.theme.combat}
/>
<MultiplierTable
rows={[
[
"Charisma Level",
player.charisma_mult,
player.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier,
],
["Charisma Experience", player.charisma_exp_mult],
{
mult: "Charisma Level",
value: player.charisma_mult,
effValue: player.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier,
},
{
mult: "Charisma Experience",
value: player.charisma_exp_mult,
},
]}
color={Settings.theme.cha}
noMargin
@ -451,44 +487,86 @@ export function CharacterStats(): React.ReactElement {
<Box>
<MultiplierTable
rows={[
[
"Hacknet Node production",
player.hacknet_node_money_mult,
player.hacknet_node_money_mult * BitNodeMultipliers.HacknetNodeMoney,
],
["Hacknet Node purchase cost", player.hacknet_node_purchase_cost_mult],
["Hacknet Node RAM upgrade cost", player.hacknet_node_ram_cost_mult],
["Hacknet Node Core purchase cost", player.hacknet_node_core_cost_mult],
["Hacknet Node level upgrade cost", player.hacknet_node_level_cost_mult],
{
mult: "Hacknet Node Production",
value: player.hacknet_node_money_mult,
effValue: player.hacknet_node_money_mult * BitNodeMultipliers.HacknetNodeMoney,
},
{
mult: "Hacknet Node Purchase Cost",
value: player.hacknet_node_purchase_cost_mult,
},
{
mult: "Hacknet Node RAM Upgrade Cost",
value: player.hacknet_node_ram_cost_mult,
},
{
mult: "Hacknet Node Core Purchase Cost",
value: player.hacknet_node_core_cost_mult,
},
{
mult: "Hacknet Node Level Upgrade Cost",
value: player.hacknet_node_level_cost_mult,
},
]}
color={Settings.theme.primary}
/>
<MultiplierTable
rows={[
["Company reputation gain", player.company_rep_mult],
[
"Faction reputation gain",
player.faction_rep_mult,
player.faction_rep_mult * BitNodeMultipliers.FactionWorkRepGain,
],
["Salary", player.work_money_mult, player.work_money_mult * BitNodeMultipliers.CompanyWorkMoney],
{
mult: "Company Reputation Gain",
value: player.company_rep_mult,
color: Settings.theme.rep,
},
{
mult: "Faction Reputation Gain",
value: player.faction_rep_mult,
effValue: player.faction_rep_mult * BitNodeMultipliers.FactionWorkRepGain,
color: Settings.theme.rep,
},
{
mult: "Salary",
value: player.work_money_mult,
effValue: player.work_money_mult * BitNodeMultipliers.CompanyWorkMoney,
color: Settings.theme.money,
},
]}
color={Settings.theme.money}
/>
<MultiplierTable
rows={[
["Crime success", player.crime_success_mult],
["Crime money", player.crime_money_mult, player.crime_money_mult * BitNodeMultipliers.CrimeMoney],
{
mult: "Crime Success Chance",
value: player.crime_success_mult,
},
{
mult: "Crime Money",
value: player.crime_money_mult,
effValue: player.crime_money_mult * BitNodeMultipliers.CrimeMoney,
color: Settings.theme.money,
},
]}
color={Settings.theme.combat}
/>
{player.canAccessBladeburner() && (
<MultiplierTable
rows={[
["Bladeburner Success Chance", player.bladeburner_success_chance_mult],
["Bladeburner Max Stamina", player.bladeburner_max_stamina_mult],
["Bladeburner Stamina Gain", player.bladeburner_stamina_gain_mult],
["Bladeburner Field Analysis", player.bladeburner_analysis_mult],
{
mult: "Bladeburner Success Chance",
value: player.bladeburner_success_chance_mult,
},
{
mult: "Bladeburner Max Stamina",
value: player.bladeburner_max_stamina_mult,
},
{
mult: "Bladeburner Stamina Gain",
value: player.bladeburner_stamina_gain_mult,
},
{
mult: "Bladeburner Field Analysis",
value: player.bladeburner_analysis_mult,
},
]}
color={Settings.theme.primary}
noMargin
@ -497,10 +575,8 @@ export function CharacterStats(): React.ReactElement {
</Box>
</Box>
</Paper>
</Box>
<Box sx={{ mb: 1 }}>
<Paper sx={{ p: 1 }}>
<Paper sx={{ p: 1, mb: 1 }}>
<Typography variant="h5">Time Played</Typography>
<Table>
<TableBody>
@ -510,8 +586,16 @@ export function CharacterStats(): React.ReactElement {
</TableBody>
</Table>
</Paper>
</Box>
<CurrentBitNode />
{showBitNodeMults && (
<Paper sx={{ p: 1, mb: 1 }}>
<Typography variant="h5">BitNode Multipliers</Typography>
<BitNodeMultipliersDisplay n={player.bitNodeN} />
</Paper>
)}
<MoneyModal open={moneyOpen} onClose={() => setMoneyOpen(false)} />
<EmployersModal open={employersOpen} onClose={() => setEmployersOpen(false)} />
</Container>

@ -35,7 +35,7 @@ export function NSSelection(props: IProps): React.ReactElement {
}
return (
<Modal open={props.open} onClose={props.onClose}>
<Modal open={props.open} onClose={props.onClose} sx={{ zIndex: 999999 }}>
<Tabs variant="fullWidth" value={value} onChange={handleChange}>
<Tab label="NS1" />
<Tab label="NS2" />

@ -18,10 +18,12 @@ import { Theme } from "@mui/material";
import { findRunningScript } from "../../Script/ScriptHelpers";
import { Player } from "../../Player";
import { debounce } from "lodash";
import { Settings } from "../../Settings/Settings";
let layerCounter = 0;
export const LogBoxEvents = new EventEmitter<[RunningScript]>();
export const LogBoxCloserEvents = new EventEmitter<[number]>();
export const LogBoxClearEvents = new EventEmitter<[]>();
interface Log {
@ -50,6 +52,15 @@ export function LogBoxManager(): React.ReactElement {
[],
);
//Event used by ns.closeTail to close tail windows
useEffect(
() =>
LogBoxCloserEvents.subscribe((pid: number) => {
closePid(pid);
}),
[],
);
useEffect(() =>
LogBoxClearEvents.subscribe(() => {
logs = [];
@ -57,11 +68,18 @@ export function LogBoxManager(): React.ReactElement {
}),
);
//Close tail windows by their id
function close(id: string): void {
logs = logs.filter((l) => l.id !== id);
rerender();
}
//Close tail windows by their pid
function closePid(pid: number): void {
logs = logs.filter((log) => log.script.pid != pid);
rerender();
}
return (
<>
{logs.map((log) => (
@ -77,42 +95,18 @@ interface IProps {
onClose: () => void;
}
const useStyles = makeStyles((theme: Theme) =>
const useStyles = makeStyles((_theme: Theme) =>
createStyles({
title: {
"&.is-minimized + *": {
border: "none",
margin: 0,
"max-height": 0,
padding: 0,
"pointer-events": "none",
visibility: "hidden",
},
},
logs: {
overflowY: "scroll",
overflowX: "hidden",
scrollbarWidth: "auto",
display: "flex",
flexDirection: "column-reverse",
whiteSpace: "pre-wrap",
},
titleButton: {
padding: "1px 6px",
},
success: {
color: theme.colors.success,
},
error: {
color: theme.palette.error.main,
},
primary: {
color: theme.palette.primary.main,
},
info: {
color: theme.palette.info.main,
},
warning: {
color: theme.palette.warning.main,
padding: "1px 0",
height: "100%",
},
}),
);
@ -190,20 +184,20 @@ function LogWindow(props: IProps): React.ReactElement {
setMinimized(!minimized);
}
function lineClass(s: string): string {
function lineColor(s: string): string {
if (s.match(/(^\[[^\]]+\] )?ERROR/) || s.match(/(^\[[^\]]+\] )?FAIL/)) {
return classes.error;
return Settings.theme.error;
}
if (s.match(/(^\[[^\]]+\] )?SUCCESS/)) {
return classes.success;
return Settings.theme.success;
}
if (s.match(/(^\[[^\]]+\] )?WARN/)) {
return classes.warning;
return Settings.theme.warning;
}
if (s.match(/(^\[[^\]]+\] )?INFO/)) {
return classes.info;
return Settings.theme.info;
}
return classes.primary;
return Settings.theme.primary;
}
// And trigger fakeDrag when the window is resized
@ -242,31 +236,63 @@ function LogWindow(props: IProps): React.ReactElement {
if (e.clientX < 0 || e.clientY < 0 || e.clientX > innerWidth || e.clientY > innerHeight) return false;
};
// Max [width, height]
const minConstraints: [number, number] = [250, 33];
return (
<Draggable handle=".drag" onDrag={boundToBody} ref={rootRef}>
<Paper
style={{
display: "flex",
<Draggable handle=".drag" onDrag={boundToBody} ref={rootRef} onMouseDown={updateLayer}>
<Box
display="flex"
sx={{
flexFlow: "column",
position: "fixed",
left: "40%",
top: "30%",
zIndex: 1400,
minWidth: `${minConstraints[0]}px`,
minHeight: `${minConstraints[1]}px`,
...(minimized
? {
border: "none",
margin: 0,
maxHeight: 0,
padding: 0,
}
: {
border: `1px solid ${Settings.theme.welllight}`,
}),
}}
ref={container}
>
<div onMouseDown={updateLayer}>
<Paper
className={classes.title + " " + (minimized ? "is-minimized" : "")}
<ResizableBox
height={500}
width={500}
minConstraints={minConstraints}
handle={
<span
style={{
cursor: "grab",
position: "absolute",
right: "-10px",
bottom: "-16px",
cursor: "nw-resize",
display: minimized ? "none" : "inline-block",
}}
>
<Box className="drag" display="flex" alignItems="center" ref={draggableRef}>
<Typography color="primary" variant="h6" sx={{ marginRight: "auto" }} title={title(true)}>
{title()}
<ArrowForwardIosIcon color="primary" style={{ transform: "rotate(45deg)", fontSize: "1.75rem" }} />
</span>
}
>
<>
<Paper className="drag" sx={{ display: "flex", alignItems: "center", cursor: "grab" }} ref={draggableRef}>
<Typography
variant="h6"
sx={{ marginRight: "auto", textOverflow: "ellipsis", whiteSpace: "nowrap", overflow: "hidden" }}
title={title(true)}
>
{title(true)}
</Typography>
<span style={{ minWidth: "fit-content", height: `${minConstraints[1]}px` }}>
{!workerScripts.has(script.pid) ? (
<Button className={classes.titleButton} onClick={run} onTouchEnd={run}>
Run
@ -282,34 +308,27 @@ function LogWindow(props: IProps): React.ReactElement {
<Button className={classes.titleButton} onClick={props.onClose} onTouchEnd={props.onClose}>
Close
</Button>
</Box>
</Paper>
<Paper sx={{ overflow: "scroll", overflowWrap: "break-word", whiteSpace: "pre-wrap" }}>
<ResizableBox
className={classes.logs}
height={500}
width={500}
minConstraints={[250, 30]}
handle={
<span style={{ position: "absolute", right: "-10px", bottom: "-13px", cursor: "nw-resize" }}>
<ArrowForwardIosIcon color="primary" style={{ transform: "rotate(45deg)" }} />
</span>
}
</Paper>
<Paper
className={classes.logs}
sx={{ height: `calc(100% - ${minConstraints[1]}px)`, display: minimized ? "none" : "flex" }}
>
<Box>
<span style={{ display: "flex", flexDirection: "column" }}>
{script.logs.map(
(line: string, i: number): JSX.Element => (
<Typography key={i} className={lineClass(line)}>
<Typography key={i} sx={{ color: lineColor(line) }}>
{line}
<br />
</Typography>
),
)}
</Box>
</span>
</Paper>
</>
</ResizableBox>
</Paper>
</div>
</Paper>
</Box>
</Draggable>
);
}

@ -1,10 +1,11 @@
import React from "react";
import { Theme } from "@mui/material";
import makeStyles from "@mui/styles/makeStyles";
import createStyles from "@mui/styles/createStyles";
import M from "@mui/material/Modal";
import Fade from "@mui/material/Fade";
import Box from "@mui/material/Box";
import Fade from "@mui/material/Fade";
import M from "@mui/material/Modal";
import createStyles from "@mui/styles/createStyles";
import makeStyles from "@mui/styles/makeStyles";
import { SxProps } from "@mui/system";
import React from "react";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
@ -12,7 +13,6 @@ const useStyles = makeStyles((theme: Theme) =>
display: "flex",
alignItems: "center",
justifyContent: "center",
zIndex: 999999,
},
paper: {
backgroundColor: theme.palette.background.default,
@ -35,6 +35,7 @@ interface IProps {
open: boolean;
onClose: () => void;
children: React.ReactNode;
sx?: SxProps<Theme>;
}
export const Modal = (props: IProps): React.ReactElement => {
@ -49,6 +50,7 @@ export const Modal = (props: IProps): React.ReactElement => {
onClose={props.onClose}
closeAfterTransition
className={classes.modal}
sx={props.sx}
>
<Fade in={props.open}>
<div className={classes.paper}>

@ -0,0 +1,9 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"checkJs": true,
"types": ["cypress", "@testing-library/cypress"]
},
"include": ["**/*"],
"exclude": []
}

@ -1,5 +1,3 @@
import { jest, describe, expect } from "@jest/globals";
import { Player } from "../../../src/Player";
import { NetscriptFunctions } from "../../../src/NetscriptFunctions";
import { getRamCost, RamCostConstants } from "../../../src/Netscript/RamCostGenerator";

@ -1,5 +1,3 @@
import { jest, describe, expect } from "@jest/globals";
import { Player } from "../../../src/Player";
import { getRamCost, RamCostConstants } from "../../../src/Netscript/RamCostGenerator";
import { calculateRamUsage } from "../../../src/Script/RamCalculations";

@ -1,6 +1,3 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { describe, expect, jest } from "@jest/globals";
// Player is needed for calculating costs like Singularity functions, that depend on acquired source files
import { Player } from "../../../src/Player";

@ -1,6 +1,3 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { jest, describe, expect, test } from "@jest/globals";
import { Script } from "../../../src/Script/Script";
import { Player } from "../../../src/Player";

@ -1,6 +1,3 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { jest, describe, expect, test } from "@jest/globals";
import { CONSTANTS } from "../../src/Constants";
import { Player } from "../../src/Player";
import { IMap } from "../../src/types";

@ -1,5 +1,3 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { jest, describe, expect, test } from "@jest/globals";
import { convertTimeMsToTimeElapsedString } from "../../src/utils/StringHelperFunctions";
describe("StringHelperFunctions Tests", function () {

@ -1,5 +1,3 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { jest, describe, expect, test } from "@jest/globals";
import * as dirHelpers from "../../../src/Terminal/DirectoryHelpers";
describe("Terminal Directory Tests", function () {
@ -102,17 +100,28 @@ describe("Terminal Directory Tests", function () {
expect(isValidDirectoryName(".a1")).toEqual(true);
expect(isValidDirectoryName("._foo")).toEqual(true);
expect(isValidDirectoryName("_foo")).toEqual(true);
expect(isValidDirectoryName("foo.dir")).toEqual(true);
expect(isValidDirectoryName("foov1.0.0.1")).toEqual(true);
expect(isValidDirectoryName("foov1..0..0..1")).toEqual(true);
expect(isValidDirectoryName("foov1-0-0-1")).toEqual(true);
expect(isValidDirectoryName("foov1-0-0-1-")).toEqual(true);
expect(isValidDirectoryName("foov1--0--0--1--")).toEqual(true);
expect(isValidDirectoryName("foov1_0_0_1")).toEqual(true);
expect(isValidDirectoryName("foov1_0_0_1_")).toEqual(true);
expect(isValidDirectoryName("foov1__0__0__1")).toEqual(true);
});
it("should return false for invalid directory names", function () {
expect(isValidDirectoryName("")).toEqual(false);
expect(isValidDirectoryName("foo.dir")).toEqual(false);
expect(isValidDirectoryName("1.")).toEqual(false);
expect(isValidDirectoryName("foo.")).toEqual(false);
expect(isValidDirectoryName("👨‍💻")).toEqual(false);
expect(isValidDirectoryName("dir#")).toEqual(false);
expect(isValidDirectoryName("dir!")).toEqual(false);
expect(isValidDirectoryName("dir*")).toEqual(false);
expect(isValidDirectoryName(".")).toEqual(false);
expect(isValidDirectoryName("..")).toEqual(false);
expect(isValidDirectoryName("1.")).toEqual(false);
expect(isValidDirectoryName("foo.")).toEqual(false);
expect(isValidDirectoryName("foov1.0.0.1.")).toEqual(false);
});
});

@ -1,7 +1,5 @@
import { CityName } from "./../../../src/Locations/data/CityNames";
/* eslint-disable no-await-in-loop */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { jest, describe, expect, test } from "@jest/globals";
import { Player } from "../../../src/Player";
import { determineAllPossibilitiesForTabCompletion } from "../../../src/Terminal/determineAllPossibilitiesForTabCompletion";

@ -1,4 +1,3 @@
import { describe, expect, test } from "@jest/globals";
import { numeralWrapper } from "../../../src/ui/numeralFormat";
let decimalFormat = "0.[000000]";

9
test/tsconfig.json Normal file

@ -0,0 +1,9 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"allowJs": true,
"types": ["jest"]
},
"include": ["**/*", "../src/**/*.d.ts"],
"exclude": ["cypress/**/*"]
}

@ -4,13 +4,13 @@
"esModuleInterop": true,
"isolatedModules": true,
"jsx": "react",
"lib": ["es2016", "dom", "es2017.object", "es2019"],
"module": "commonjs",
"target": "es6",
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"resolveJsonModule": true,
"types": ["cypress", "@testing-library/cypress", "node"]
"target": "es2019"
},
"exclude": ["node_modules"]
"include": ["src/**/*"]
}