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 # DELETE THIS AFTER READING
# READ CONTRIBUTING.md
# PR title # PR title
Formatted as such: 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/bcryptjs": "^2.4.2",
"@types/escodegen": "^0.0.7", "@types/escodegen": "^0.0.7",
"@types/file-saver": "^2.0.3", "@types/file-saver": "^2.0.3",
<<<<<<< HEAD
=======
"@types/jest": "^27.4.1",
>>>>>>> dev
"@types/jquery": "^3.5.14", "@types/jquery": "^3.5.14",
"@types/lodash": "^4.14.168", "@types/lodash": "^4.14.168",
"@types/numeral": "^2.0.2", "@types/numeral": "^2.0.2",
@ -2068,19 +2072,33 @@
"integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
}, },
"node_modules/@eslint/eslintrc": { "node_modules/@eslint/eslintrc": {
<<<<<<< HEAD
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.2.tgz", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.2.tgz",
"integrity": "sha512-lTVWHs7O2hjBFZunXTZYnYqtB9GakA1lnxIf+gKq2nY5gxkkNi/lQvveW6t8gFdOHTg6nG50Xs95PrLqVpcaLg==", "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, "dev": true,
"dependencies": { "dependencies": {
"ajv": "^6.12.4", "ajv": "^6.12.4",
"debug": "^4.3.2", "debug": "^4.3.2",
<<<<<<< HEAD
"espree": "^9.3.1", "espree": "^9.3.1",
=======
"espree": "^9.3.2",
>>>>>>> dev
"globals": "^13.9.0", "globals": "^13.9.0",
"ignore": "^5.2.0", "ignore": "^5.2.0",
"import-fresh": "^3.2.1", "import-fresh": "^3.2.1",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
<<<<<<< HEAD
"minimatch": "^3.0.4", "minimatch": "^3.0.4",
=======
"minimatch": "^3.1.2",
>>>>>>> dev
"strip-json-comments": "^3.1.1" "strip-json-comments": "^3.1.1"
}, },
"engines": { "engines": {
@ -2094,9 +2112,15 @@
"dev": true "dev": true
}, },
"node_modules/@eslint/eslintrc/node_modules/globals": { "node_modules/@eslint/eslintrc/node_modules/globals": {
<<<<<<< HEAD
"version": "13.13.0", "version": "13.13.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz", "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz",
"integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==", "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, "dev": true,
"dependencies": { "dependencies": {
"type-fest": "^0.20.2" "type-fest": "^0.20.2"
@ -4062,6 +4086,19 @@
"@types/istanbul-lib-report": "*" "@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": { "node_modules/@types/jquery": {
"version": "3.5.14", "version": "3.5.14",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.14.tgz", "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.14.tgz",
@ -4230,6 +4267,7 @@
} }
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
<<<<<<< HEAD
"version": "5.21.0", "version": "5.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.21.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.21.0.tgz",
"integrity": "sha512-fTU85q8v5ZLpoZEyn/u1S2qrFOhi33Edo2CZ0+q1gDaWWm0JuPh3bgOyU8lM0edIEYgKLDkPFiZX2MOupgjlyg==", "integrity": "sha512-fTU85q8v5ZLpoZEyn/u1S2qrFOhi33Edo2CZ0+q1gDaWWm0JuPh3bgOyU8lM0edIEYgKLDkPFiZX2MOupgjlyg==",
@ -4243,6 +4281,21 @@
"ignore": "^5.1.8", "ignore": "^5.1.8",
"regexpp": "^3.2.0", "regexpp": "^3.2.0",
"semver": "^7.3.5", "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" "tsutils": "^3.21.0"
}, },
"engines": { "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": { "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": {
"version": "7.3.5", "version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"lru-cache": "^6.0.0" "lru-cache": "^6.0.0"
@ -4278,6 +4348,7 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
<<<<<<< HEAD
"version": "5.21.0", "version": "5.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.21.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.21.0.tgz",
"integrity": "sha512-8RUwTO77hstXUr3pZoWZbRQUxXcSXafZ8/5gpnQCfXvgmP9gpNlRGlWzvfbEQ14TLjmtU8eGnONkff8U2ui2Eg==", "integrity": "sha512-8RUwTO77hstXUr3pZoWZbRQUxXcSXafZ8/5gpnQCfXvgmP9gpNlRGlWzvfbEQ14TLjmtU8eGnONkff8U2ui2Eg==",
@ -4287,6 +4358,17 @@
"@typescript-eslint/types": "5.21.0", "@typescript-eslint/types": "5.21.0",
"@typescript-eslint/typescript-estree": "5.21.0", "@typescript-eslint/typescript-estree": "5.21.0",
"debug": "^4.3.2" "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": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -4304,6 +4386,7 @@
} }
} }
}, },
<<<<<<< HEAD
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "5.21.0", "version": "5.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.21.0.tgz", "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" "@typescript-eslint/visitor-keys": "5.21.0"
}, },
"engines": { "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" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}, },
"funding": { "funding": {
@ -4321,6 +4432,26 @@
"url": "https://opencollective.com/typescript-eslint" "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": { "node_modules/@typescript-eslint/type-utils": {
"version": "5.21.0", "version": "5.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.21.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.21.0.tgz",
@ -4343,14 +4474,45 @@
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"typescript": { "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 "optional": true
} }
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
<<<<<<< HEAD
"version": "5.21.0", "version": "5.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.21.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.21.0.tgz",
"integrity": "sha512-XnOOo5Wc2cBlq8Lh5WNvAgHzpjnEzxn4CJBwGkcau7b/tZ556qrWXQz4DJyChYg8JZAD06kczrdgFPpEQZfDsA==", "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, "dev": true,
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -4361,6 +4523,7 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
<<<<<<< HEAD
"version": "5.21.0", "version": "5.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.21.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.21.0.tgz",
"integrity": "sha512-Y8Y2T2FNvm08qlcoSMoNchh9y2Uj3QmjtwNMdRQkcFG7Muz//wfJBGBxh8R7HAGQFpgYpdHqUpEoPQk+q9Kjfg==", "integrity": "sha512-Y8Y2T2FNvm08qlcoSMoNchh9y2Uj3QmjtwNMdRQkcFG7Muz//wfJBGBxh8R7HAGQFpgYpdHqUpEoPQk+q9Kjfg==",
@ -4372,6 +4535,19 @@
"globby": "^11.0.4", "globby": "^11.0.4",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
"semver": "^7.3.5", "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" "tsutils": "^3.21.0"
}, },
"engines": { "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": { "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
"version": "7.3.7", "version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
@ -4403,6 +4596,7 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
<<<<<<< HEAD
"version": "5.21.0", "version": "5.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.21.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.21.0.tgz",
"integrity": "sha512-q/emogbND9wry7zxy7VYri+7ydawo2HDZhRZ5k6yggIvXa7PvBbAAZ4PFH/oZLem72ezC4Pr63rJvDK/sTlL8Q==", "integrity": "sha512-q/emogbND9wry7zxy7VYri+7ydawo2HDZhRZ5k6yggIvXa7PvBbAAZ4PFH/oZLem72ezC4Pr63rJvDK/sTlL8Q==",
@ -4412,6 +4606,17 @@
"@typescript-eslint/scope-manager": "5.21.0", "@typescript-eslint/scope-manager": "5.21.0",
"@typescript-eslint/types": "5.21.0", "@typescript-eslint/types": "5.21.0",
"@typescript-eslint/typescript-estree": "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-scope": "^5.1.1",
"eslint-utils": "^3.0.0" "eslint-utils": "^3.0.0"
}, },
@ -4427,6 +4632,7 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
<<<<<<< HEAD
"version": "5.21.0", "version": "5.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.21.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.21.0.tgz",
"integrity": "sha512-SX8jNN+iHqAF0riZQMkm7e8+POXa/fXw5cxL+gjpyP+FI+JVNhii53EmQgDAfDcBpFekYSlO0fGytMQwRiMQCA==", "integrity": "sha512-SX8jNN+iHqAF0riZQMkm7e8+POXa/fXw5cxL+gjpyP+FI+JVNhii53EmQgDAfDcBpFekYSlO0fGytMQwRiMQCA==",
@ -4434,6 +4640,15 @@
"dependencies": { "dependencies": {
"@typescript-eslint/types": "5.21.0", "@typescript-eslint/types": "5.21.0",
"eslint-visitor-keys": "^3.0.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": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -4650,9 +4865,15 @@
} }
}, },
"node_modules/acorn": { "node_modules/acorn": {
<<<<<<< HEAD
"version": "8.7.0", "version": "8.7.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", "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": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@ -8244,12 +8465,21 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
<<<<<<< HEAD
"version": "8.14.0", "version": "8.14.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.14.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.14.0.tgz",
"integrity": "sha512-3/CE4aJX7LNEiE3i6FeodHmI/38GZtWCsAtsymScmzYapx8q1nVVb+eLcLSzATmCPXw5pT4TqVs1E0OmxAd9tw==", "integrity": "sha512-3/CE4aJX7LNEiE3i6FeodHmI/38GZtWCsAtsymScmzYapx8q1nVVb+eLcLSzATmCPXw5pT4TqVs1E0OmxAd9tw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint/eslintrc": "^1.2.2", "@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", "@humanwhocodes/config-array": "^0.9.2",
"ajv": "^6.10.0", "ajv": "^6.10.0",
"chalk": "^4.0.0", "chalk": "^4.0.0",
@ -8260,7 +8490,11 @@
"eslint-scope": "^7.1.1", "eslint-scope": "^7.1.1",
"eslint-utils": "^3.0.0", "eslint-utils": "^3.0.0",
"eslint-visitor-keys": "^3.3.0", "eslint-visitor-keys": "^3.3.0",
<<<<<<< HEAD
"espree": "^9.3.1", "espree": "^9.3.1",
=======
"espree": "^9.3.2",
>>>>>>> dev
"esquery": "^1.4.0", "esquery": "^1.4.0",
"esutils": "^2.0.2", "esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
@ -8276,7 +8510,7 @@
"json-stable-stringify-without-jsonify": "^1.0.1", "json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1", "levn": "^0.4.1",
"lodash.merge": "^4.6.2", "lodash.merge": "^4.6.2",
"minimatch": "^3.0.4", "minimatch": "^3.1.2",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
"optionator": "^0.9.1", "optionator": "^0.9.1",
"regexpp": "^3.2.0", "regexpp": "^3.2.0",
@ -8519,6 +8753,7 @@
} }
}, },
"node_modules/espree": { "node_modules/espree": {
<<<<<<< HEAD
"version": "9.3.1", "version": "9.3.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz", "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz",
"integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==", "integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==",
@ -8526,6 +8761,15 @@
"dependencies": { "dependencies": {
"acorn": "^8.7.0", "acorn": "^8.7.0",
"acorn-jsx": "^5.3.1", "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" "eslint-visitor-keys": "^3.3.0"
}, },
"engines": { "engines": {
@ -15103,9 +15347,9 @@
"dev": true "dev": true
}, },
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.0.4", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
@ -23764,19 +24008,33 @@
"integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
}, },
"@eslint/eslintrc": { "@eslint/eslintrc": {
<<<<<<< HEAD
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.2.tgz", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.2.tgz",
"integrity": "sha512-lTVWHs7O2hjBFZunXTZYnYqtB9GakA1lnxIf+gKq2nY5gxkkNi/lQvveW6t8gFdOHTg6nG50Xs95PrLqVpcaLg==", "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, "dev": true,
"requires": { "requires": {
"ajv": "^6.12.4", "ajv": "^6.12.4",
"debug": "^4.3.2", "debug": "^4.3.2",
<<<<<<< HEAD
"espree": "^9.3.1", "espree": "^9.3.1",
=======
"espree": "^9.3.2",
>>>>>>> dev
"globals": "^13.9.0", "globals": "^13.9.0",
"ignore": "^5.2.0", "ignore": "^5.2.0",
"import-fresh": "^3.2.1", "import-fresh": "^3.2.1",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
<<<<<<< HEAD
"minimatch": "^3.0.4", "minimatch": "^3.0.4",
=======
"minimatch": "^3.1.2",
>>>>>>> dev
"strip-json-comments": "^3.1.1" "strip-json-comments": "^3.1.1"
}, },
"dependencies": { "dependencies": {
@ -23787,9 +24045,15 @@
"dev": true "dev": true
}, },
"globals": { "globals": {
<<<<<<< HEAD
"version": "13.13.0", "version": "13.13.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz", "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz",
"integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==", "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, "dev": true,
"requires": { "requires": {
"type-fest": "^0.20.2" "type-fest": "^0.20.2"
@ -25205,6 +25469,19 @@
"@types/istanbul-lib-report": "*" "@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": { "@types/jquery": {
"version": "3.5.14", "version": "3.5.14",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.14.tgz", "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.14.tgz",
@ -25373,6 +25650,7 @@
} }
}, },
"@typescript-eslint/eslint-plugin": { "@typescript-eslint/eslint-plugin": {
<<<<<<< HEAD
"version": "5.21.0", "version": "5.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.21.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.21.0.tgz",
"integrity": "sha512-fTU85q8v5ZLpoZEyn/u1S2qrFOhi33Edo2CZ0+q1gDaWWm0JuPh3bgOyU8lM0edIEYgKLDkPFiZX2MOupgjlyg==", "integrity": "sha512-fTU85q8v5ZLpoZEyn/u1S2qrFOhi33Edo2CZ0+q1gDaWWm0JuPh3bgOyU8lM0edIEYgKLDkPFiZX2MOupgjlyg==",
@ -25386,13 +25664,37 @@
"ignore": "^5.1.8", "ignore": "^5.1.8",
"regexpp": "^3.2.0", "regexpp": "^3.2.0",
"semver": "^7.3.5", "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" "tsutils": "^3.21.0"
}, },
"dependencies": { "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": { "semver": {
"version": "7.3.5", "version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
"dev": true, "dev": true,
"requires": { "requires": {
"lru-cache": "^6.0.0" "lru-cache": "^6.0.0"
@ -25401,6 +25703,7 @@
} }
}, },
"@typescript-eslint/parser": { "@typescript-eslint/parser": {
<<<<<<< HEAD
"version": "5.21.0", "version": "5.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.21.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.21.0.tgz",
"integrity": "sha512-8RUwTO77hstXUr3pZoWZbRQUxXcSXafZ8/5gpnQCfXvgmP9gpNlRGlWzvfbEQ14TLjmtU8eGnONkff8U2ui2Eg==", "integrity": "sha512-8RUwTO77hstXUr3pZoWZbRQUxXcSXafZ8/5gpnQCfXvgmP9gpNlRGlWzvfbEQ14TLjmtU8eGnONkff8U2ui2Eg==",
@ -25451,9 +25754,92 @@
"globby": "^11.0.4", "globby": "^11.0.4",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
"semver": "^7.3.5", "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" "tsutils": "^3.21.0"
}, },
"dependencies": { "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": { "semver": {
"version": "7.3.7", "version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
@ -25466,6 +25852,7 @@
} }
}, },
"@typescript-eslint/utils": { "@typescript-eslint/utils": {
<<<<<<< HEAD
"version": "5.21.0", "version": "5.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.21.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.21.0.tgz",
"integrity": "sha512-q/emogbND9wry7zxy7VYri+7ydawo2HDZhRZ5k6yggIvXa7PvBbAAZ4PFH/oZLem72ezC4Pr63rJvDK/sTlL8Q==", "integrity": "sha512-q/emogbND9wry7zxy7VYri+7ydawo2HDZhRZ5k6yggIvXa7PvBbAAZ4PFH/oZLem72ezC4Pr63rJvDK/sTlL8Q==",
@ -25475,11 +25862,23 @@
"@typescript-eslint/scope-manager": "5.21.0", "@typescript-eslint/scope-manager": "5.21.0",
"@typescript-eslint/types": "5.21.0", "@typescript-eslint/types": "5.21.0",
"@typescript-eslint/typescript-estree": "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-scope": "^5.1.1",
"eslint-utils": "^3.0.0" "eslint-utils": "^3.0.0"
} }
}, },
"@typescript-eslint/visitor-keys": { "@typescript-eslint/visitor-keys": {
<<<<<<< HEAD
"version": "5.21.0", "version": "5.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.21.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.21.0.tgz",
"integrity": "sha512-SX8jNN+iHqAF0riZQMkm7e8+POXa/fXw5cxL+gjpyP+FI+JVNhii53EmQgDAfDcBpFekYSlO0fGytMQwRiMQCA==", "integrity": "sha512-SX8jNN+iHqAF0riZQMkm7e8+POXa/fXw5cxL+gjpyP+FI+JVNhii53EmQgDAfDcBpFekYSlO0fGytMQwRiMQCA==",
@ -25487,6 +25886,15 @@
"requires": { "requires": {
"@typescript-eslint/types": "5.21.0", "@typescript-eslint/types": "5.21.0",
"eslint-visitor-keys": "^3.0.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": { "@webassemblyjs/ast": {
@ -25693,9 +26101,15 @@
} }
}, },
"acorn": { "acorn": {
<<<<<<< HEAD
"version": "8.7.0", "version": "8.7.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==" "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": { "acorn-globals": {
"version": "4.3.4", "version": "4.3.4",
@ -28606,12 +29020,21 @@
} }
}, },
"eslint": { "eslint": {
<<<<<<< HEAD
"version": "8.14.0", "version": "8.14.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.14.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.14.0.tgz",
"integrity": "sha512-3/CE4aJX7LNEiE3i6FeodHmI/38GZtWCsAtsymScmzYapx8q1nVVb+eLcLSzATmCPXw5pT4TqVs1E0OmxAd9tw==", "integrity": "sha512-3/CE4aJX7LNEiE3i6FeodHmI/38GZtWCsAtsymScmzYapx8q1nVVb+eLcLSzATmCPXw5pT4TqVs1E0OmxAd9tw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@eslint/eslintrc": "^1.2.2", "@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", "@humanwhocodes/config-array": "^0.9.2",
"ajv": "^6.10.0", "ajv": "^6.10.0",
"chalk": "^4.0.0", "chalk": "^4.0.0",
@ -28622,7 +29045,11 @@
"eslint-scope": "^7.1.1", "eslint-scope": "^7.1.1",
"eslint-utils": "^3.0.0", "eslint-utils": "^3.0.0",
"eslint-visitor-keys": "^3.3.0", "eslint-visitor-keys": "^3.3.0",
<<<<<<< HEAD
"espree": "^9.3.1", "espree": "^9.3.1",
=======
"espree": "^9.3.2",
>>>>>>> dev
"esquery": "^1.4.0", "esquery": "^1.4.0",
"esutils": "^2.0.2", "esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
@ -28638,7 +29065,7 @@
"json-stable-stringify-without-jsonify": "^1.0.1", "json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1", "levn": "^0.4.1",
"lodash.merge": "^4.6.2", "lodash.merge": "^4.6.2",
"minimatch": "^3.0.4", "minimatch": "^3.1.2",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
"optionator": "^0.9.1", "optionator": "^0.9.1",
"regexpp": "^3.2.0", "regexpp": "^3.2.0",
@ -28807,6 +29234,7 @@
"integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==" "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA=="
}, },
"espree": { "espree": {
<<<<<<< HEAD
"version": "9.3.1", "version": "9.3.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz", "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz",
"integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==", "integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==",
@ -28814,6 +29242,15 @@
"requires": { "requires": {
"acorn": "^8.7.0", "acorn": "^8.7.0",
"acorn-jsx": "^5.3.1", "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" "eslint-visitor-keys": "^3.3.0"
} }
}, },
@ -33925,9 +34362,9 @@
"dev": true "dev": true
}, },
"minimatch": { "minimatch": {
"version": "3.0.4", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true, "dev": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"

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

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

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

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

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

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

@ -1,554 +1,335 @@
import ExpandMore from "@mui/icons-material/ExpandMore";
import ExpandLess from "@mui/icons-material/ExpandLess"; 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 React from "react";
import { SpecialServers } from "../../Server/data/SpecialServers";
import { Settings } from "../../Settings/Settings";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
import { StatsRow } from "../../ui/React/StatsRow";
import { defaultMultipliers, getBitNodeMultipliers } from "../BitNode"; import { defaultMultipliers, getBitNodeMultipliers } from "../BitNode";
import { IBitNodeMultipliers } from "../BitNodeMultipliers"; import { IBitNodeMultipliers } from "../BitNodeMultipliers";
import { SpecialServers } from "../../Server/data/SpecialServers";
interface IProps { interface IProps {
n: number; n: number;
level?: number;
} }
export function BitnodeMultiplierDescription({ n }: IProps): React.ReactElement { export function BitnodeMultiplierDescription({ n, level }: IProps): React.ReactElement {
const player = use.Player();
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const mults = getBitNodeMultipliers(n, player.sourceFileLvl(n));
if (n === 1) return <></>; if (n === 1) return <></>;
return ( return (
<> <Box component={Paper} sx={{ mt: 1, p: 1 }}>
<br /> <ListItemButton disableGutters onClick={() => setOpen((old) => !old)}>
<Box component={Paper}> <ListItemText primary={<Typography variant="h6">Bitnode Multipliers</Typography>} />
<ListItemButton onClick={() => setOpen((old) => !old)}> {open ? <ExpandLess color="primary" /> : <ExpandMore color="primary" />}
<ListItemText primary={<Typography>Bitnode multipliers:</Typography>} /> </ListItemButton>
{open ? <ExpandLess color="primary" /> : <ExpandMore color="primary" />} <Collapse in={open}>
</ListItemButton> <BitNodeMultipliersDisplay n={n} level={level} />
<Box mx={2}> </Collapse>
<Collapse in={open}> </Box>
<GeneralMults 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} />
<CrimeMults n={n} mults={mults} />
<InfiltrationMults n={n} mults={mults} />
<CompanyMults n={n} mults={mults} />
<GangMults n={n} mults={mults} />
<CorporationMults n={n} mults={mults} />
<BladeburnerMults n={n} mults={mults} />
<StanekMults n={n} mults={mults} />
<br />
</Collapse>
</Box>
</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} />
<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} />
<GangMults n={n} mults={mults} />
<CorporationMults n={n} mults={mults} />
<BladeburnerMults n={n} mults={mults} />
<StanekMults n={n} mults={mults} />
</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 { interface IMultsProps {
n: number; n: number;
mults: IBitNodeMultipliers; mults: IBitNodeMultipliers;
} }
function GeneralMults({ mults }: IMultsProps): React.ReactElement { function GeneralMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check const rows: IBNMultRows = {
if ( WorldDaemonDifficulty: { name: `${SpecialServers.WorldDaemon} Difficulty` },
mults.ClassGymExpGain === defaultMultipliers.ClassGymExpGain && DaedalusAugsRequirement: {
mults.CodingContractMoney === defaultMultipliers.CodingContractMoney && name: "Daedalus Augs Requirement",
mults.DaedalusAugsRequirement === defaultMultipliers.DaedalusAugsRequirement && content: String(mults.DaedalusAugsRequirement),
mults.WorldDaemonDifficulty === defaultMultipliers.WorldDaemonDifficulty && },
mults.HacknetNodeMoney === defaultMultipliers.HacknetNodeMoney HacknetNodeMoney: { name: "Hacknet Production" },
) CodingContractMoney: { name: "Coding Contract Reward" },
return <></>; ClassGymExpGain: { name: "Class/Gym Exp" },
return ( };
<>
<br /> return <BNMultTable sectionName="General" rowData={rows} mults={mults} />;
<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>
</>
);
} }
function AugmentationMults({ mults }: IMultsProps): React.ReactElement { function AugmentationMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check const rows: IBNMultRows = {
if ( AugmentationMoneyCost: { name: "Money Cost" },
mults.AugmentationMoneyCost === defaultMultipliers.AugmentationMoneyCost && AugmentationRepCost: {
mults.AugmentationRepCost === defaultMultipliers.AugmentationRepCost name: "Reputation Cost",
) color: Settings.theme.rep,
return <></>; },
return ( };
<>
<br /> return <BNMultTable sectionName="Augmentations" rowData={rows} mults={mults} />;
<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>
</>
);
} }
function CompanyMults({ mults }: IMultsProps): React.ReactElement { function CompanyMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check const rows: IBNMultRows = {
if ( CompanyWorkMoney: {
mults.CompanyWorkExpGain === defaultMultipliers.CompanyWorkExpGain && name: "Work Money",
mults.CompanyWorkMoney === defaultMultipliers.CompanyWorkMoney color: Settings.theme.money,
) },
return <></>; CompanyWorkExpGain: { name: "Work Exp" },
return ( };
<>
<br /> return <BNMultTable sectionName="Company" rowData={rows} mults={mults} />;
<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>
</>
);
} }
function StockMults({ mults }: IMultsProps): React.ReactElement { function StockMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check const rows: IBNMultRows = {
if ( FourSigmaMarketDataCost: { name: "Market Data Cost" },
mults.FourSigmaMarketDataApiCost === defaultMultipliers.FourSigmaMarketDataApiCost && FourSigmaMarketDataApiCost: { name: "Market Data API Cost" },
mults.FourSigmaMarketDataCost === defaultMultipliers.FourSigmaMarketDataCost };
)
return <></>; return <BNMultTable sectionName="Stock Market" rowData={rows} mults={mults} />;
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>
</>
);
} }
function FactionMults({ mults }: IMultsProps): React.ReactElement { function FactionMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check const rows: IBNMultRows = {
if ( RepToDonateToFaction: { name: "Favor to Donate" },
mults.FactionPassiveRepGain === defaultMultipliers.FactionPassiveRepGain && FactionWorkRepGain: {
mults.FactionWorkExpGain === defaultMultipliers.FactionWorkExpGain && name: "Work Reputation",
mults.FactionWorkRepGain === defaultMultipliers.FactionWorkRepGain && color: Settings.theme.rep,
mults.RepToDonateToFaction === defaultMultipliers.RepToDonateToFaction },
) FactionWorkExpGain: { name: "Work Exp" },
return <></>; FactionPassiveRepGain: {
return ( name: "Passive Rep",
<> color: Settings.theme.rep,
<br /> },
<Typography variant={"h5"}>Faction:</Typography> };
<Box mx={1}>
{mults.RepToDonateToFaction !== defaultMultipliers.RepToDonateToFaction ? ( return <BNMultTable sectionName="Faction" rowData={rows} mults={mults} />;
<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>
</>
);
} }
function CrimeMults({ mults }: IMultsProps): React.ReactElement { function CrimeMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check const rows: IBNMultRows = {
if (mults.CrimeExpGain === defaultMultipliers.CrimeExpGain && mults.CrimeMoney === defaultMultipliers.CrimeMoney) CrimeExpGain: {
return <></>; name: "Crime Exp",
return ( color: Settings.theme.combat,
<> },
<br /> CrimeMoney: {
<Typography variant={"h5"}>Crime:</Typography> name: "Crime Money",
<Box mx={1}> color: Settings.theme.combat,
{mults.CrimeExpGain !== defaultMultipliers.CrimeExpGain ? ( },
<Typography>Exp: x{mults.CrimeExpGain.toFixed(3)}</Typography> };
) : (
<></> return <BNMultTable sectionName="Crime" rowData={rows} mults={mults} />;
)}
{mults.CrimeMoney !== defaultMultipliers.CrimeMoney ? (
<Typography>Money: x{mults.CrimeMoney.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
} }
function SkillMults({ mults }: IMultsProps): React.ReactElement { function SkillMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check const rows: IBNMultRows = {
if ( HackingLevelMultiplier: {
mults.HackingLevelMultiplier === defaultMultipliers.HackingLevelMultiplier && name: "Hacking Level",
mults.AgilityLevelMultiplier === defaultMultipliers.AgilityLevelMultiplier && color: Settings.theme.hack,
mults.DefenseLevelMultiplier === defaultMultipliers.DefenseLevelMultiplier && },
mults.DexterityLevelMultiplier === defaultMultipliers.DexterityLevelMultiplier && StrengthLevelMultiplier: {
mults.StrengthLevelMultiplier === defaultMultipliers.StrengthLevelMultiplier && name: "Strength Level",
mults.CharismaLevelMultiplier === defaultMultipliers.CharismaLevelMultiplier color: Settings.theme.combat,
) },
return <></>; DefenseLevelMultiplier: {
return ( name: "Defense Level",
<> color: Settings.theme.combat,
<br /> },
<Typography variant={"h5"}>Skills:</Typography> DexterityLevelMultiplier: {
<Box mx={1}> name: "Dexterity Level",
{mults.HackingLevelMultiplier !== defaultMultipliers.HackingLevelMultiplier ? ( color: Settings.theme.combat,
<Typography>Hacking: x{mults.HackingLevelMultiplier.toFixed(3)}</Typography> },
) : ( AgilityLevelMultiplier: {
<></> name: "Agility Level",
)} color: Settings.theme.combat,
{mults.AgilityLevelMultiplier !== defaultMultipliers.AgilityLevelMultiplier ? ( },
<Typography>Agility: x{mults.AgilityLevelMultiplier.toFixed(3)}</Typography> CharismaLevelMultiplier: {
) : ( name: "Charisma Level",
<></> color: Settings.theme.cha,
)} },
{mults.DefenseLevelMultiplier !== defaultMultipliers.DefenseLevelMultiplier ? ( };
<Typography>Defense: x{mults.DefenseLevelMultiplier.toFixed(3)}</Typography>
) : ( return <BNMultTable sectionName="Skills" rowData={rows} mults={mults} />;
<></>
)}
{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>
</>
);
} }
function HackingMults({ mults }: IMultsProps): React.ReactElement { function HackingMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check const rows: IBNMultRows = {
if ( HackExpGain: {
mults.ServerGrowthRate === defaultMultipliers.ServerGrowthRate && name: "Hacking Exp",
mults.ServerMaxMoney === defaultMultipliers.ServerMaxMoney && color: Settings.theme.hack,
mults.ServerStartingMoney === defaultMultipliers.ServerStartingMoney && },
mults.ServerStartingSecurity === defaultMultipliers.ServerStartingSecurity && ServerGrowthRate: { name: "Server Growth Rate" },
mults.ServerWeakenRate === defaultMultipliers.ServerWeakenRate && ServerMaxMoney: { name: "Server Max Money" },
mults.ManualHackMoney === defaultMultipliers.ManualHackMoney && ServerStartingMoney: { name: "Server Starting Money" },
mults.ScriptHackMoney === defaultMultipliers.ScriptHackMoney && ServerStartingSecurity: { name: "Server Starting Security" },
mults.ScriptHackMoneyGain === defaultMultipliers.ScriptHackMoneyGain && ServerWeakenRate: { name: "Server Weaken Rate" },
mults.HackExpGain === defaultMultipliers.HackExpGain ManualHackMoney: {
) name: "Manual Hack Money",
return <></>; color: Settings.theme.money,
},
ScriptHackMoney: {
name: "Script Hack Money",
color: Settings.theme.money,
},
ScriptHackMoneyGain: {
name: "Money Gained From Hack",
color: Settings.theme.money,
},
};
return ( return <BNMultTable sectionName="Hacking" rowData={rows} mults={mults} />;
<>
<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>
</>
);
} }
function PurchasedServersMults({ mults }: IMultsProps): React.ReactElement { function PurchasedServersMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check const rows: IBNMultRows = {
if ( PurchasedServerCost: {
mults.PurchasedServerCost === defaultMultipliers.PurchasedServerCost && name: "Base Cost",
mults.PurchasedServerSoftcap === defaultMultipliers.PurchasedServerSoftcap && content: mults.PurchasedServerCost.toFixed(3),
mults.PurchasedServerLimit === defaultMultipliers.PurchasedServerLimit && },
mults.PurchasedServerMaxRam === defaultMultipliers.PurchasedServerMaxRam && PurchasedServerSoftcap: {
mults.HomeComputerRamCost === defaultMultipliers.HomeComputerRamCost name: "Softcap Cost",
) content: mults.PurchasedServerSoftcap.toFixed(3),
return <></>; },
return ( PurchasedServerLimit: { name: "Server Limit" },
<> PurchasedServerMaxRam: { name: "Max RAM" },
<br /> HomeComputerRamCost: { name: "Home RAM Cost" },
<Typography variant={"h5"}>Purchased servers:</Typography> };
<Box mx={1}>
{mults.PurchasedServerCost !== defaultMultipliers.PurchasedServerCost ? ( return <BNMultTable sectionName="Purchased Servers" rowData={rows} mults={mults} />;
<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>
</>
);
} }
function InfiltrationMults({ mults }: IMultsProps): React.ReactElement { function InfiltrationMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check const rows: IBNMultRows = {
if ( InfiltrationMoney: {
mults.InfiltrationMoney === defaultMultipliers.InfiltrationMoney && name: "Infiltration Money",
mults.InfiltrationRep === defaultMultipliers.InfiltrationRep color: Settings.theme.money,
) },
return <></>; InfiltrationRep: {
return ( name: "Infiltration Reputation",
<> color: Settings.theme.rep,
<br /> },
<Typography variant={"h5"}>Infiltration:</Typography> };
<Box mx={1}>
{mults.InfiltrationMoney !== defaultMultipliers.InfiltrationMoney ? ( return <BNMultTable sectionName="Infiltration" rowData={rows} mults={mults} />;
<Typography>Money: {mults.InfiltrationMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.InfiltrationRep !== defaultMultipliers.InfiltrationRep ? (
<Typography>Reputation: x{mults.InfiltrationRep.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
} }
function BladeburnerMults({ n, mults }: IMultsProps): React.ReactElement { function BladeburnerMults({ mults }: IMultsProps): React.ReactElement {
const player = use.Player(); const player = use.Player();
// access check if (!player.canAccessBladeburner()) return <></>;
if (n !== 6 && n !== 7 && player.sourceFileLvl(6) === 0) return <></>;
//default mults check const rows: IBNMultRows = {
if (mults.BladeburnerRank === 1 && mults.BladeburnerSkillCost === 1) return <></>; BladeburnerRank: { name: "Rank Gain" },
return ( BladeburnerSkillCost: { name: "Skill Cost" },
<> };
<br />
<Typography variant={"h5"}>Bladeburner:</Typography> return <BNMultTable sectionName="Bladeburner" rowData={rows} mults={mults} />;
<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>
</>
);
} }
function StanekMults({ n, mults }: IMultsProps): React.ReactElement { function StanekMults({ mults }: IMultsProps): React.ReactElement {
const player = use.Player(); const player = use.Player();
// access check if (!player.canAccessCotMG()) return <></>;
if (n !== 13 && player.sourceFileLvl(13) === 0) return <></>;
//default mults check
if (
mults.StaneksGiftExtraSize === defaultMultipliers.StaneksGiftExtraSize &&
mults.StaneksGiftPowerMultiplier === defaultMultipliers.StaneksGiftPowerMultiplier
)
return <></>;
const s = mults.StaneksGiftExtraSize; const extraSize = mults.StaneksGiftExtraSize.toFixed(3);
return ( const rows: IBNMultRows = {
<> StnakesGiftPowerMultiplier: { name: "Gift Power" },
<br /> StaneksGiftExtraSize: {
<Typography variant={"h5"}>Stanek's Gift:</Typography> name: "Base Size Modifier",
<Box mx={1}> content: `${mults.StaneksGiftExtraSize > defaultMultipliers.StaneksGiftExtraSize ? `+${extraSize}` : extraSize}`,
{mults.StaneksGiftPowerMultiplier !== defaultMultipliers.StaneksGiftPowerMultiplier ? ( },
<Typography>Gift power: x{mults.StaneksGiftPowerMultiplier.toFixed(3)}</Typography> };
) : (
<></> return <BNMultTable sectionName="Stanek's Gift" rowData={rows} mults={mults} />;
)}
{s !== defaultMultipliers.StaneksGiftExtraSize ? (
<Typography>Base size modifier: {s > defaultMultipliers.StaneksGiftExtraSize ? `+${s}` : s}</Typography>
) : (
<></>
)}
</Box>
</>
);
} }
function GangMults({ n, mults }: IMultsProps): React.ReactElement { function GangMults({ mults }: IMultsProps): React.ReactElement {
const player = use.Player(); const player = use.Player();
// access check if (player.bitNodeN !== 2 && player.sourceFileLvl(2) <= 0) return <></>;
if (n !== 2 && player.sourceFileLvl(2) === 0) return <></>;
// is it empty check const rows: IBNMultRows = {
if ( GangSoftcap: {
mults.GangSoftcap === defaultMultipliers.GangSoftcap && name: "Gang Softcap",
mults.GangUniqueAugs === defaultMultipliers.GangUniqueAugs content: mults.GangSoftcap.toFixed(3),
) },
return <></>; GangUniqueAugs: { name: "Unique Augmentations" },
return ( };
<>
<br /> return <BNMultTable sectionName="Gang" rowData={rows} mults={mults} />;
<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>
</>
);
} }
function CorporationMults({ n, mults }: IMultsProps): React.ReactElement { function CorporationMults({ mults }: IMultsProps): React.ReactElement {
const player = use.Player(); const player = use.Player();
// access check if (!player.canAccessCorporation()) return <></>;
if (n !== 3 && player.sourceFileLvl(3) === 0) return <></>;
// is it empty check
if (
mults.CorporationSoftCap === defaultMultipliers.CorporationSoftCap &&
mults.CorporationValuation === defaultMultipliers.CorporationValuation
)
return <></>;
return ( const rows: IBNMultRows = {
<> CorporationSoftcap: {
<br /> name: "Corporation Softcap",
<Typography variant={"h5"}>Corporation:</Typography> content: mults.CorporationSoftcap.toFixed(3),
<Box mx={1}> },
{mults.CorporationSoftCap !== defaultMultipliers.CorporationSoftCap ? ( CorporationValuation: { name: "Valuation" },
<Typography>Softcap: {mults.CorporationSoftCap.toFixed(3)}</Typography> };
) : (
<></> return <BNMultTable sectionName="Corporation" rowData={rows} mults={mults} />;
)}
{mults.CorporationValuation !== defaultMultipliers.CorporationValuation ? (
<Typography>Valuation: x{mults.CorporationValuation.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
} }

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

@ -700,7 +700,7 @@ export class Bladeburner implements IBladeburner {
// Set variables // Set variables
if (args.length === 4) { 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]; const val = args[2];
let highLow = false; // True for high, false for low 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 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"]) { if (this.action.type !== ActionTypes["Idle"]) {
let msg = "Your Bladeburner action was cancelled because you started doing something else."; let msg = "Your Bladeburner action was cancelled because you started doing something else.";
if (this.automateEnabled) { if (this.automateEnabled) {

@ -84,11 +84,12 @@ export const CONSTANTS: {
SoARepMult: number; SoARepMult: number;
EntropyEffect: number; EntropyEffect: number;
TotalNumBitNodes: number; TotalNumBitNodes: number;
InfiniteLoopLimit: number;
Donations: number; // number of blood/plasma/palette donation the dev have verified., boosts NFG Donations: number; // number of blood/plasma/palette donation the dev have verified., boosts NFG
LatestUpdate: string; LatestUpdate: string;
} = { } = {
VersionString: "1.6.4", VersionString: "1.7.0",
VersionNumber: 17, VersionNumber: 18,
// Speed (in ms) at which the main loop is updated // Speed (in ms) at which the main loop is updated
_idleSpeed: 200, _idleSpeed: 200,
@ -226,22 +227,173 @@ export const CONSTANTS: {
// BitNode/Source-File related stuff // BitNode/Source-File related stuff
TotalNumBitNodes: 24, TotalNumBitNodes: 24,
InfiniteLoopLimit: 1000,
Donations: 7, Donations: 7,
LatestUpdate: ` 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 Modifications included between **2022-04-13** and **2022-05-20** 'b5e4d70' to '0fbe4a1').
* 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.
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) { if (this.unlockUpgrades[6] === 1) {
upgrades += 0.1; upgrades += 0.1;
} }
return Math.pow(dividends, BitNodeMultipliers.CorporationSoftCap + upgrades); return Math.pow(dividends, BitNodeMultipliers.CorporationSoftcap + upgrades);
} }
determineValuation(): number { determineValuation(): number {

@ -68,13 +68,7 @@ export class ActiveFragment {
} }
copy(): ActiveFragment { copy(): ActiveFragment {
// We have to do a round trip because the constructor. return Object.assign({}, this);
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;
} }
/** /**

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

@ -136,8 +136,9 @@ export class StaneksGift implements IStaneksGift {
} }
updateMults(p: IPlayer): void { updateMults(p: IPlayer): void {
p.reapplyAllAugmentations(true); // applyEntropy also reapplies all augmentations and source files
p.reapplyAllSourceFiles(); // This wraps up the reset nicely
p.applyEntropy(p.entropy);
for (const aFrag of this.fragments) { for (const aFrag of this.fragments) {
const fragment = aFrag.fragment(); 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"; 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 ( return (
1 + 1 +
(Math.log(avgCharge + 1) / (Math.log(1.8) * 100)) * (Math.log(highestCharge + 1) / 60) *
Math.pow((numCharge + 1) / 5, 0.07) * Math.pow((numCharge + 1) / 5, 0.07) *
power * power *
boost * 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) { if (props.fragment === undefined) {
return ( return (
<Paper> <Paper sx={{ flexGrow: 1 }}>
<Typography> <Typography>
[X, Y] {props.x}, {props.y}
<br />
<br />
ID: N/A ID: N/A
<br /> <br />
Effect: N/A Effect: N/A
<br /> <br />
Magnitude: N/A Base Power: N/A
<br /> <br />
Charge: N/A Charge: N/A
<br /> <br />
Heat: N/A root [X, Y] N/A
<br /> <br />
Effect: N/A
<br />
[X, Y] N/A
<br />
[X, Y] {props.x}, {props.y}
</Typography> </Typography>
</Paper> </Paper>
); );
@ -63,8 +61,11 @@ export function FragmentInspector(props: IProps): React.ReactElement {
} }
return ( return (
<Paper> <Paper sx={{ flexGrow: 1 }}>
<Typography> <Typography>
[X, Y] {props.x}, {props.y}
<br />
<br />
ID: {props.fragment.id} ID: {props.fragment.id}
<br /> <br />
Effect: {effect} Effect: {effect}
@ -73,10 +74,8 @@ export function FragmentInspector(props: IProps): React.ReactElement {
<br /> <br />
Charge: {charge} Charge: {charge}
<br /> <br />
<br />
root [X, Y] {props.fragment.x}, {props.fragment.y} root [X, Y] {props.fragment.x}, {props.fragment.y}
<br /> <br />
[X, Y] {props.x}, {props.y}
</Typography> </Typography>
</Paper> </Paper>
); );

@ -9,6 +9,9 @@ import Button from "@mui/material/Button";
import { Table } from "../../ui/React/Table"; import { Table } from "../../ui/React/Table";
import { Grid } from "./Grid"; import { Grid } from "./Grid";
import { zeros, calculateGrid } from "../Helper"; import { zeros, calculateGrid } from "../Helper";
import { ActiveFragmentSummary } from "./ActiveFragmentSummary";
import Tooltip from "@mui/material/Tooltip";
import Typography from "@mui/material/Typography";
interface IProps { interface IProps {
gift: IStaneksGift; gift: IStaneksGift;
@ -84,9 +87,8 @@ export function MainBoard(props: IProps): React.ReactElement {
return ( return (
<> <>
<Button onClick={clear}>Clear</Button> <Box display="flex" sx={{ mb: 1 }}>
<Box display="flex"> <Table sx={{ mr: 1 }}>
<Table>
<Grid <Grid
width={props.gift.width()} width={props.gift.width()}
height={props.gift.height()} height={props.gift.height()}
@ -99,7 +101,22 @@ export function MainBoard(props: IProps): React.ReactElement {
</Table> </Table>
<FragmentInspector gift={props.gift} x={pos[0]} y={pos[1]} fragment={props.gift.fragmentAt(pos[0], pos[1])} /> <FragmentInspector gift={props.gift} x={pos[0]} y={pos[1]} fragment={props.gift.fragmentAt(pos[0], pos[1])} />
</Box> </Box>
<FragmentSelector gift={props.gift} selectFragment={updateSelectedFragment} /> <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 { ActiveFragment } from "../ActiveFragment";
import { Fragments } from "../Fragment"; import { Fragments } from "../Fragment";
import { DummyGrid } from "./DummyGrid"; import { DummyGrid } from "./DummyGrid";
import Container from "@mui/material/Container";
type IProps = { type IProps = {
staneksGift: IStaneksGift; staneksGift: IStaneksGift;
@ -22,7 +23,7 @@ export function StaneksGiftRoot({ staneksGift }: IProps): React.ReactElement {
} }
useEffect(() => StaneksGiftEvents.subscribe(rerender), []); useEffect(() => StaneksGiftEvents.subscribe(rerender), []);
return ( return (
<> <Container maxWidth="lg" disableGutters sx={{ mx: 0 }}>
<Typography variant="h4"> <Typography variant="h4">
Stanek's Gift Stanek's Gift
<Info <Info
@ -184,18 +185,18 @@ export function StaneksGiftRoot({ staneksGift }: IProps): React.ReactElement {
/> />
</Typography> </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, 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 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 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. efficiency of the neighboring fragments (not diagonally). Use Q/E to rotate fragments.
</Typography> </Typography>
{staneksGift.storedCycles > 5 && ( {staneksGift.storedCycles > 5 && (
<Typography> <Typography sx={{ mb: 1 }}>
Bonus time: {convertTimeMsToTimeElapsedString(CONSTANTS._idleSpeed * staneksGift.storedCycles)} Bonus time: {convertTimeMsToTimeElapsedString(CONSTANTS._idleSpeed * staneksGift.storedCycles)}
</Typography> </Typography>
)} )}
<MainBoard gift={staneksGift} /> <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 React, { useEffect, useState } from "react";
import { Box, Button, Container, Paper, TableBody, TableRow, Typography } from "@mui/material";
import { IPlayer } from "../../PersonObjects/IPlayer"; 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 { IRouter } from "../../ui/Router";
import { Faction } from "../Faction";
import { joinFaction, getFactionAugmentationsFiltered } from "../FactionHelpers";
import { Factions } from "../Factions";
import { FactionNames } from "../data/FactionNames"; import { FactionNames } from "../data/FactionNames";
import { Faction } from "../Faction";
import { getFactionAugmentationsFiltered, joinFaction } from "../FactionHelpers";
import { Factions } from "../Factions";
export const InvitationsSeen: string[] = []; 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 { interface IProps {
player: IPlayer; player: IPlayer;
router: IRouter; router: IRouter;
} }
export function FactionsRoot(props: IProps): React.ReactElement { export function FactionsRoot(props: IProps): React.ReactElement {
const theme = useTheme();
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
function rerender(): void { function rerender(): void {
setRerender((old) => !old); 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 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)); allJoinedFactions.sort((a, b) => allFactions.indexOf(a) - allFactions.indexOf(b));
const invitations = props.player.factionInvitations;
return ( return (
<Container disableGutters maxWidth="md" sx={{ mx: 0, mb: 10 }}> <Container disableGutters maxWidth="lg" sx={{ mx: 0, mb: 10 }}>
<Typography variant="h4">Factions</Typography> <Typography variant="h4">
<Typography mb={4}> Factions
Throughout the game you may receive invitations from factions. There are many different factions, and each <Tooltip
faction has different criteria for determining its potential members. Joining a faction and furthering its cause title={
is crucial to progressing in the game and unlocking endgame content. <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.
</Typography>
}
>
<Info sx={{ ml: 1, mb: 0 }} color="info" />
</Tooltip>
</Typography> </Typography>
<Typography variant="h5" color="primary" mt={2} mb={1}> <Box
Factions you have joined: display="grid"
</Typography> sx={{
{(allJoinedFactions.length > 0 && ( gap: 1,
<Paper sx={{ my: 1, p: 1, pb: 0, display: "inline-block" }}> gridTemplateColumns: (invitations.length > 0 ? "1fr " : "") + "2fr",
<Table padding="none" style={{ width: "fit-content" }}> [theme.breakpoints.down("lg")]: { gridTemplateColumns: "1fr", "& > span:nth-child(1)": { order: 1 } },
<TableBody> gridTemplateRows: "minmax(0, 1fr)",
{allJoinedFactions.map((faction: string) => ( "& > span > .MuiBox-root": {
<TableRow key={faction}> display: "grid",
<TableCell> gridAutoRows: "70px",
<Typography noWrap mb={1}> gap: 1,
{faction} },
</Typography> }}
</TableCell> >
<TableCell align="right"> {invitations.length > 0 && (
<Box ml={1} mb={1}> <span>
<Button onClick={() => openFaction(Factions[faction])}>Details</Button> <Typography variant="h5" color="primary">
</Box> Faction Invitations
</TableCell> </Typography>
<TableCell align="right"> <Box>
<Box ml={1} mb={1}> {invitations.map((facName) => {
<Button sx={{ width: "100%" }} onClick={() => openFactionAugPage(Factions[faction])}> if (!Factions.hasOwnProperty(facName)) return null;
Augmentations Left: {getAugsLeft(Factions[faction], props.player)} return (
</Button> <FactionElement
</Box> key={facName}
</TableCell> faction={Factions[facName]}
</TableRow> player={props.player}
))} router={props.router}
</TableBody> joined={false}
</Table> rerender={rerender}
</Paper> />
)) || <Typography>You haven't joined any factions.</Typography>} );
<Typography variant="h5" color="primary" mt={4} mb={1}> })}
Outstanding Faction Invitations </Box>
</Typography> </span>
<Typography mb={1}> )}
Factions you have been invited to. You can accept these faction invitations at any time:
</Typography> <span>
{(props.player.factionInvitations.length > 0 && ( <Typography variant="h5" color="primary">
<Paper sx={{ my: 1, mb: 4, p: 1, pb: 0, display: "inline-block" }}> Your Factions
<Table padding="none"> </Typography>
<TableBody> <Box>
{props.player.factionInvitations.map((faction: string) => ( {allJoinedFactions.length > 0 ? (
<TableRow key={faction}> allJoinedFactions.map((facName) => {
<TableCell> if (!Factions.hasOwnProperty(facName)) return null;
<Typography noWrap mb={1}> return (
{faction} <FactionElement
</Typography> key={facName}
</TableCell> faction={Factions[facName]}
<TableCell align="right"> player={props.player}
<Box ml={1} mb={1}> router={props.router}
<Button onClick={(e) => acceptInvitation(e, faction)}>Join!</Button> joined={true}
</Box> rerender={rerender}
</TableCell> />
</TableRow> );
))} })
</TableBody> ) : (
</Table> <Typography>You have not yet joined any Factions.</Typography>
</Paper> )}
)) || <Typography>You have no outstanding faction invites.</Typography>} </Box>
</span>
</Box>
</Container> </Container>
); );
} }

@ -70,7 +70,7 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
<> <>
<OptionsSlider <OptionsSlider
label=".script exec time (ms)" label=".script exec time (ms)"
value={execTime} initialValue={execTime}
callback={handleExecTimeChange} callback={handleExecTimeChange}
step={1} step={1}
min={5} min={5}
@ -84,7 +84,7 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
/> />
<OptionsSlider <OptionsSlider
label="Recently killed scripts size" label="Recently killed scripts size"
value={recentScriptsSize} initialValue={recentScriptsSize}
callback={handleRecentScriptsSizeChange} callback={handleRecentScriptsSizeChange}
step={25} step={25}
min={0} min={0}
@ -98,7 +98,7 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
/> />
<OptionsSlider <OptionsSlider
label="Netscript log size" label="Netscript log size"
value={logSize} initialValue={logSize}
callback={handleLogSizeChange} callback={handleLogSizeChange}
step={20} step={20}
min={20} min={20}
@ -112,7 +112,7 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
/> />
<OptionsSlider <OptionsSlider
label="Netscript port size" label="Netscript port size"
value={portSize} initialValue={portSize}
callback={handlePortSizeChange} callback={handlePortSizeChange}
step={1} step={1}
min={20} min={20}
@ -126,7 +126,7 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
/> />
<OptionsSlider <OptionsSlider
label="Terminal capacity" label="Terminal capacity"
value={terminalSize} initialValue={terminalSize}
callback={handleTerminalSizeChange} callback={handleTerminalSizeChange}
step={50} step={50}
min={50} min={50}
@ -141,7 +141,7 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
/> />
<OptionsSlider <OptionsSlider
label="Autosave interval (s)" label="Autosave interval (s)"
value={autosaveInterval} initialValue={autosaveInterval}
callback={handleAutosaveIntervalChange} callback={handleAutosaveIntervalChange}
step={30} step={30}
min={0} 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> </GameOptionsPage>
), ),
[GameOptionsTab.INTERFACE]: ( [GameOptionsTab.INTERFACE]: (

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

@ -85,9 +85,15 @@ export function TerritorySubpage(): React.ReactElement {
</Typography> </Typography>
</Box> </Box>
<Box sx={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)" }}> <Box sx={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)" }}>
{gangNames.map((name) => ( {gangNames
<OtherGangTerritory key={name} name={name} /> .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> </Box>
<TerritoryInfoModal open={infoOpen} onClose={() => setInfoOpen(false)} /> <TerritoryInfoModal open={infoOpen} onClose={() => setInfoOpen(false)} />
</Container> </Container>
@ -114,14 +120,16 @@ function OtherGangTerritory(props: ITerritoryProps): React.ReactElement {
const playerPower = AllGangs[gang.facName].power; const playerPower = AllGangs[gang.facName].power;
const power = AllGangs[props.name].power; const power = AllGangs[props.name].power;
const clashVictoryChance = playerPower / (power + playerPower); const clashVictoryChance = playerPower / (power + playerPower);
const territory = AllGangs[props.name].territory;
const opacity = territory ? 1 : 0.75;
return ( return (
<Box component={Paper} sx={{ p: 1 }}> <Box component={Paper} sx={{ p: 1, opacity }}>
<Typography variant="h6" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}> <Typography variant="h6" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
{props.name} {props.name}
</Typography> </Typography>
<Typography> <Typography>
<b>Power:</b> {formatNumber(power, 3)} <br /> <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)} <b>Clash Win Chance:</b> {numeralWrapper.formatPercentage(clashVictoryChance, 3)}
</Typography> </Typography>
</Box> </Box>

@ -36,7 +36,7 @@ export function calculateTradeInformationRepReward(
30 * 30 *
levelBonus * levelBonus *
(player.hasAugmentation(AugmentationNames.WKSharmonizer, true) ? 1.5 : 1) * (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 [guess, setGuess] = useState("");
const hasAugment = Player.hasAugmentation(AugmentationNames.ChaosOfDionysus, true); 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 { function press(this: Document, event: KeyboardEvent): void {
event.preventDefault(); event.preventDefault();
if (event.key === KEY.BACKSPACE) return; if (ignorableKeyboardEvent(event)) return;
const nextGuess = guess + event.key.toUpperCase(); const nextGuess = guess + event.key.toUpperCase();
if (!answer.startsWith(nextGuess)) props.onFailure(); if (!answer.startsWith(nextGuess)) props.onFailure();
else if (answer === nextGuess) props.onSuccess(); else if (answer === nextGuess) props.onSuccess();

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

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

@ -5,6 +5,8 @@ import type { WorkerScript } from "./WorkerScript";
import { makeRuntimeRejectMsg } from "../NetscriptEvaluator"; import { makeRuntimeRejectMsg } from "../NetscriptEvaluator";
import { Player } from "../Player"; import { Player } from "../Player";
import { CityName } from "src/Locations/data/CityNames"; import { CityName } from "src/Locations/data/CityNames";
import { Settings } from "../Settings/Settings";
import { CONSTANTS } from "../Constants";
type ExternalFunction = (...args: any[]) => any; type ExternalFunction = (...args: any[]) => any;
type ExternalAPI = { type ExternalAPI = {
@ -91,8 +93,14 @@ function wrapFunction(
getValidPort: (port: any) => helpers.getValidPort(functionPath, port), getValidPort: (port: any) => helpers.getValidPort(functionPath, port),
}, },
}; };
const safetyEnabled = Settings.InfinityLoopSafety;
function wrappedFunction(...args: unknown[]): unknown { function wrappedFunction(...args: unknown[]): unknown {
helpers.updateDynamicRam(ctx.function, getRamCost(Player, ...tree, ctx.function)); 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); return func(ctx)(...args);
} }
const parent = getNestedProperty(wrappedAPI, ...tree); const parent = getNestedProperty(wrappedAPI, ...tree);

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

@ -111,6 +111,11 @@ export class WorkerScript {
*/ */
atExit: any; 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) { constructor(runningScriptObj: RunningScript, pid: number, nsFuncsGenerator?: (ws: WorkerScript) => any) {
this.name = runningScriptObj.filename; this.name = runningScriptObj.filename;
this.hostname = runningScriptObj.server; this.hostname = runningScriptObj.server;

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

@ -55,7 +55,7 @@ import { makeRuntimeRejectMsg, netscriptDelay, resolveNetscriptRequestedThreads
import { numeralWrapper } from "./ui/numeralFormat"; import { numeralWrapper } from "./ui/numeralFormat";
import { convertTimeMsToTimeElapsedString } from "./utils/StringHelperFunctions"; 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 { arrayToString } from "./utils/helpers/arrayToString";
import { isString } from "./utils/helpers/isString"; import { isString } from "./utils/helpers/isString";
@ -82,6 +82,7 @@ import {
Gang as IGang, Gang as IGang,
Bladeburner as IBladeburner, Bladeburner as IBladeburner,
Stanek as IStanek, Stanek as IStanek,
Sleeve as ISleeve,
Infiltration as IInfiltration, Infiltration as IInfiltration,
RunningScript as IRunningScript, RunningScript as IRunningScript,
RecentScript as IRecentScript, RecentScript as IRecentScript,
@ -93,6 +94,12 @@ import {
BitNodeMultipliers as IBNMults, BitNodeMultipliers as IBNMults,
Server as IServerDef, Server as IServerDef,
RunningScript as IRunningScriptDef, RunningScript as IRunningScriptDef,
Grafting as IGrafting,
UserInterface as IUserInterface,
TIX as ITIX,
Corporation as ICorporation,
CodingContract as ICodingContract,
Hacknet as IHacknet,
// ToastVariant, // ToastVariant,
} from "./ScriptEditor/NetscriptDefinitions"; } from "./ScriptEditor/NetscriptDefinitions";
import { NetscriptSingularity } from "./NetscriptFunctions/Singularity"; import { NetscriptSingularity } from "./NetscriptFunctions/Singularity";
@ -360,7 +367,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
} }
}; };
const hack = function ( const hack = async function (
hostname: string, hostname: string,
manual: boolean, manual: boolean,
{ threads: requestedThreads, stock }: any = {}, { 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 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") const infiltration = wrapAPI(helper, {}, workerScript, NetscriptInfiltration(Player), "infiltration")
.infiltration as unknown as IInfiltration; .infiltration as unknown as IInfiltration;
const stanek = wrapAPI(helper, {}, workerScript, NetscriptStanek(Player, workerScript, helper), "stanek") const stanek = wrapAPI(helper, {}, workerScript, NetscriptStanek(Player, workerScript, helper), "stanek")
.stanek as unknown as IStanek; .stanek as unknown as IStanek;
const bladeburner = NetscriptBladeburner(Player, workerScript, helper); const corporation = wrapAPI(helper, {}, workerScript, NetscriptCorporation(Player, workerScript), "corporation")
const codingcontract = NetscriptCodingContract(Player, workerScript, helper); .corporation as unknown as ICorporation;
const corporation = NetscriptCorporation(Player, workerScript, helper);
const formulas = NetscriptFormulas(Player, workerScript, helper);
const singularity = wrapAPI(helper, {}, workerScript, NetscriptSingularity(Player, workerScript), "singularity") const singularity = wrapAPI(helper, {}, workerScript, NetscriptSingularity(Player, workerScript), "singularity")
.singularity as unknown as ISingularity; .singularity as unknown as ISingularity;
const stockmarket = NetscriptStockMarket(Player, workerScript, helper); const stockmarket = wrapAPI(helper, {}, workerScript, NetscriptStockMarket(Player, workerScript), "stock")
const ui = NetscriptUserInterface(Player, workerScript, helper); .stock as unknown as ITIX;
const grafting = NetscriptGrafting(Player, workerScript, helper); 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 = { const base: INS = {
...singularity, ...singularity,
@ -988,6 +1007,12 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
LogBoxEvents.emit(runningScriptObj); 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 { nuke: function (_hostname: unknown): boolean {
updateDynamicRam("nuke", getRamCost(Player, "nuke")); updateDynamicRam("nuke", getRamCost(Player, "nuke"));
const hostname = helper.string("tail", "hostname", _hostname); const hostname = helper.string("tail", "hostname", _hostname);

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

@ -1,132 +1,124 @@
import { INetscriptHelper } from "./INetscriptHelper";
import { WorkerScript } from "../Netscript/WorkerScript"; import { WorkerScript } from "../Netscript/WorkerScript";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { is2DArray } from "../utils/helpers/is2DArray"; import { is2DArray } from "../utils/helpers/is2DArray";
import { CodingContract } from "../CodingContracts"; import { CodingContract } from "../CodingContracts";
import { CodingAttemptOptions, CodingContract as ICodingContract } from "../ScriptEditor/NetscriptDefinitions"; import { CodingAttemptOptions, CodingContract as ICodingContract } from "../ScriptEditor/NetscriptDefinitions";
import { InternalAPI, NetscriptContext } from "src/Netscript/APIWrapper";
export function NetscriptCodingContract( export function NetscriptCodingContract(player: IPlayer, workerScript: WorkerScript): InternalAPI<ICodingContract> {
player: IPlayer, const getCodingContract = function (
workerScript: WorkerScript, ctx: NetscriptContext,
helper: INetscriptHelper, func: string,
): ICodingContract { hostname: string,
const getCodingContract = function (func: string, hostname: string, filename: string): CodingContract { filename: string,
const server = helper.getServer(hostname, func); ): CodingContract {
const server = ctx.helper.getServer(hostname);
const contract = server.getContract(filename); const contract = server.getContract(filename);
if (contract == null) { if (contract == null) {
throw helper.makeRuntimeErrorMsg( throw ctx.makeRuntimeErrorMsg(`Cannot find contract '${filename}' on server '${hostname}'`);
`codingcontract.${func}`,
`Cannot find contract '${filename}' on server '${hostname}'`,
);
} }
return contract; return contract;
}; };
const updateRam = (funcName: string): void =>
helper.updateDynamicRam(funcName, getRamCost(player, "codingcontract", funcName));
return { return {
attempt: function ( attempt:
answer: any, (ctx: NetscriptContext) =>
_filename: unknown, (
_hostname: unknown = workerScript.hostname, answer: any,
{ returnReward }: CodingAttemptOptions = { returnReward: false }, _filename: unknown,
): boolean | string { _hostname: unknown = workerScript.hostname,
updateRam("attempt"); { returnReward }: CodingAttemptOptions = { returnReward: false },
const filename = helper.string("attempt", "filename", _filename); ): boolean | string => {
const hostname = helper.string("attempt", "hostname", _hostname); const filename = ctx.helper.string("filename", _filename);
const contract = getCodingContract("attempt", hostname, 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 // Convert answer to string. If the answer is a 2D array, then we have to
// manually add brackets for the inner arrays // manually add brackets for the inner arrays
if (is2DArray(answer)) { if (is2DArray(answer)) {
const answerComponents = []; const answerComponents = [];
for (let i = 0; i < answer.length; ++i) { for (let i = 0; i < answer.length; ++i) {
answerComponents.push(["[", answer[i].toString(), "]"].join("")); answerComponents.push(["[", answer[i].toString(), "]"].join(""));
}
answer = answerComponents.join(",");
} else {
answer = String(answer);
}
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");
if (contract.isSolution(answer)) {
const reward = player.gainCodingContractReward(creward, contract.getDifficulty());
workerScript.log(
"codingcontract.attempt",
() => `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`,
);
serv.removeContract(filename);
} else {
workerScript.log(
"codingcontract.attempt",
() =>
`Coding Contract attempt '${filename}' failed. ${
contract.getMaxNumTries() - contract.tries
} attempts remaining.`,
);
}
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);
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);
const data = contract.getData();
if (data.constructor === Array) {
// For two dimensional arrays, we have to copy the internal arrays using
// slice() as well. As of right now, no contract has arrays that have
// more than two dimensions
const copy = data.slice();
for (let i = 0; i < copy.length; ++i) {
if (data[i].constructor === Array) {
copy[i] = data[i].slice();
} }
answer = answerComponents.join(",");
} else {
answer = String(answer);
} }
return copy; const creward = contract.reward;
} else { if (creward === null) throw new Error("Somehow solved a contract that didn't have a reward");
return data;
} const serv = ctx.helper.getServer(hostname);
}, if (contract.isSolution(answer)) {
getDescription: function (_filename: unknown, _hostname: unknown = workerScript.hostname): string { const reward = player.gainCodingContractReward(creward, contract.getDifficulty());
updateRam("getDescription"); ctx.log(() => `Successfully completed Coding Contract '${filename}'. Reward: ${reward}`);
const filename = helper.string("getDescription", "filename", _filename); serv.removeContract(filename);
const hostname = helper.string("getDescription", "hostname", _hostname); return returnReward ? reward : true;
const contract = getCodingContract("getDescription", hostname, filename); } else {
return contract.getDescription(); ++contract.tries;
}, if (contract.tries >= contract.getMaxNumTries()) {
getNumTriesRemaining: function (_filename: unknown, _hostname: unknown = workerScript.hostname): number { ctx.log(() => `Coding Contract attempt '${filename}' failed. Contract is now self-destructing`);
updateRam("getNumTriesRemaining"); serv.removeContract(filename);
const filename = helper.string("getNumTriesRemaining", "filename", _filename); } else {
const hostname = helper.string("getNumTriesRemaining", "hostname", _hostname); ctx.log(
const contract = getCodingContract("getNumTriesRemaining", hostname, filename); () =>
return contract.getMaxNumTries() - contract.tries; `Coding Contract attempt '${filename}' failed. ${
}, contract.getMaxNumTries() - contract.tries
} attempts remaining.`,
);
}
return returnReward ? "" : false;
}
},
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:
(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
// slice() as well. As of right now, no contract has arrays that have
// more than two dimensions
const copy = data.slice();
for (let i = 0; i < copy.length; ++i) {
if (data[i].constructor === Array) {
copy[i] = data[i].slice();
}
}
return copy;
} else {
return data;
}
},
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:
(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 { FactionNames } from "../Faction/data/FactionNames";
import { GangConstants } from "../Gang/data/Constants"; import { GangConstants } from "../Gang/data/Constants";
import { INetscriptHelper } from "./INetscriptHelper";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { Gang } from "../Gang/Gang"; import { Gang } from "../Gang/Gang";
import { AllGangs } from "../Gang/AllGangs"; import { AllGangs } from "../Gang/AllGangs";
import { GangMemberTasks } from "../Gang/GangMemberTasks"; import { GangMemberTasks } from "../Gang/GangMemberTasks";
@ -20,63 +18,60 @@ import {
EquipmentStats, EquipmentStats,
GangTaskStats, GangTaskStats,
} from "../ScriptEditor/NetscriptDefinitions"; } from "../ScriptEditor/NetscriptDefinitions";
import { InternalAPI, NetscriptContext } from "../Netscript/APIWrapper";
export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): IGang { export function NetscriptGang(player: IPlayer, workerScript: WorkerScript): InternalAPI<IGang> {
const checkGangApiAccess = function (func: string): void { const checkGangApiAccess = function (ctx: NetscriptContext): void {
const gang = player.gang; const gang = player.gang;
if (gang === null) throw new Error("Must have joined gang"); if (gang === null) throw new Error("Must have joined gang");
const hasAccess = gang instanceof Gang; const hasAccess = gang instanceof Gang;
if (!hasAccess) { 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; const gang = player.gang;
if (gang === null) throw new Error("Must have joined gang"); if (gang === null) throw new Error("Must have joined gang");
for (const member of gang.members) if (member.name === name) return member; 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]; const task = GangMemberTasks[name];
if (!task) { if (!task) {
throw helper.makeRuntimeErrorMsg(`gang.${func}`, `Invalid task: '${name}'`); throw ctx.makeRuntimeErrorMsg(`Invalid task: '${name}'`);
} }
return task; return task;
}; };
const updateRam = (funcName: string): void => helper.updateDynamicRam(funcName, getRamCost(player, "gang", funcName));
return { return {
createGang: function (_faction: unknown): boolean { createGang:
updateRam("createGang"); (ctx: NetscriptContext) =>
const faction = helper.string("createGang", "faction", _faction); (_faction: unknown): boolean => {
// this list is copied from Faction/ui/Root.tsx 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; if (!player.canAccessGang() || !GangConstants.Names.includes(faction)) return false;
if (player.inGang()) return false; if (player.inGang()) return false;
if (!player.factions.includes(faction)) return false; if (!player.factions.includes(faction)) return false;
const isHacking = faction === FactionNames.NiteSec || faction === FactionNames.TheBlackHand; const isHacking = faction === FactionNames.NiteSec || faction === FactionNames.TheBlackHand;
player.startGang(faction, isHacking); player.startGang(faction, isHacking);
return true; return true;
}, },
inGang: function (): boolean { inGang: () => (): boolean => {
updateRam("inGang");
return player.inGang(); return player.inGang();
}, },
getMemberNames: function (): string[] { getMemberNames: (ctx: NetscriptContext) => (): string[] => {
updateRam("getMemberNames"); checkGangApiAccess(ctx);
checkGangApiAccess("getMemberNames");
const gang = player.gang; const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang"); if (gang === null) throw new Error("Should not be called without Gang");
return gang.members.map((member) => member.name); return gang.members.map((member) => member.name);
}, },
getGangInformation: function (): GangGenInfo { getGangInformation: (ctx: NetscriptContext) => (): GangGenInfo => {
updateRam("getGangInformation"); checkGangApiAccess(ctx);
checkGangApiAccess("getGangInformation");
const gang = player.gang; const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang"); if (gang === null) throw new Error("Should not be called without Gang");
return { return {
@ -94,9 +89,8 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
wantedPenalty: gang.getWantedPenalty(), wantedPenalty: gang.getWantedPenalty(),
}; };
}, },
getOtherGangInformation: function (): GangOtherInfo { getOtherGangInformation: (ctx: NetscriptContext) => (): GangOtherInfo => {
updateRam("getOtherGangInformation"); checkGangApiAccess(ctx);
checkGangApiAccess("getOtherGangInformation");
const cpy: any = {}; const cpy: any = {};
for (const gang of Object.keys(AllGangs)) { for (const gang of Object.keys(AllGangs)) {
cpy[gang] = Object.assign({}, AllGangs[gang]); cpy[gang] = Object.assign({}, AllGangs[gang]);
@ -104,242 +98,251 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
return cpy; return cpy;
}, },
getMemberInformation: function (_memberName: unknown): GangMemberInfo { getMemberInformation:
updateRam("getMemberInformation"); (ctx: NetscriptContext) =>
const memberName = helper.string("getMemberInformation", "memberName", _memberName); (_memberName: unknown): GangMemberInfo => {
checkGangApiAccess("getMemberInformation"); const memberName = ctx.helper.string("memberName", _memberName);
const gang = player.gang; checkGangApiAccess(ctx);
if (gang === null) throw new Error("Should not be called without Gang"); const gang = player.gang;
const member = getGangMember("getMemberInformation", memberName); if (gang === null) throw new Error("Should not be called without Gang");
return { const member = getGangMember(ctx, memberName);
name: member.name, return {
task: member.task, name: member.name,
earnedRespect: member.earnedRespect, task: member.task,
hack: member.hack, earnedRespect: member.earnedRespect,
str: member.str, hack: member.hack,
def: member.def, str: member.str,
dex: member.dex, def: member.def,
agi: member.agi, dex: member.dex,
cha: member.cha, agi: member.agi,
cha: member.cha,
hack_exp: member.hack_exp, hack_exp: member.hack_exp,
str_exp: member.str_exp, str_exp: member.str_exp,
def_exp: member.def_exp, def_exp: member.def_exp,
dex_exp: member.dex_exp, dex_exp: member.dex_exp,
agi_exp: member.agi_exp, agi_exp: member.agi_exp,
cha_exp: member.cha_exp, cha_exp: member.cha_exp,
hack_mult: member.hack_mult, hack_mult: member.hack_mult,
str_mult: member.str_mult, str_mult: member.str_mult,
def_mult: member.def_mult, def_mult: member.def_mult,
dex_mult: member.dex_mult, dex_mult: member.dex_mult,
agi_mult: member.agi_mult, agi_mult: member.agi_mult,
cha_mult: member.cha_mult, cha_mult: member.cha_mult,
hack_asc_mult: member.calculateAscensionMult(member.hack_asc_points), hack_asc_mult: member.calculateAscensionMult(member.hack_asc_points),
str_asc_mult: member.calculateAscensionMult(member.str_asc_points), str_asc_mult: member.calculateAscensionMult(member.str_asc_points),
def_asc_mult: member.calculateAscensionMult(member.def_asc_points), def_asc_mult: member.calculateAscensionMult(member.def_asc_points),
dex_asc_mult: member.calculateAscensionMult(member.dex_asc_points), dex_asc_mult: member.calculateAscensionMult(member.dex_asc_points),
agi_asc_mult: member.calculateAscensionMult(member.agi_asc_points), agi_asc_mult: member.calculateAscensionMult(member.agi_asc_points),
cha_asc_mult: member.calculateAscensionMult(member.cha_asc_points), cha_asc_mult: member.calculateAscensionMult(member.cha_asc_points),
hack_asc_points: member.hack_asc_points, hack_asc_points: member.hack_asc_points,
str_asc_points: member.str_asc_points, str_asc_points: member.str_asc_points,
def_asc_points: member.def_asc_points, def_asc_points: member.def_asc_points,
dex_asc_points: member.dex_asc_points, dex_asc_points: member.dex_asc_points,
agi_asc_points: member.agi_asc_points, agi_asc_points: member.agi_asc_points,
cha_asc_points: member.cha_asc_points, cha_asc_points: member.cha_asc_points,
upgrades: member.upgrades.slice(), upgrades: member.upgrades.slice(),
augmentations: member.augmentations.slice(), augmentations: member.augmentations.slice(),
respectGain: member.calculateRespectGain(gang), respectGain: member.calculateRespectGain(gang),
wantedLevelGain: member.calculateWantedLevelGain(gang), wantedLevelGain: member.calculateWantedLevelGain(gang),
moneyGain: member.calculateMoneyGain(gang), moneyGain: member.calculateMoneyGain(gang),
}; };
}, },
canRecruitMember: function (): boolean { canRecruitMember: (ctx: NetscriptContext) => (): boolean => {
updateRam("canRecruitMember"); checkGangApiAccess(ctx);
checkGangApiAccess("canRecruitMember");
const gang = player.gang; const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang"); if (gang === null) throw new Error("Should not be called without Gang");
return gang.canRecruitMember(); return gang.canRecruitMember();
}, },
recruitMember: function (_memberName: unknown): boolean { recruitMember:
updateRam("recruitMember"); (ctx: NetscriptContext) =>
const memberName = helper.string("recruitMember", "memberName", _memberName); (_memberName: unknown): boolean => {
checkGangApiAccess("recruitMember"); const memberName = ctx.helper.string("memberName", _memberName);
const gang = player.gang; checkGangApiAccess(ctx);
if (gang === null) throw new Error("Should not be called without Gang"); const gang = player.gang;
const recruited = gang.recruitMember(memberName); if (gang === null) throw new Error("Should not be called without Gang");
if (recruited) { const recruited = gang.recruitMember(memberName);
workerScript.log("gang.recruitMember", () => `Successfully recruited Gang Member '${memberName}'`); if (recruited) {
} else { workerScript.log("gang.recruitMember", () => `Successfully recruited Gang Member '${memberName}'`);
workerScript.log("gang.recruitMember", () => `Failed to recruit Gang Member '${memberName}'`); } else {
} workerScript.log("gang.recruitMember", () => `Failed to recruit Gang Member '${memberName}'`);
}
return recruited; return recruited;
}, },
getTaskNames: function (): string[] { getTaskNames: (ctx: NetscriptContext) => (): string[] => {
updateRam("getTaskNames"); checkGangApiAccess(ctx);
checkGangApiAccess("getTaskNames");
const gang = player.gang; const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang"); if (gang === null) throw new Error("Should not be called without Gang");
const tasks = gang.getAllTaskNames(); const tasks = gang.getAllTaskNames();
tasks.unshift("Unassigned"); tasks.unshift("Unassigned");
return tasks; return tasks;
}, },
setMemberTask: function (_memberName: unknown, _taskName: unknown): boolean { setMemberTask:
updateRam("setMemberTask"); (ctx: NetscriptContext) =>
const memberName = helper.string("setMemberTask", "memberName", _memberName); (_memberName: unknown, _taskName: unknown): boolean => {
const taskName = helper.string("setMemberTask", "taskName", _taskName); const memberName = ctx.helper.string("memberName", _memberName);
checkGangApiAccess("setMemberTask"); const taskName = ctx.helper.string("taskName", _taskName);
const member = getGangMember("setMemberTask", memberName); checkGangApiAccess(ctx);
const gang = player.gang; const member = getGangMember(ctx, memberName);
if (gang === null) throw new Error("Should not be called without Gang"); const gang = player.gang;
if (!gang.getAllTaskNames().includes(taskName)) { if (gang === null) throw new Error("Should not be called without Gang");
workerScript.log( if (!gang.getAllTaskNames().includes(taskName)) {
"gang.setMemberTask", workerScript.log(
() => "gang.setMemberTask",
`Failed to assign Gang Member '${memberName}' to Invalid task '${taskName}'. '${memberName}' is now Unassigned`, () =>
); `Failed to assign Gang Member '${memberName}' to Invalid task '${taskName}'. '${memberName}' is now Unassigned`,
return member.assignToTask("Unassigned"); );
} return member.assignToTask("Unassigned");
const success = member.assignToTask(taskName); }
if (success) { const success = member.assignToTask(taskName);
workerScript.log( if (success) {
"gang.setMemberTask", workerScript.log(
() => `Successfully assigned Gang Member '${memberName}' to '${taskName}' task`, "gang.setMemberTask",
); () => `Successfully assigned Gang Member '${memberName}' to '${taskName}' task`,
} else { );
workerScript.log( } else {
"gang.setMemberTask", workerScript.log(
() => `Failed to assign Gang Member '${memberName}' to '${taskName}' task. '${memberName}' is now Unassigned`, "gang.setMemberTask",
); () =>
} `Failed to assign Gang Member '${memberName}' to '${taskName}' task. '${memberName}' is now Unassigned`,
);
}
return success; return success;
}, },
getTaskStats: function (_taskName: unknown): GangTaskStats { getTaskStats:
updateRam("getTaskStats"); (ctx: NetscriptContext) =>
const taskName = helper.string("getTaskStats", "taskName", _taskName); (_taskName: unknown): GangTaskStats => {
checkGangApiAccess("getTaskStats"); const taskName = ctx.helper.string("taskName", _taskName);
const task = getGangTask("getTaskStats", taskName); checkGangApiAccess(ctx);
const copy = Object.assign({}, task); const task = getGangTask(ctx, taskName);
copy.territory = Object.assign({}, task.territory); const copy = Object.assign({}, task);
return copy; copy.territory = Object.assign({}, task.territory);
}, return copy;
getEquipmentNames: function (): string[] { },
updateRam("getEquipmentNames"); getEquipmentNames: (ctx: NetscriptContext) => (): string[] => {
checkGangApiAccess("getEquipmentNames"); checkGangApiAccess(ctx);
return Object.keys(GangMemberUpgrades); return Object.keys(GangMemberUpgrades);
}, },
getEquipmentCost: function (_equipName: any): number { getEquipmentCost:
updateRam("getEquipmentCost"); (ctx: NetscriptContext) =>
const equipName = helper.string("getEquipmentCost", "equipName", _equipName); (_equipName: any): number => {
checkGangApiAccess("getEquipmentCost"); const equipName = ctx.helper.string("equipName", _equipName);
const gang = player.gang; checkGangApiAccess(ctx);
if (gang === null) throw new Error("Should not be called without Gang"); const gang = player.gang;
const upg = GangMemberUpgrades[equipName]; if (gang === null) throw new Error("Should not be called without Gang");
if (upg === null) return Infinity; const upg = GangMemberUpgrades[equipName];
return gang.getUpgradeCost(upg); if (upg === null) return Infinity;
}, return gang.getUpgradeCost(upg);
getEquipmentType: function (_equipName: unknown): string { },
updateRam("getEquipmentType"); getEquipmentType:
const equipName = helper.string("getEquipmentType", "equipName", _equipName); (ctx: NetscriptContext) =>
checkGangApiAccess("getEquipmentType"); (_equipName: unknown): string => {
const upg = GangMemberUpgrades[equipName]; const equipName = ctx.helper.string("equipName", _equipName);
if (upg == null) return ""; checkGangApiAccess(ctx);
return upg.getType(); const upg = GangMemberUpgrades[equipName];
}, if (upg == null) return "";
getEquipmentStats: function (_equipName: unknown): EquipmentStats { return upg.getType();
updateRam("getEquipmentStats"); },
const equipName = helper.string("getEquipmentStats", "equipName", _equipName); getEquipmentStats:
checkGangApiAccess("getEquipmentStats"); (ctx: NetscriptContext) =>
const equipment = GangMemberUpgrades[equipName]; (_equipName: unknown): EquipmentStats => {
if (!equipment) { const equipName = ctx.helper.string("equipName", _equipName);
throw helper.makeRuntimeErrorMsg("getEquipmentStats", `Invalid equipment: ${equipName}`); checkGangApiAccess(ctx);
} const equipment = GangMemberUpgrades[equipName];
const typecheck: EquipmentStats = equipment.mults; if (!equipment) {
return Object.assign({}, typecheck) as any; throw ctx.makeRuntimeErrorMsg(`Invalid equipment: ${equipName}`);
}, }
purchaseEquipment: function (_memberName: unknown, _equipName: unknown): boolean { const typecheck: EquipmentStats = equipment.mults;
updateRam("purchaseEquipment"); return Object.assign({}, typecheck) as any;
const memberName = helper.string("purchaseEquipment", "memberName", _memberName); },
const equipName = helper.string("purchaseEquipment", "equipName", _equipName); purchaseEquipment:
checkGangApiAccess("purchaseEquipment"); (ctx: NetscriptContext) =>
const gang = player.gang; (_memberName: unknown, _equipName: unknown): boolean => {
if (gang === null) throw new Error("Should not be called without Gang"); const memberName = ctx.helper.string("memberName", _memberName);
const member = getGangMember("purchaseEquipment", memberName); const equipName = ctx.helper.string("equipName", _equipName);
const equipment = GangMemberUpgrades[equipName]; checkGangApiAccess(ctx);
if (!equipment) return false; const gang = player.gang;
const res = member.buyUpgrade(equipment, player, gang); if (gang === null) throw new Error("Should not be called without Gang");
if (res) { const member = getGangMember(ctx, memberName);
workerScript.log("gang.purchaseEquipment", () => `Purchased '${equipName}' for Gang member '${memberName}'`); const equipment = GangMemberUpgrades[equipName];
} else { if (!equipment) return false;
workerScript.log( const res = member.buyUpgrade(equipment, player, gang);
"gang.purchaseEquipment", if (res) {
() => `Failed to purchase '${equipName}' for Gang member '${memberName}'`, workerScript.log("gang.purchaseEquipment", () => `Purchased '${equipName}' for Gang member '${memberName}'`);
); } else {
} workerScript.log(
"gang.purchaseEquipment",
() => `Failed to purchase '${equipName}' for Gang member '${memberName}'`,
);
}
return res; return res;
}, },
ascendMember: function (_memberName: unknown): GangMemberAscension | undefined { ascendMember:
updateRam("ascendMember"); (ctx: NetscriptContext) =>
const memberName = helper.string("ascendMember", "memberName", _memberName); (_memberName: unknown): GangMemberAscension | undefined => {
checkGangApiAccess("ascendMember"); const memberName = ctx.helper.string("memberName", _memberName);
const gang = player.gang; checkGangApiAccess(ctx);
if (gang === null) throw new Error("Should not be called without Gang"); const gang = player.gang;
const member = getGangMember("ascendMember", memberName); if (gang === null) throw new Error("Should not be called without Gang");
if (!member.canAscend()) return; const member = getGangMember(ctx, memberName);
return gang.ascendMember(member, workerScript); if (!member.canAscend()) return;
}, return gang.ascendMember(member, workerScript);
getAscensionResult: function (_memberName: unknown): GangMemberAscension | undefined { },
updateRam("getAscensionResult"); getAscensionResult:
const memberName = helper.string("getAscensionResult", "memberName", _memberName); (ctx: NetscriptContext) =>
checkGangApiAccess("getAscensionResult"); (_memberName: unknown): GangMemberAscension | undefined => {
const gang = player.gang; const memberName = ctx.helper.string("memberName", _memberName);
if (gang === null) throw new Error("Should not be called without Gang"); checkGangApiAccess(ctx);
const member = getGangMember("getAscensionResult", memberName); const gang = player.gang;
if (!member.canAscend()) return; if (gang === null) throw new Error("Should not be called without Gang");
return { const member = getGangMember(ctx, memberName);
respect: member.earnedRespect, if (!member.canAscend()) return;
...member.getAscensionResults(), return {
}; respect: member.earnedRespect,
}, ...member.getAscensionResults(),
setTerritoryWarfare: function (_engage: unknown): void { };
updateRam("setTerritoryWarfare"); },
const engage = helper.boolean(_engage); setTerritoryWarfare:
checkGangApiAccess("setTerritoryWarfare"); (ctx: NetscriptContext) =>
const gang = player.gang; (_engage: unknown): void => {
if (gang === null) throw new Error("Should not be called without Gang"); const engage = ctx.helper.boolean(_engage);
if (engage) { checkGangApiAccess(ctx);
gang.territoryWarfareEngaged = true; const gang = player.gang;
workerScript.log("gang.setTerritoryWarfare", () => "Engaging in Gang Territory Warfare"); if (gang === null) throw new Error("Should not be called without Gang");
} else { if (engage) {
gang.territoryWarfareEngaged = false; gang.territoryWarfareEngaged = true;
workerScript.log("gang.setTerritoryWarfare", () => "Disengaging in Gang Territory Warfare"); workerScript.log("gang.setTerritoryWarfare", () => "Engaging in Gang Territory Warfare");
} } else {
}, gang.territoryWarfareEngaged = false;
getChanceToWinClash: function (_otherGang: unknown): number { workerScript.log("gang.setTerritoryWarfare", () => "Disengaging in Gang Territory Warfare");
updateRam("getChanceToWinClash"); }
const otherGang = helper.string("getChanceToWinClash", "otherGang", _otherGang); },
checkGangApiAccess("getChanceToWinClash"); getChanceToWinClash:
const gang = player.gang; (ctx: NetscriptContext) =>
if (gang === null) throw new Error("Should not be called without Gang"); (_otherGang: unknown): number => {
if (AllGangs[otherGang] == null) { const otherGang = ctx.helper.string("otherGang", _otherGang);
throw helper.makeRuntimeErrorMsg(`gang.getChanceToWinClash`, `Invalid gang: ${otherGang}`); checkGangApiAccess(ctx);
} const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
if (AllGangs[otherGang] == null) {
throw ctx.makeRuntimeErrorMsg(`Invalid gang: ${otherGang}`);
}
const playerPower = AllGangs[gang.facName].power; const playerPower = AllGangs[gang.facName].power;
const otherPower = AllGangs[otherGang].power; const otherPower = AllGangs[otherGang].power;
return playerPower / (otherPower + playerPower); return playerPower / (otherPower + playerPower);
}, },
getBonusTime: function (): number { getBonusTime: (ctx: NetscriptContext) => (): number => {
updateRam("getBonusTime"); checkGangApiAccess(ctx);
checkGangApiAccess("getBonusTime");
const gang = player.gang; const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang"); if (gang === null) throw new Error("Should not be called without Gang");
return Math.round(gang.storedCycles / 5) * 1000; return Math.round(gang.storedCycles / 5) * 1000;

@ -1,104 +1,97 @@
import { InternalAPI, NetscriptContext } from "../Netscript/APIWrapper";
import { StaticAugmentations } from "../Augmentation/StaticAugmentations"; import { StaticAugmentations } from "../Augmentation/StaticAugmentations";
import { hasAugmentationPrereqs } from "../Faction/FactionHelpers"; import { hasAugmentationPrereqs } from "../Faction/FactionHelpers";
import { CityName } from "../Locations/data/CityNames"; import { CityName } from "../Locations/data/CityNames";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { WorkerScript } from "../Netscript/WorkerScript";
import { GraftableAugmentation } from "../PersonObjects/Grafting/GraftableAugmentation"; import { GraftableAugmentation } from "../PersonObjects/Grafting/GraftableAugmentation";
import { getGraftingAvailableAugs, calculateGraftingTimeWithBonus } from "../PersonObjects/Grafting/GraftingHelpers"; import { getGraftingAvailableAugs, calculateGraftingTimeWithBonus } from "../PersonObjects/Grafting/GraftingHelpers";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { Grafting as IGrafting } from "../ScriptEditor/NetscriptDefinitions"; import { Grafting as IGrafting } from "../ScriptEditor/NetscriptDefinitions";
import { Router } from "../ui/GameRoot"; import { Router } from "../ui/GameRoot";
import { INetscriptHelper } from "./INetscriptHelper";
export function NetscriptGrafting(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): IGrafting { export function NetscriptGrafting(player: IPlayer): InternalAPI<IGrafting> {
const checkGraftingAPIAccess = (func: string): void => { const checkGraftingAPIAccess = (ctx: NetscriptContext): void => {
if (!player.canAccessGrafting()) { if (!player.canAccessGrafting()) {
throw helper.makeRuntimeErrorMsg( throw ctx.makeRuntimeErrorMsg(
`grafting.${func}`,
"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", "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 { return {
getAugmentationGraftPrice: (_augName: unknown): number => { getAugmentationGraftPrice:
updateRam("getAugmentationGraftPrice"); (ctx: NetscriptContext) =>
const augName = helper.string("getAugmentationGraftPrice", "augName", _augName); (_augName: unknown): number => {
checkGraftingAPIAccess("getAugmentationGraftPrice"); const augName = ctx.helper.string("augName", _augName);
if (!getGraftingAvailableAugs(player).includes(augName) || !StaticAugmentations.hasOwnProperty(augName)) { checkGraftingAPIAccess(ctx);
throw helper.makeRuntimeErrorMsg("grafting.getAugmentationGraftPrice", `Invalid aug: ${augName}`); if (!getGraftingAvailableAugs(player).includes(augName) || !StaticAugmentations.hasOwnProperty(augName)) {
} throw ctx.makeRuntimeErrorMsg(`Invalid aug: ${augName}`);
const graftableAug = new GraftableAugmentation(StaticAugmentations[augName]); }
return graftableAug.cost; const graftableAug = new GraftableAugmentation(StaticAugmentations[augName]);
}, return graftableAug.cost;
},
getAugmentationGraftTime: (_augName: string): number => { getAugmentationGraftTime:
updateRam("getAugmentationGraftTime"); (ctx: NetscriptContext) =>
const augName = helper.string("getAugmentationGraftTime", "augName", _augName); (_augName: string): number => {
checkGraftingAPIAccess("getAugmentationGraftTime"); const augName = ctx.helper.string("augName", _augName);
if (!getGraftingAvailableAugs(player).includes(augName) || !StaticAugmentations.hasOwnProperty(augName)) { checkGraftingAPIAccess(ctx);
throw helper.makeRuntimeErrorMsg("grafting.getAugmentationGraftTime", `Invalid aug: ${augName}`); if (!getGraftingAvailableAugs(player).includes(augName) || !StaticAugmentations.hasOwnProperty(augName)) {
} throw ctx.makeRuntimeErrorMsg(`Invalid aug: ${augName}`);
const graftableAug = new GraftableAugmentation(StaticAugmentations[augName]); }
return calculateGraftingTimeWithBonus(player, graftableAug); const graftableAug = new GraftableAugmentation(StaticAugmentations[augName]);
}, return calculateGraftingTimeWithBonus(player, graftableAug);
},
getGraftableAugmentations: (): string[] => { getGraftableAugmentations: (ctx: NetscriptContext) => (): string[] => {
updateRam("getGraftableAugmentations"); checkGraftingAPIAccess(ctx);
checkGraftingAPIAccess("getGraftableAugmentations");
const graftableAugs = getGraftingAvailableAugs(player); const graftableAugs = getGraftingAvailableAugs(player);
return graftableAugs; return graftableAugs;
}, },
graftAugmentation: (_augName: string, _focus: unknown = true): boolean => { graftAugmentation:
updateRam("graftAugmentation"); (ctx: NetscriptContext) =>
const augName = helper.string("graftAugmentation", "augName", _augName); (_augName: string, _focus: unknown = true): boolean => {
const focus = helper.boolean(_focus); const augName = ctx.helper.string("augName", _augName);
checkGraftingAPIAccess("graftAugmentation"); const focus = ctx.helper.boolean(_focus);
if (player.city !== CityName.NewTokyo) { checkGraftingAPIAccess(ctx);
throw helper.makeRuntimeErrorMsg( if (player.city !== CityName.NewTokyo) {
"grafting.graftAugmentation", throw ctx.makeRuntimeErrorMsg("You must be in New Tokyo to begin grafting an Augmentation.");
"You must be in New Tokyo to begin grafting an Augmentation.", }
); if (!getGraftingAvailableAugs(player).includes(augName) || !StaticAugmentations.hasOwnProperty(augName)) {
} ctx.log(() => `Invalid aug: ${augName}`);
if (!getGraftingAvailableAugs(player).includes(augName) || !StaticAugmentations.hasOwnProperty(augName)) { return false;
workerScript.log("grafting.graftAugmentation", () => `Invalid aug: ${augName}`); }
return false;
}
const wasFocusing = player.focus; const wasFocusing = player.focus;
if (player.isWorking) { if (player.isWorking) {
const txt = player.singularityStopWork(); const txt = player.singularityStopWork();
workerScript.log("graftAugmentation", () => txt); ctx.log(() => txt);
} }
const craftableAug = new GraftableAugmentation(StaticAugmentations[augName]); const craftableAug = new GraftableAugmentation(StaticAugmentations[augName]);
if (player.money < craftableAug.cost) { 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; return false;
} }
if (!hasAugmentationPrereqs(craftableAug.augmentation)) { 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; return false;
} }
player.loseMoney(craftableAug.cost, "augmentations"); player.loseMoney(craftableAug.cost, "augmentations");
player.startGraftAugmentationWork(augName, craftableAug.time); player.startGraftAugmentationWork(augName, craftableAug.time);
if (focus) { if (focus) {
player.startFocusing(); player.startFocusing();
Router.toWork(); Router.toWork();
} else if (wasFocusing) { } else if (wasFocusing) {
player.stopFocusing(); player.stopFocusing();
Router.toTerminal(); Router.toTerminal();
} }
workerScript.log("grafting.graftAugmentation", () => `Began grafting Augmentation ${augName}.`); ctx.log(() => `Began grafting Augmentation ${augName}.`);
return true; return true;
}, },
}; };
} }

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

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

@ -1,9 +1,6 @@
import { INetscriptHelper } from "./INetscriptHelper";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { FactionWorkType } from "../Faction/FactionWorkTypeEnum"; import { FactionWorkType } from "../Faction/FactionWorkTypeEnum";
import { SleeveTaskType } from "../PersonObjects/Sleeve/SleeveTaskTypesEnum"; import { SleeveTaskType } from "../PersonObjects/Sleeve/SleeveTaskTypesEnum";
import { WorkerScript } from "../Netscript/WorkerScript";
import { findSleevePurchasableAugs } from "../PersonObjects/Sleeve/SleeveHelpers"; import { findSleevePurchasableAugs } from "../PersonObjects/Sleeve/SleeveHelpers";
import { StaticAugmentations } from "../Augmentation/StaticAugmentations"; import { StaticAugmentations } from "../Augmentation/StaticAugmentations";
import { CityName } from "../Locations/data/CityNames"; import { CityName } from "../Locations/data/CityNames";
@ -17,22 +14,22 @@ import {
SleeveTask, SleeveTask,
} from "../ScriptEditor/NetscriptDefinitions"; } from "../ScriptEditor/NetscriptDefinitions";
import { checkEnum } from "../utils/helpers/checkEnum"; import { checkEnum } from "../utils/helpers/checkEnum";
import { InternalAPI, NetscriptContext } from "../Netscript/APIWrapper";
export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): ISleeve { export function NetscriptSleeve(player: IPlayer): InternalAPI<ISleeve> {
const checkSleeveAPIAccess = function (func: string): void { const checkSleeveAPIAccess = function (ctx: NetscriptContext): void {
if (player.bitNodeN !== 10 && !player.sourceFileLvl(10)) { if (player.bitNodeN !== 10 && !player.sourceFileLvl(10)) {
throw helper.makeRuntimeErrorMsg( throw ctx.makeRuntimeErrorMsg(
`sleeve.${func}`,
"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", "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) { if (sleeveNumber >= player.sleeves.length || sleeveNumber < 0) {
const msg = `Invalid sleeve number: ${sleeveNumber}`; const msg = `Invalid sleeve number: ${sleeveNumber}`;
workerScript.log(func, () => msg); ctx.log(() => msg);
throw helper.makeRuntimeErrorMsg(`sleeve.${func}`, msg); throw ctx.makeRuntimeErrorMsg(msg);
} }
}; };
@ -50,296 +47,299 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
}; };
}; };
const updateRam = (funcName: string): void =>
helper.updateDynamicRam(funcName, getRamCost(player, "sleeve", funcName));
return { return {
getNumSleeves: function (): number { getNumSleeves: (ctx: NetscriptContext) => (): number => {
updateRam("getNumSleeves"); checkSleeveAPIAccess(ctx);
checkSleeveAPIAccess("getNumSleeves");
return player.sleeves.length; return player.sleeves.length;
}, },
setToShockRecovery: function (_sleeveNumber: unknown): boolean { setToShockRecovery:
updateRam("setToShockRecovery"); (ctx: NetscriptContext) =>
const sleeveNumber = helper.number("setToShockRecovery", "sleeveNumber", _sleeveNumber); (_sleeveNumber: unknown): boolean => {
checkSleeveAPIAccess("setToShockRecovery"); const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
checkSleeveNumber("setToShockRecovery", sleeveNumber); checkSleeveAPIAccess(ctx);
return player.sleeves[sleeveNumber].shockRecovery(player); checkSleeveNumber(ctx, sleeveNumber);
}, return player.sleeves[sleeveNumber].shockRecovery(player);
setToSynchronize: function (_sleeveNumber: unknown): boolean { },
updateRam("setToSynchronize"); setToSynchronize:
const sleeveNumber = helper.number("setToSynchronize", "sleeveNumber", _sleeveNumber); (ctx: NetscriptContext) =>
checkSleeveAPIAccess("setToSynchronize"); (_sleeveNumber: unknown): boolean => {
checkSleeveNumber("setToSynchronize", sleeveNumber); const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
return player.sleeves[sleeveNumber].synchronize(player); checkSleeveAPIAccess(ctx);
}, checkSleeveNumber(ctx, sleeveNumber);
setToCommitCrime: function (_sleeveNumber: unknown, _crimeRoughName: unknown): boolean { return player.sleeves[sleeveNumber].synchronize(player);
updateRam("setToCommitCrime"); },
const sleeveNumber = helper.number("setToCommitCrime", "sleeveNumber", _sleeveNumber); setToCommitCrime:
const crimeRoughName = helper.string("setToCommitCrime", "crimeName", _crimeRoughName); (ctx: NetscriptContext) =>
checkSleeveAPIAccess("setToCommitCrime"); (_sleeveNumber: unknown, _crimeRoughName: unknown): boolean => {
checkSleeveNumber("setToCommitCrime", sleeveNumber); const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
const crime = findCrime(crimeRoughName); const crimeRoughName = ctx.helper.string("crimeName", _crimeRoughName);
if (crime === null) { checkSleeveAPIAccess(ctx);
return false; checkSleeveNumber(ctx, sleeveNumber);
} const crime = findCrime(crimeRoughName);
return player.sleeves[sleeveNumber].commitCrime(player, crime.name); if (crime === null) {
}, return false;
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);
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);
if (checkEnum(CityName, cityName)) {
return player.sleeves[sleeveNumber].travel(player, cityName);
} else {
throw helper.makeRuntimeErrorMsg("sleeve.setToCompanyWork", `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);
// Cannot work at the same company that another sleeve is working at
for (let i = 0; i < player.sleeves.length; ++i) {
if (i === sleeveNumber) {
continue;
} }
const other = player.sleeves[i]; return player.sleeves[sleeveNumber].commitCrime(player, crime.name);
if (other.currentTask === SleeveTaskType.Company && other.currentTaskLocation === companyName) { },
throw helper.makeRuntimeErrorMsg( setToUniversityCourse:
"sleeve.setToCompanyWork", (ctx: NetscriptContext) =>
`Sleeve ${sleeveNumber} cannot work for company ${companyName} because Sleeve ${i} is already working for them.`, (_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:
(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 ctx.makeRuntimeErrorMsg(`Invalid city name: '${cityName}'.`);
} }
} },
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);
return player.sleeves[sleeveNumber].workForCompany(player, companyName); // Cannot work at the same company that another sleeve is working at
},
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);
// Cannot work at the same faction that another sleeve is working at
for (let i = 0; i < player.sleeves.length; ++i) {
if (i === sleeveNumber) {
continue;
}
const other = player.sleeves[i];
if (other.currentTask === SleeveTaskType.Faction && other.currentTaskLocation === factionName) {
throw helper.makeRuntimeErrorMsg(
"sleeve.setToFactionWork",
`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",
`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);
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);
return getSleeveStats(sleeveNumber);
},
getTask: function (_sleeveNumber: unknown): SleeveTask {
updateRam("getTask");
const sleeveNumber = helper.number("getTask", "sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess("getTask");
checkSleeveNumber("getTask", sleeveNumber);
const sl = player.sleeves[sleeveNumber];
return {
task: SleeveTaskType[sl.currentTask],
crime: sl.crimeType,
location: sl.currentTaskLocation,
gymStatType: sl.gymStatType,
factionWorkType: FactionWorkType[sl.factionWorkType],
};
},
getInformation: function (_sleeveNumber: unknown): SleeveInformation {
updateRam("getInformation");
const sleeveNumber = helper.number("getInformation", "sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess("getInformation");
checkSleeveNumber("getInformation", sleeveNumber);
const sl = player.sleeves[sleeveNumber];
return {
tor: false,
city: sl.city,
hp: sl.hp,
jobs: Object.keys(player.jobs), // technically sleeves have the same jobs as the player.
jobTitle: Object.values(player.jobs),
maxHp: sl.max_hp,
mult: {
agility: sl.agility_mult,
agilityExp: sl.agility_exp_mult,
charisma: sl.charisma_mult,
charismaExp: sl.charisma_exp_mult,
companyRep: sl.company_rep_mult,
crimeMoney: sl.crime_money_mult,
crimeSuccess: sl.crime_success_mult,
defense: sl.defense_mult,
defenseExp: sl.defense_exp_mult,
dexterity: sl.dexterity_mult,
dexterityExp: sl.dexterity_exp_mult,
factionRep: sl.faction_rep_mult,
hacking: sl.hacking_mult,
hackingExp: sl.hacking_exp_mult,
strength: sl.strength_mult,
strengthExp: sl.strength_exp_mult,
workMoney: sl.work_money_mult,
},
timeWorked: sl.currentTaskTime,
earningsForSleeves: {
workHackExpGain: sl.earningsForSleeves.hack,
workStrExpGain: sl.earningsForSleeves.str,
workDefExpGain: sl.earningsForSleeves.def,
workDexExpGain: sl.earningsForSleeves.dex,
workAgiExpGain: sl.earningsForSleeves.agi,
workChaExpGain: sl.earningsForSleeves.cha,
workMoneyGain: sl.earningsForSleeves.money,
},
earningsForPlayer: {
workHackExpGain: sl.earningsForPlayer.hack,
workStrExpGain: sl.earningsForPlayer.str,
workDefExpGain: sl.earningsForPlayer.def,
workDexExpGain: sl.earningsForPlayer.dex,
workAgiExpGain: sl.earningsForPlayer.agi,
workChaExpGain: sl.earningsForPlayer.cha,
workMoneyGain: sl.earningsForPlayer.money,
},
earningsForTask: {
workHackExpGain: sl.earningsForTask.hack,
workStrExpGain: sl.earningsForTask.str,
workDefExpGain: sl.earningsForTask.def,
workDexExpGain: sl.earningsForTask.dex,
workAgiExpGain: sl.earningsForTask.agi,
workChaExpGain: sl.earningsForTask.cha,
workMoneyGain: sl.earningsForTask.money,
},
workRepGain: sl.getRepGain(player),
};
},
getSleeveAugmentations: function (_sleeveNumber: unknown): string[] {
updateRam("getSleeveAugmentations");
const sleeveNumber = helper.number("getSleeveAugmentations", "sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess("getSleeveAugmentations");
checkSleeveNumber("getSleeveAugmentations", sleeveNumber);
const augs = [];
for (let i = 0; i < player.sleeves[sleeveNumber].augmentations.length; i++) {
augs.push(player.sleeves[sleeveNumber].augmentations[i].name);
}
return augs;
},
getSleevePurchasableAugs: function (_sleeveNumber: unknown): AugmentPair[] {
updateRam("getSleevePurchasableAugs");
const sleeveNumber = helper.number("getSleevePurchasableAugs", "sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess("getSleevePurchasableAugs");
checkSleeveNumber("getSleevePurchasableAugs", sleeveNumber);
const purchasableAugs = findSleevePurchasableAugs(player.sleeves[sleeveNumber], player);
const augs = [];
for (let i = 0; i < purchasableAugs.length; i++) {
const aug = purchasableAugs[i];
augs.push({
name: aug.name,
cost: aug.baseCost,
});
}
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);
if (getSleeveStats(sleeveNumber).shock > 0) {
throw helper.makeRuntimeErrorMsg("sleeve.purchaseSleeveAug", `Sleeve shock too high: Sleeve ${sleeveNumber}`);
}
const aug = StaticAugmentations[augName];
if (!aug) {
throw helper.makeRuntimeErrorMsg("sleeve.purchaseSleeveAug", `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);
let contract: string;
if (typeof _contract === "undefined") {
contract = "------";
} else {
contract = helper.string("setToBladeburnerAction", "contract", _contract);
}
checkSleeveAPIAccess("setToBladeburnerAction");
checkSleeveNumber("setToBladeburnerAction", sleeveNumber);
// Cannot Take on Contracts if another sleeve is performing that action
if (action === "Take on contracts") {
for (let i = 0; i < player.sleeves.length; ++i) { for (let i = 0; i < player.sleeves.length; ++i) {
if (i === sleeveNumber) { if (i === sleeveNumber) {
continue; continue;
} }
const other = player.sleeves[i]; const other = player.sleeves[i];
if (other.currentTask === SleeveTaskType.Bladeburner && other.bbAction === action) { if (other.currentTask === SleeveTaskType.Company && other.currentTaskLocation === companyName) {
throw helper.makeRuntimeErrorMsg( throw ctx.makeRuntimeErrorMsg(
"sleeve.setToBladeburnerAction", `Sleeve ${sleeveNumber} cannot work for company ${companyName} because Sleeve ${i} is already working for them.`,
`Sleeve ${sleeveNumber} cannot take of contracts because Sleeve ${i} is already performing that action.`,
); );
} }
} }
}
return player.sleeves[sleeveNumber].bladeburner(player, action, contract); return player.sleeves[sleeveNumber].workForCompany(player, companyName);
}, },
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) {
if (i === sleeveNumber) {
continue;
}
const other = player.sleeves[i];
if (other.currentTask === SleeveTaskType.Faction && other.currentTaskLocation === factionName) {
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 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:
(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:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown): SleeveSkills => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
return getSleeveStats(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 {
task: SleeveTaskType[sl.currentTask],
crime: sl.crimeType,
location: sl.currentTaskLocation,
gymStatType: sl.gymStatType,
factionWorkType: FactionWorkType[sl.factionWorkType],
};
},
getInformation:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown): SleeveInformation => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
const sl = player.sleeves[sleeveNumber];
return {
tor: false,
city: sl.city,
hp: sl.hp,
jobs: Object.keys(player.jobs), // technically sleeves have the same jobs as the player.
jobTitle: Object.values(player.jobs),
maxHp: sl.max_hp,
mult: {
agility: sl.agility_mult,
agilityExp: sl.agility_exp_mult,
charisma: sl.charisma_mult,
charismaExp: sl.charisma_exp_mult,
companyRep: sl.company_rep_mult,
crimeMoney: sl.crime_money_mult,
crimeSuccess: sl.crime_success_mult,
defense: sl.defense_mult,
defenseExp: sl.defense_exp_mult,
dexterity: sl.dexterity_mult,
dexterityExp: sl.dexterity_exp_mult,
factionRep: sl.faction_rep_mult,
hacking: sl.hacking_mult,
hackingExp: sl.hacking_exp_mult,
strength: sl.strength_mult,
strengthExp: sl.strength_exp_mult,
workMoney: sl.work_money_mult,
},
timeWorked: sl.currentTaskTime,
earningsForSleeves: {
workHackExpGain: sl.earningsForSleeves.hack,
workStrExpGain: sl.earningsForSleeves.str,
workDefExpGain: sl.earningsForSleeves.def,
workDexExpGain: sl.earningsForSleeves.dex,
workAgiExpGain: sl.earningsForSleeves.agi,
workChaExpGain: sl.earningsForSleeves.cha,
workMoneyGain: sl.earningsForSleeves.money,
},
earningsForPlayer: {
workHackExpGain: sl.earningsForPlayer.hack,
workStrExpGain: sl.earningsForPlayer.str,
workDefExpGain: sl.earningsForPlayer.def,
workDexExpGain: sl.earningsForPlayer.dex,
workAgiExpGain: sl.earningsForPlayer.agi,
workChaExpGain: sl.earningsForPlayer.cha,
workMoneyGain: sl.earningsForPlayer.money,
},
earningsForTask: {
workHackExpGain: sl.earningsForTask.hack,
workStrExpGain: sl.earningsForTask.str,
workDefExpGain: sl.earningsForTask.def,
workDexExpGain: sl.earningsForTask.dex,
workAgiExpGain: sl.earningsForTask.agi,
workChaExpGain: sl.earningsForTask.cha,
workMoneyGain: sl.earningsForTask.money,
},
workRepGain: sl.getRepGain(player),
};
},
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++) {
augs.push(player.sleeves[sleeveNumber].augmentations[i].name);
}
return augs;
},
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 = [];
for (let i = 0; i < purchasableAugs.length; i++) {
const aug = purchasableAugs[i];
augs.push({
name: aug.name,
cost: aug.baseCost,
});
}
return augs;
},
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 ctx.makeRuntimeErrorMsg(`Sleeve shock too high: Sleeve ${sleeveNumber}`);
}
const aug = StaticAugmentations[augName];
if (!aug) {
throw ctx.makeRuntimeErrorMsg(`Invalid aug: ${augName}`);
}
return player.sleeves[sleeveNumber].tryBuyAugmentation(player, aug);
},
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 = ctx.helper.string("contract", _contract);
}
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
// Cannot Take on Contracts if another sleeve is performing that action
if (action === "Take on contracts") {
for (let i = 0; i < player.sleeves.length; ++i) {
if (i === sleeveNumber) {
continue;
}
const other = player.sleeves[i];
if (other.currentTask === SleeveTaskType.Bladeburner && other.bbAction === action) {
throw ctx.helper.makeRuntimeErrorMsg(
`Sleeve ${sleeveNumber} cannot take of contracts because Sleeve ${i} is already performing that action.`,
);
}
}
}
return player.sleeves[sleeveNumber].bladeburner(player, action, contract);
},
}; };
} }

@ -5,6 +5,7 @@ import { netscriptDelay } from "../NetscriptEvaluator";
import { staneksGift } from "../CotMG/Helper"; import { staneksGift } from "../CotMG/Helper";
import { Fragments, FragmentById } from "../CotMG/Fragment"; import { Fragments, FragmentById } from "../CotMG/Fragment";
import { FragmentType } from "../CotMG/FragmentType";
import { import {
Fragment as IFragment, Fragment as IFragment,
@ -25,7 +26,7 @@ export function NetscriptStanek(
): InternalAPI<IStanek> { ): InternalAPI<IStanek> {
function checkStanekAPIAccess(func: string): void { function checkStanekAPIAccess(func: string): void {
if (!player.hasAugmentation(AugmentationNames.StaneksGift1, true)) { 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) => chargeFragment: (_ctx: NetscriptContext) =>
function (_rootX: unknown, _rootY: unknown): Promise<void> { function (_rootX: unknown, _rootY: unknown): Promise<void> {
//Get the fragment object using the given coordinates
const rootX = _ctx.helper.number("rootX", _rootX); const rootX = _ctx.helper.number("rootX", _rootX);
const rootY = _ctx.helper.number("rootY", _rootY); const rootY = _ctx.helper.number("rootY", _rootY);
checkStanekAPIAccess("chargeFragment"); checkStanekAPIAccess("chargeFragment");
const fragment = staneksGift.findFragment(rootX, rootY); 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) 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; const time = staneksGift.inBonus() ? 200 : 1000;
return netscriptDelay(time, workerScript).then(function () { return netscriptDelay(time, workerScript).then(function () {
const charge = staneksGift.charge(player, fragment, workerScript.scriptRef.threads); staneksGift.charge(player, fragment, workerScript.scriptRef.threads);
_ctx.log(() => `Charged fragment for ${charge} charge.`); _ctx.log(() => `Charged fragment with ${_ctx.workerScript.scriptRef.threads} threads.`);
return Promise.resolve(); return Promise.resolve();
}); });
}, },

@ -1,7 +1,5 @@
import { INetscriptHelper } from "./INetscriptHelper";
import { WorkerScript } from "../Netscript/WorkerScript"; import { WorkerScript } from "../Netscript/WorkerScript";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { buyStock, sellStock, shortStock, sellShort } from "../StockMarket/BuyingAndSelling"; import { buyStock, sellStock, shortStock, sellShort } from "../StockMarket/BuyingAndSelling";
import { StockMarket, SymbolToStockMap, placeOrder, cancelOrder, initStockMarketFn } from "../StockMarket/StockMarket"; import { StockMarket, SymbolToStockMap, placeOrder, cancelOrder, initStockMarketFn } from "../StockMarket/StockMarket";
import { getBuyTransactionCost, getSellTransactionGain } from "../StockMarket/StockMarketHelpers"; import { getBuyTransactionCost, getSellTransactionGain } from "../StockMarket/StockMarketHelpers";
@ -16,301 +14,286 @@ import {
} from "../StockMarket/StockMarketCosts"; } from "../StockMarket/StockMarketCosts";
import { Stock } from "../StockMarket/Stock"; import { Stock } from "../StockMarket/Stock";
import { TIX } from "../ScriptEditor/NetscriptDefinitions"; 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 * 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) { 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) { 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]; const stock = SymbolToStockMap[symbol];
if (stock == null) { if (stock == null) {
throw helper.makeRuntimeErrorMsg(callingFn, `Invalid stock symbol: '${symbol}'`); throw ctx.makeRuntimeErrorMsg(`Invalid stock symbol: '${symbol}'`);
} }
return stock; return stock;
}; };
const updateRam = (funcName: string): void =>
helper.updateDynamicRam(funcName, getRamCost(player, "stock", funcName));
return { return {
getSymbols: function (): string[] { getSymbols: (ctx: NetscriptContext) => (): string[] => {
updateRam("getSymbols"); checkTixApiAccess(ctx);
checkTixApiAccess("getSymbols");
return Object.values(StockSymbols); return Object.values(StockSymbols);
}, },
getPrice: function (_symbol: unknown): number { getPrice:
updateRam("getPrice"); (ctx: NetscriptContext) =>
const symbol = helper.string("getPrice", "symbol", _symbol); (_symbol: unknown): number => {
checkTixApiAccess("getPrice"); const symbol = ctx.helper.string("symbol", _symbol);
const stock = getStockFromSymbol(symbol, "getPrice"); checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
return stock.price; return stock.price;
}, },
getAskPrice: function (_symbol: unknown): number { getAskPrice:
updateRam("getAskPrice"); (ctx: NetscriptContext) =>
const symbol = helper.string("getAskPrice", "symbol", _symbol); (_symbol: unknown): number => {
checkTixApiAccess("getAskPrice"); const symbol = ctx.helper.string("symbol", _symbol);
const stock = getStockFromSymbol(symbol, "getAskPrice"); checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
return stock.getAskPrice(); return stock.getAskPrice();
}, },
getBidPrice: function (_symbol: unknown): number { getBidPrice:
updateRam("getBidPrice"); (ctx: NetscriptContext) =>
const symbol = helper.string("getBidPrice", "symbol", _symbol); (_symbol: unknown): number => {
checkTixApiAccess("getBidPrice"); const symbol = ctx.helper.string("symbol", _symbol);
const stock = getStockFromSymbol(symbol, "getBidPrice"); checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
return stock.getBidPrice(); return stock.getBidPrice();
}, },
getPosition: function (_symbol: unknown): [number, number, number, number] { getPosition:
updateRam("getPosition"); (ctx: NetscriptContext) =>
const symbol = helper.string("getPosition", "symbol", _symbol); (_symbol: unknown): [number, number, number, number] => {
checkTixApiAccess("getPosition"); const symbol = ctx.helper.string("symbol", _symbol);
const stock = SymbolToStockMap[symbol]; checkTixApiAccess(ctx);
if (stock == null) { const stock = SymbolToStockMap[symbol];
throw helper.makeRuntimeErrorMsg("getPosition", `Invalid stock symbol: ${symbol}`); if (stock == null) {
} 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");
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");
shares = Math.round(shares);
let pos;
const sanitizedPosType = posType.toLowerCase();
if (sanitizedPosType.includes("l")) {
pos = PositionTypes.Long;
} else if (sanitizedPosType.includes("s")) {
pos = PositionTypes.Short;
} else {
return Infinity;
}
const res = getBuyTransactionCost(stock, shares, pos);
if (res == null) {
return Infinity;
}
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");
shares = Math.round(shares);
let pos;
const sanitizedPosType = posType.toLowerCase();
if (sanitizedPosType.includes("l")) {
pos = PositionTypes.Long;
} else if (sanitizedPosType.includes("s")) {
pos = PositionTypes.Short;
} else {
return 0;
}
const res = getSellTransactionGain(stock, shares, pos);
if (res == null) {
return 0;
}
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");
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");
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");
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.",
);
} }
} return [stock.playerShares, stock.playerAvgPx, stock.playerShortShares, stock.playerAvgShortPx];
const stock = getStockFromSymbol(symbol, "short"); },
const res = shortStock(stock, shares, workerScript, {}); getMaxShares:
(ctx: NetscriptContext) =>
(_symbol: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
return res ? stock.getBidPrice() : 0; return stock.maxShares;
}, },
sellShort: function (_symbol: unknown, _shares: unknown): number { getPurchaseCost:
updateRam("sellShort"); (ctx: NetscriptContext) =>
const symbol = helper.string("sellShort", "symbol", _symbol); (_symbol: unknown, _shares: unknown, _posType: unknown): number => {
const shares = helper.number("sellShort", "shares", _shares); const symbol = ctx.helper.string("symbol", _symbol);
checkTixApiAccess("sellShort"); let shares = ctx.helper.number("shares", _shares);
if (player.bitNodeN !== 8) { const posType = ctx.helper.string("posType", _posType);
if (player.sourceFileLvl(8) <= 1) { checkTixApiAccess(ctx);
throw helper.makeRuntimeErrorMsg( const stock = getStockFromSymbol(ctx, symbol);
"sellShort", shares = Math.round(shares);
"You must either be in BitNode-8 or you must have Source-File 8 Level 2.",
); let pos;
const sanitizedPosType = posType.toLowerCase();
if (sanitizedPosType.includes("l")) {
pos = PositionTypes.Long;
} else if (sanitizedPosType.includes("s")) {
pos = PositionTypes.Short;
} else {
return Infinity;
} }
}
const stock = getStockFromSymbol(symbol, "sellShort");
const res = sellShort(stock, shares, workerScript, {});
return res ? stock.getAskPrice() : 0; const res = getBuyTransactionCost(stock, shares, pos);
}, if (res == null) {
placeOrder: function (_symbol: unknown, _shares: unknown, _price: unknown, _type: unknown, _pos: unknown): boolean { return Infinity;
updateRam("placeOrder"); }
const symbol = helper.string("placeOrder", "symbol", _symbol);
const shares = helper.number("placeOrder", "shares", _shares); return res;
const price = helper.number("placeOrder", "price", _price); },
const type = helper.string("placeOrder", "type", _type); getSaleGain:
const pos = helper.string("placeOrder", "pos", _pos); (ctx: NetscriptContext) =>
checkTixApiAccess("placeOrder"); (_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;
const sanitizedPosType = posType.toLowerCase();
if (sanitizedPosType.includes("l")) {
pos = PositionTypes.Long;
} else if (sanitizedPosType.includes("s")) {
pos = PositionTypes.Short;
} else {
return 0;
}
const res = getSellTransactionGain(stock, shares, pos);
if (res == null) {
return 0;
}
return res;
},
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:
(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:
(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 ctx.makeRuntimeErrorMsg("You must either be in BitNode-8 or you must have Source-File 8 Level 2.");
}
}
const stock = getStockFromSymbol(ctx, symbol);
const res = shortStock(stock, shares, workerScript, {});
return res ? stock.getBidPrice() : 0;
},
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 ctx.makeRuntimeErrorMsg("You must either be in BitNode-8 or you must have Source-File 8 Level 2.");
}
}
const stock = getStockFromSymbol(ctx, symbol);
const res = sellShort(stock, shares, workerScript, {});
return res ? stock.getAskPrice() : 0;
},
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 ctx.makeRuntimeErrorMsg("You must either be in BitNode-8 or you must have Source-File 8 Level 3.");
}
}
const stock = getStockFromSymbol(ctx, symbol);
let orderType;
let orderPos;
const ltype = type.toLowerCase();
if (ltype.includes("limit") && ltype.includes("buy")) {
orderType = OrderTypes.LimitBuy;
} else if (ltype.includes("limit") && ltype.includes("sell")) {
orderType = OrderTypes.LimitSell;
} else if (ltype.includes("stop") && ltype.includes("buy")) {
orderType = OrderTypes.StopBuy;
} else if (ltype.includes("stop") && ltype.includes("sell")) {
orderType = OrderTypes.StopSell;
} else {
throw ctx.makeRuntimeErrorMsg(`Invalid order type: ${type}`);
}
const lpos = pos.toLowerCase();
if (lpos.includes("l")) {
orderPos = PositionTypes.Long;
} else if (lpos.includes("s")) {
orderPos = PositionTypes.Short;
} else {
throw ctx.makeRuntimeErrorMsg(`Invalid position type: ${pos}`);
}
return placeOrder(stock, shares, price, orderType, orderPos, workerScript);
},
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 ctx.makeRuntimeErrorMsg("You must either be in BitNode-8 or you must have Source-File 8 Level 3.");
}
}
const stock = getStockFromSymbol(ctx, symbol);
if (isNaN(shares) || isNaN(price)) {
throw ctx.makeRuntimeErrorMsg(`Invalid shares or price. Must be numeric. shares=${shares}, price=${price}`);
}
let orderType;
let orderPos;
const ltype = type.toLowerCase();
if (ltype.includes("limit") && ltype.includes("buy")) {
orderType = OrderTypes.LimitBuy;
} else if (ltype.includes("limit") && ltype.includes("sell")) {
orderType = OrderTypes.LimitSell;
} else if (ltype.includes("stop") && ltype.includes("buy")) {
orderType = OrderTypes.StopBuy;
} else if (ltype.includes("stop") && ltype.includes("sell")) {
orderType = OrderTypes.StopSell;
} else {
throw ctx.makeRuntimeErrorMsg(`Invalid order type: ${type}`);
}
const lpos = pos.toLowerCase();
if (lpos.includes("l")) {
orderPos = PositionTypes.Long;
} else if (lpos.includes("s")) {
orderPos = PositionTypes.Short;
} else {
throw ctx.makeRuntimeErrorMsg(`Invalid position type: ${pos}`);
}
const params = {
stock: stock,
shares: shares,
price: price,
type: orderType,
pos: orderPos,
};
return cancelOrder(params, workerScript);
},
getOrders: (ctx: NetscriptContext) => (): any => {
checkTixApiAccess(ctx);
if (player.bitNodeN !== 8) { if (player.bitNodeN !== 8) {
if (player.sourceFileLvl(8) <= 2) { if (player.sourceFileLvl(8) <= 2) {
throw helper.makeRuntimeErrorMsg( throw ctx.makeRuntimeErrorMsg("You must either be in BitNode-8 or have Source-File 8 Level 3.");
"placeOrder",
"You must either be in BitNode-8 or you must have Source-File 8 Level 3.",
);
}
}
const stock = getStockFromSymbol(symbol, "placeOrder");
let orderType;
let orderPos;
const ltype = type.toLowerCase();
if (ltype.includes("limit") && ltype.includes("buy")) {
orderType = OrderTypes.LimitBuy;
} else if (ltype.includes("limit") && ltype.includes("sell")) {
orderType = OrderTypes.LimitSell;
} else if (ltype.includes("stop") && ltype.includes("buy")) {
orderType = OrderTypes.StopBuy;
} else if (ltype.includes("stop") && ltype.includes("sell")) {
orderType = OrderTypes.StopSell;
} else {
throw helper.makeRuntimeErrorMsg("placeOrder", `Invalid order type: ${type}`);
}
const lpos = pos.toLowerCase();
if (lpos.includes("l")) {
orderPos = PositionTypes.Long;
} else if (lpos.includes("s")) {
orderPos = PositionTypes.Short;
} else {
throw helper.makeRuntimeErrorMsg("placeOrder", `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");
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.",
);
}
}
const stock = getStockFromSymbol(symbol, "cancelOrder");
if (isNaN(shares) || isNaN(price)) {
throw helper.makeRuntimeErrorMsg(
"cancelOrder",
`Invalid shares or price. Must be numeric. shares=${shares}, price=${price}`,
);
}
let orderType;
let orderPos;
const ltype = type.toLowerCase();
if (ltype.includes("limit") && ltype.includes("buy")) {
orderType = OrderTypes.LimitBuy;
} else if (ltype.includes("limit") && ltype.includes("sell")) {
orderType = OrderTypes.LimitSell;
} else if (ltype.includes("stop") && ltype.includes("buy")) {
orderType = OrderTypes.StopBuy;
} else if (ltype.includes("stop") && ltype.includes("sell")) {
orderType = OrderTypes.StopSell;
} else {
throw helper.makeRuntimeErrorMsg("cancelOrder", `Invalid order type: ${type}`);
}
const lpos = pos.toLowerCase();
if (lpos.includes("l")) {
orderPos = PositionTypes.Long;
} else if (lpos.includes("s")) {
orderPos = PositionTypes.Short;
} else {
throw helper.makeRuntimeErrorMsg("cancelOrder", `Invalid position type: ${pos}`);
}
const params = {
stock: stock,
shares: shares,
price: price,
type: orderType,
pos: orderPos,
};
return cancelOrder(params, workerScript);
},
getOrders: function (): any {
updateRam("getOrders");
checkTixApiAccess("getOrders");
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.",
);
} }
} }
@ -334,103 +317,95 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
return orders; return orders;
}, },
getVolatility: function (_symbol: unknown): number { getVolatility:
updateRam("getVolatility"); (ctx: NetscriptContext) =>
const symbol = helper.string("getVolatility", "symbol", _symbol); (_symbol: unknown): number => {
if (!player.has4SDataTixApi) { const symbol = ctx.helper.string("symbol", _symbol);
throw helper.makeRuntimeErrorMsg("getVolatility", "You don't have 4S Market Data TIX API Access!"); if (!player.has4SDataTixApi) {
} 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 return stock.mv / 100; // Convert from percentage to decimal
}, },
getForecast: function (_symbol: unknown): number { getForecast:
updateRam("getForecast"); (ctx: NetscriptContext) =>
const symbol = helper.string("getForecast", "symbol", _symbol); (_symbol: unknown): number => {
if (!player.has4SDataTixApi) { const symbol = ctx.helper.string("symbol", _symbol);
throw helper.makeRuntimeErrorMsg("getForecast", "You don't have 4S Market Data TIX API Access!"); if (!player.has4SDataTixApi) {
} 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");
let forecast = 50;
stock.b ? (forecast += stock.otlkMag) : (forecast -= stock.otlkMag);
return forecast / 100; // Convert from percentage to decimal
},
purchase4SMarketData: (ctx: NetscriptContext) => (): boolean => {
if (player.has4SData) { if (player.has4SData) {
workerScript.log("stock.purchase4SMarketData", () => "Already purchased 4S Market Data."); ctx.log(() => "Already purchased 4S Market Data.");
return true; return true;
} }
if (player.money < getStockMarket4SDataCost()) { 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; return false;
} }
player.has4SData = true; player.has4SData = true;
player.loseMoney(getStockMarket4SDataCost(), "stock"); player.loseMoney(getStockMarket4SDataCost(), "stock");
workerScript.log("stock.purchase4SMarketData", () => "Purchased 4S Market Data"); ctx.log(() => "Purchased 4S Market Data");
return true; return true;
}, },
purchase4SMarketDataTixApi: function (): boolean { purchase4SMarketDataTixApi: (ctx: NetscriptContext) => (): boolean => {
updateRam("purchase4SMarketDataTixApi"); checkTixApiAccess(ctx);
checkTixApiAccess("purchase4SMarketDataTixApi");
if (player.has4SDataTixApi) { 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; return true;
} }
if (player.money < getStockMarket4STixApiCost()) { if (player.money < getStockMarket4STixApiCost()) {
workerScript.log( ctx.log(() => "Not enough money to purchase 4S Market Data TIX API");
"stock.purchase4SMarketDataTixApi",
() => "Not enough money to purchase 4S Market Data TIX API",
);
return false; return false;
} }
player.has4SDataTixApi = true; player.has4SDataTixApi = true;
player.loseMoney(getStockMarket4STixApiCost(), "stock"); player.loseMoney(getStockMarket4STixApiCost(), "stock");
workerScript.log("stock.purchase4SMarketDataTixApi", () => "Purchased 4S Market Data TIX API"); ctx.log(() => "Purchased 4S Market Data TIX API");
return true; return true;
}, },
purchaseWseAccount: function (): boolean { purchaseWseAccount: (ctx: NetscriptContext) => (): boolean => {
updateRam("PurchaseWseAccount");
if (player.hasWseAccount) { if (player.hasWseAccount) {
workerScript.log("stock.purchaseWseAccount", () => "Already purchased WSE Account"); ctx.log(() => "Already purchased WSE Account");
return true; return true;
} }
if (player.money < getStockMarketWseCost()) { 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; return false;
} }
player.hasWseAccount = true; player.hasWseAccount = true;
initStockMarketFn(); initStockMarketFn();
player.loseMoney(getStockMarketWseCost(), "stock"); player.loseMoney(getStockMarketWseCost(), "stock");
workerScript.log("stock.purchaseWseAccount", () => "Purchased WSE Account Access"); ctx.log(() => "Purchased WSE Account Access");
return true; return true;
}, },
purchaseTixApi: function (): boolean { purchaseTixApi: (ctx: NetscriptContext) => (): boolean => {
updateRam("purchaseTixApi");
if (player.hasTixApiAccess) { if (player.hasTixApiAccess) {
workerScript.log("stock.purchaseTixApi", () => "Already purchased TIX API"); ctx.log(() => "Already purchased TIX API");
return true; return true;
} }
if (player.money < getStockMarketTixApiCost()) { 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; return false;
} }
player.hasTixApiAccess = true; player.hasTixApiAccess = true;
player.loseMoney(getStockMarketTixApiCost(), "stock"); player.loseMoney(getStockMarketTixApiCost(), "stock");
workerScript.log("stock.purchaseTixApi", () => "Purchased TIX API"); ctx.log(() => "Purchased TIX API");
return true; 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 { import {
GameInfo, GameInfo,
IStyleSettings, IStyleSettings,
@ -14,88 +10,81 @@ import { defaultTheme } from "../Themes/Themes";
import { defaultStyles } from "../Themes/Styles"; import { defaultStyles } from "../Themes/Styles";
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../Constants";
import { hash } from "../hash/hash"; import { hash } from "../hash/hash";
import { InternalAPI, NetscriptContext } from "src/Netscript/APIWrapper";
import { Terminal } from "../../src/Terminal";
export function NetscriptUserInterface( export function NetscriptUserInterface(): InternalAPI<IUserInterface> {
player: IPlayer,
workerScript: WorkerScript,
helper: INetscriptHelper,
): IUserInterface {
const updateRam = (funcName: string): void => helper.updateDynamicRam(funcName, getRamCost(player, "ui", funcName));
return { return {
getTheme: function (): UserInterfaceTheme { getTheme: () => (): UserInterfaceTheme => {
updateRam("getTheme");
return { ...Settings.theme }; return { ...Settings.theme };
}, },
getStyles: function (): IStyleSettings { getStyles: () => (): IStyleSettings => {
updateRam("getStyles");
return { ...Settings.styles }; return { ...Settings.styles };
}, },
setTheme: function (newTheme: UserInterfaceTheme): void { setTheme:
updateRam("setTheme"); (ctx: NetscriptContext) =>
const hex = /^(#)((?:[A-Fa-f0-9]{2}){3,4}|(?:[A-Fa-f0-9]{3}))$/; (newTheme: UserInterfaceTheme): void => {
const currentTheme = { ...Settings.theme }; const hex = /^(#)((?:[A-Fa-f0-9]{2}){3,4}|(?:[A-Fa-f0-9]{3}))$/;
const errors: string[] = []; const currentTheme = { ...Settings.theme };
for (const key of Object.keys(newTheme)) { const errors: string[] = [];
if (!currentTheme[key]) { for (const key of Object.keys(newTheme)) {
// Invalid key if (!currentTheme[key]) {
errors.push(`Invalid key "${key}"`); // Invalid key
} else if (!hex.test(newTheme[key] ?? "")) { errors.push(`Invalid key "${key}"`);
errors.push(`Invalid color "${key}": ${newTheme[key]}`); } else if (!hex.test(newTheme[key] ?? "")) {
} else { errors.push(`Invalid color "${key}": ${newTheme[key]}`);
currentTheme[key] = newTheme[key]; } else {
currentTheme[key] = newTheme[key];
}
} }
}
if (errors.length === 0) { if (errors.length === 0) {
Object.assign(Settings.theme, currentTheme); Object.assign(Settings.theme, currentTheme);
ThemeEvents.emit(); 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(", ")}`);
}
},
setStyles: function (newStyles: IStyleSettings): void {
updateRam("setStyles");
const currentStyles = { ...Settings.styles };
const errors: string[] = [];
for (const key of Object.keys(newStyles)) {
if (!(currentStyles as any)[key]) {
// Invalid key
errors.push(`Invalid key "${key}"`);
} else { } else {
(currentStyles as any)[key] = (newStyles as any)[key]; ctx.log(() => `Failed to set theme. Errors: ${errors.join(", ")}`);
} }
} },
if (errors.length === 0) { setStyles:
Object.assign(Settings.styles, currentStyles); (ctx: NetscriptContext) =>
ThemeEvents.emit(); (newStyles: IStyleSettings): void => {
workerScript.log("ui.setStyles", () => `Successfully set styles`); const currentStyles = { ...Settings.styles };
} else { const errors: string[] = [];
workerScript.log("ui.setStyles", () => `Failed to set styles. Errors: ${errors.join(", ")}`); for (const key of Object.keys(newStyles)) {
} if (!(currentStyles as any)[key]) {
}, // Invalid key
errors.push(`Invalid key "${key}"`);
} else {
(currentStyles as any)[key] = (newStyles as any)[key];
}
}
resetTheme: function (): void { if (errors.length === 0) {
updateRam("resetTheme"); Object.assign(Settings.styles, currentStyles);
ThemeEvents.emit();
ctx.log(() => `Successfully set styles`);
} else {
ctx.log(() => `Failed to set styles. Errors: ${errors.join(", ")}`);
}
},
resetTheme: (ctx: NetscriptContext) => (): void => {
Settings.theme = { ...defaultTheme }; Settings.theme = { ...defaultTheme };
ThemeEvents.emit(); ThemeEvents.emit();
workerScript.log("ui.resetTheme", () => `Reinitialized theme to default`); ctx.log(() => `Reinitialized theme to default`);
}, },
resetStyles: function (): void { resetStyles: (ctx: NetscriptContext) => (): void => {
updateRam("resetStyles");
Settings.styles = { ...defaultStyles }; Settings.styles = { ...defaultStyles };
ThemeEvents.emit(); ThemeEvents.emit();
workerScript.log("ui.resetStyles", () => `Reinitialized styles to default`); ctx.log(() => `Reinitialized styles to default`);
}, },
getGameInfo: function (): GameInfo { getGameInfo: () => (): GameInfo => {
updateRam("getGameInfo");
const version = CONSTANTS.VersionString; const version = CONSTANTS.VersionString;
const commit = hash(); const commit = hash();
const platform = navigator.userAgent.toLowerCase().indexOf(" electron/") > -1 ? "Steam" : "Browser"; const platform = navigator.userAgent.toLowerCase().indexOf(" electron/") > -1 ? "Steam" : "Browser";
@ -108,5 +97,10 @@ export function NetscriptUserInterface(
return gameInfo; return gameInfo;
}, },
clearTerminal: (ctx: NetscriptContext) => (): void => {
ctx.log(() => `Clearing terminal`);
Terminal.clear();
},
}; };
} }

@ -532,7 +532,7 @@ function createAndAddWorkerScript(
const oneRamUsage = getRamUsageFromRunningScript(runningScriptObj); const oneRamUsage = getRamUsageFromRunningScript(runningScriptObj);
const ramUsage = roundToTwo(oneRamUsage * threads); const ramUsage = roundToTwo(oneRamUsage * threads);
const ramAvailable = server.maxRam - server.ramUsed; const ramAvailable = server.maxRam - server.ramUsed;
if (ramUsage > ramAvailable) { if (ramUsage > ramAvailable + 0.001) {
dialogBoxCreate( dialogBoxCreate(
`Not enough RAM to run script ${runningScriptObj.filename} with args ` + `Not enough RAM to run script ${runningScriptObj.filename} with args ` +
`${arrayToString(runningScriptObj.args)}. This likely occurred because you re-loaded ` + `${arrayToString(runningScriptObj.args)}. This likely occurred because you re-loaded ` +
@ -750,7 +750,7 @@ export function runScriptFromScript(
if (server.hasAdminRights == false) { if (server.hasAdminRights == false) {
workerScript.log(caller, () => `You do not have root access on '${server.hostname}'`); workerScript.log(caller, () => `You do not have root access on '${server.hostname}'`);
return 0; return 0;
} else if (ramUsage > ramAvailable) { } else if (ramUsage > ramAvailable + 0.001) {
workerScript.log( workerScript.log(
caller, 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 { 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 { Augmentation } from "../../../Augmentation/Augmentation";
import { StaticAugmentations } from "../../../Augmentation/StaticAugmentations";
import { AugmentationNames } from "../../../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../../../Augmentation/data/AugmentationNames";
import { StaticAugmentations } from "../../../Augmentation/StaticAugmentations";
import { CONSTANTS } from "../../../Constants"; import { CONSTANTS } from "../../../Constants";
import { hasAugmentationPrereqs } from "../../../Faction/FactionHelpers"; import { hasAugmentationPrereqs } from "../../../Faction/FactionHelpers";
import { LocationName } from "../../../Locations/data/LocationNames"; import { LocationName } from "../../../Locations/data/LocationNames";
import { Locations } from "../../../Locations/Locations"; import { Locations } from "../../../Locations/Locations";
import { PurchaseAugmentationsOrderSetting } from "../../../Settings/SettingEnums";
import { Settings } from "../../../Settings/Settings"; import { Settings } from "../../../Settings/Settings";
import { IMap } from "../../../types"; import { IMap } from "../../../types";
import { use } from "../../../ui/Context"; import { use } from "../../../ui/Context";
@ -15,8 +16,8 @@ import { ConfirmationModal } from "../../../ui/React/ConfirmationModal";
import { Money } from "../../../ui/React/Money"; import { Money } from "../../../ui/React/Money";
import { convertTimeMsToTimeElapsedString, formatNumber } from "../../../utils/StringHelperFunctions"; import { convertTimeMsToTimeElapsedString, formatNumber } from "../../../utils/StringHelperFunctions";
import { IPlayer } from "../../IPlayer"; import { IPlayer } from "../../IPlayer";
import { getGraftingAvailableAugs, calculateGraftingTimeWithBonus } from "../GraftingHelpers";
import { GraftableAugmentation } from "../GraftableAugmentation"; import { GraftableAugmentation } from "../GraftableAugmentation";
import { calculateGraftingTimeWithBonus, getGraftingAvailableAugs } from "../GraftingHelpers";
const GraftableAugmentations: IMap<GraftableAugmentation> = {}; const GraftableAugmentations: IMap<GraftableAugmentation> = {};
@ -69,6 +70,21 @@ export const GraftingRoot = (): React.ReactElement => {
setRerender((old) => !old); 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(() => { useEffect(() => {
const id = setInterval(rerender, 200); const id = setInterval(rerender, 200);
return () => clearInterval(id); return () => clearInterval(id);
@ -91,13 +107,31 @@ export const GraftingRoot = (): React.ReactElement => {
</Typography> </Typography>
<Box sx={{ my: 3 }}> <Box sx={{ my: 3 }}>
<Typography variant="h5">Graft Augmentations</Typography> <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 ? ( {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}` }}> <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}> <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> </ListItemButton>
))} ))}
</List> </List>

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

@ -4,7 +4,14 @@ import { CONSTANTS } from "./Constants";
import { Factions, loadFactions } from "./Faction/Factions"; import { Factions, loadFactions } from "./Faction/Factions";
import { loadAllGangs, AllGangs } from "./Gang/AllGangs"; import { loadAllGangs, AllGangs } from "./Gang/AllGangs";
import { Player, loadPlayer } from "./Player"; 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 { Settings } from "./Settings/Settings";
import { loadStockMarket, StockMarket } from "./StockMarket/StockMarket"; import { loadStockMarket, StockMarket } from "./StockMarket/StockMarket";
import { staneksGift, loadStaneksGift } from "./CotMG/Helper"; import { staneksGift, loadStaneksGift } from "./CotMG/Helper";
@ -26,6 +33,8 @@ import { pushGameSaved } from "./Electron";
import { defaultMonacoTheme } from "./ScriptEditor/ui/themes"; import { defaultMonacoTheme } from "./ScriptEditor/ui/themes";
import { FactionNames } from "./Faction/data/FactionNames"; import { FactionNames } from "./Faction/data/FactionNames";
import { Faction } from "./Faction/Faction"; import { Faction } from "./Faction/Faction";
import { safetlyCreateUniqueServer } from "./Server/ServerHelpers";
import { SpecialServers } from "./Server/data/SpecialServers";
/* SaveObject.js /* SaveObject.js
* Defines the object used to save/load games * Defines the object used to save/load games
@ -434,6 +443,22 @@ function evaluateVersionCompatibility(ver: string | number): void {
Player.reapplyAllAugmentations(true); Player.reapplyAllAugmentations(true);
Player.reapplyAllSourceFiles(); 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. */ /** Influences how much money the player earns when completing working their job. */
CompanyWorkMoney: number; CompanyWorkMoney: number;
/** Influences the money gain from dividends of corporations created by the player. */ /** 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. */ /** Influences the valuation of corporations created by the player. */
CorporationValuation: number; CorporationValuation: number;
/** Influences the base experience gained for each ability when the player commits a crime. */ /** Influences the base experience gained for each ability when the player commits a crime. */
@ -4405,6 +4405,13 @@ interface UserInterface {
* RAM cost: 0 GB * RAM cost: 0 GB
*/ */
getGameInfo(): GameInfo; 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; 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. * Get the list of servers connected to a server.
* @remarks * @remarks

@ -1565,4 +1565,12 @@ export const serverMetadata: IServerMetadata[] = [
serverGrowth: 0, serverGrowth: 0,
specialName: SpecialServers.WorldDaemon, 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; EnableBashHotkeys: boolean;
/**
* Infinite loop safety net
*/
InfinityLoopSafety: boolean;
/** /**
* Timestamps format * Timestamps format
*/ */
@ -201,6 +206,7 @@ export const defaultSettings: IDefaultSettings = {
DisableOverviewProgressBars: false, DisableOverviewProgressBars: false,
EnableBashHotkeys: false, EnableBashHotkeys: false,
TimestampsFormat: "", TimestampsFormat: "",
InfinityLoopSafety: true,
Locale: "en", Locale: "en",
MaxRecentScriptsCapacity: 50, MaxRecentScriptsCapacity: 50,
MaxLogCapacity: 50, MaxLogCapacity: 50,
@ -241,6 +247,7 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
DisableOverviewProgressBars: defaultSettings.DisableOverviewProgressBars, DisableOverviewProgressBars: defaultSettings.DisableOverviewProgressBars,
EnableBashHotkeys: defaultSettings.EnableBashHotkeys, EnableBashHotkeys: defaultSettings.EnableBashHotkeys,
TimestampsFormat: defaultSettings.TimestampsFormat, TimestampsFormat: defaultSettings.TimestampsFormat,
InfinityLoopSafety: defaultSettings.InfinityLoopSafety,
Locale: "en", Locale: "en",
MaxRecentScriptsCapacity: defaultSettings.MaxRecentScriptsCapacity, MaxRecentScriptsCapacity: defaultSettings.MaxRecentScriptsCapacity,
MaxLogCapacity: defaultSettings.MaxLogCapacity, 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 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 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 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 Source-File will unlock:
will raise all of your hacking-related multipliers by: <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 />
<br /> <br />
Level 1: 8% Level 1: 8%

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

@ -463,6 +463,12 @@ export class Terminal implements ITerminal {
this.contractOpen = true; this.contractOpen = true;
const res = await contract.prompt(); 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) { switch (res) {
case CodingContractResult.Success: case CodingContractResult.Success:
if (contract.reward !== null) { if (contract.reward !== null) {

@ -66,6 +66,13 @@ export function mv(
if (destFile != null) { if (destFile != null) {
// Already exists, will be overwritten, so we'll delete it // 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); const status = server.removeFile(destPath);
if (!status.res) { if (!status.res) {
terminal.error(`Something went wrong...please contact game dev (probably a bug)`); terminal.error(`Something went wrong...please contact game dev (probably a bug)`);

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

@ -1382,7 +1382,8 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return [n + m, edges]; return [n + m, edges];
}, },
solver: (data: [number, [number, number][]], ans: string): boolean => { 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 == "[]") { if (ans == "[]") {
//Helper function to get neighbourhood of a vertex //Helper function to get neighbourhood of a vertex
function neighbourhood(vertex: number): number[] { function neighbourhood(vertex: number): number[] {

@ -1,8 +1,9 @@
import { Paper, Table, TableBody, Box, IconButton, Typography, Container, Tooltip } from "@mui/material"; import { Paper, Table, TableBody, Box, IconButton, Typography, Container, Tooltip } from "@mui/material";
import { MoreHoriz, Info } from "@mui/icons-material"; import { MoreHoriz, Info } from "@mui/icons-material";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { BitNodes } from "../BitNode/BitNode"; import { BitNodes, defaultMultipliers, getBitNodeMultipliers } from "../BitNode/BitNode";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { BitNodeMultipliersDisplay } from "../BitNode/ui/BitnodeMultipliersDescription";
import { HacknetServerConstants } from "../Hacknet/data/Constants"; import { HacknetServerConstants } from "../Hacknet/data/Constants";
import { getPurchaseServerLimit } from "../Server/ServerPurchases"; import { getPurchaseServerLimit } from "../Server/ServerPurchases";
import { Settings } from "../Settings/Settings"; import { Settings } from "../Settings/Settings";
@ -14,6 +15,7 @@ import { Modal } from "./React/Modal";
import { Money } from "./React/Money"; import { Money } from "./React/Money";
import { StatsRow } from "./React/StatsRow"; import { StatsRow } from "./React/StatsRow";
import { StatsTable } from "./React/StatsTable"; import { StatsTable } from "./React/StatsTable";
import { isEqual } from "lodash";
interface EmployersModalProps { interface EmployersModalProps {
open: boolean; 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 { interface MultTableProps {
rows: (string | number)[][]; rows: IMultRow[];
color: string; color: string;
noMargin?: boolean; 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 }}> <Table sx={{ display: "table", width: "100%", mb: (props.noMargin ?? false) === true ? 0 : 2 }}>
<TableBody> <TableBody>
{props.rows.map((data) => { {props.rows.map((data) => {
const mult = data[0] as string, const { mult, value, effValue = null, color = props.color } = data;
value = data[1] as number,
modded = data[2] as number | null;
if (modded && modded !== value && player.sourceFileLvl(5) > 0) { if (effValue !== null && effValue !== value && player.sourceFileLvl(5) > 0) {
return ( 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>{" "} <span style={{ opacity: 0.5 }}>{numeralWrapper.formatPercentage(value)}</span>{" "}
{numeralWrapper.formatPercentage(modded)} {numeralWrapper.formatPercentage(effValue)}
</Typography> </Typography>
</> </>
</StatsRow> </StatsRow>
); );
} }
return ( return (
<StatsRow <StatsRow key={mult} name={mult} color={color} data={{ content: numeralWrapper.formatPercentage(value) }} />
key={mult}
name={mult}
color={props.color}
data={{ content: numeralWrapper.formatPercentage(value) }}
/>
); );
})} })}
</TableBody> </TableBody>
@ -82,16 +91,14 @@ function CurrentBitNode(): React.ReactElement {
const player = use.Player(); const player = use.Player();
if (player.sourceFiles.length > 0) { if (player.sourceFiles.length > 0) {
const index = "BitNode" + player.bitNodeN; 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 ( return (
<Box> <Paper sx={{ mb: 1, p: 1 }}>
<Paper sx={{ p: 1 }}> <Typography variant="h5">
<Typography variant="h5"> BitNode {player.bitNodeN}: {BitNodes[index].name} (Level {lvl})
BitNode {player.bitNodeN}: {BitNodes[index].name} (Level {lvl}) </Typography>
</Typography> <Typography sx={{ whiteSpace: "pre-wrap", overflowWrap: "break-word" }}>{BitNodes[index].info}</Typography>
<Typography sx={{ whiteSpace: "pre-wrap", overflowWrap: "break-word" }}>{BitNodes[index].info}</Typography> </Paper>
</Paper>
</Box>
); );
} }
@ -218,6 +225,14 @@ export function CharacterStats(): React.ReactElement {
} }
timeRows.push(["Total", convertTimeMsToTimeElapsedString(player.totalPlaytime)]); 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 ( return (
<Container maxWidth="lg" disableGutters sx={{ mx: 0 }}> <Container maxWidth="lg" disableGutters sx={{ mx: 0 }}>
<Typography variant="h4">Stats</Typography> <Typography variant="h4">Stats</Typography>
@ -332,186 +347,255 @@ export function CharacterStats(): React.ReactElement {
</Table> </Table>
</Paper> </Paper>
</Box> </Box>
<Box sx={{ mb: 1 }}>
<Paper sx={{ p: 1 }}>
<Typography variant="h5" color="primary" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
Multipliers
{player.sourceFileLvl(5) > 0 && (
<Tooltip
title={
<Typography>
Displays your current multipliers.
<br />
<br />
When there is a dim number next to a multiplier, that means that the multiplier in question is being
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.
</Typography>
}
>
<Info sx={{ ml: 1, mb: 0.5 }} color="info" />
</Tooltip>
)}
</Typography>
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 1 }}>
<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,
],
]}
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,
],
]}
color={Settings.theme.hack}
/>
<MultiplierTable
rows={[
[
"Strength Level",
player.strength_mult,
player.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier,
],
["Strength Experience", 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],
]}
color={Settings.theme.combat}
/>
<MultiplierTable
rows={[
[
"Dexterity Level",
player.dexterity_mult,
player.dexterity_mult * BitNodeMultipliers.DexterityLevelMultiplier,
],
["Dexterity Experience", 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],
]}
color={Settings.theme.combat}
/>
<MultiplierTable
rows={[
[
"Charisma Level",
player.charisma_mult,
player.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier,
],
["Charisma Experience", player.charisma_exp_mult],
]}
color={Settings.theme.cha}
noMargin
/>
</Box>
<Box> <Paper sx={{ p: 1, mb: 1 }}>
<Typography variant="h5" color="primary" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
Multipliers
{player.sourceFileLvl(5) > 0 && (
<Tooltip
title={
<Typography>
Displays your current multipliers.
<br />
<br />
When there is a dim number next to a multiplier, that means that the multiplier in question is being
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.
</Typography>
}
>
<Info sx={{ ml: 1, mb: 0.5 }} color="info" />
</Tooltip>
)}
</Typography>
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 1 }}>
<Box>
<MultiplierTable
rows={[
{
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={[
{
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={[
{
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={[
{
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={[
{
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={[
{
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={[
{
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
/>
</Box>
<Box>
<MultiplierTable
rows={[
{
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={[
{
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={[
{
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 <MultiplierTable
rows={[ rows={[
[ {
"Hacknet Node production", mult: "Bladeburner Success Chance",
player.hacknet_node_money_mult, value: player.bladeburner_success_chance_mult,
player.hacknet_node_money_mult * BitNodeMultipliers.HacknetNodeMoney, },
], {
["Hacknet Node purchase cost", player.hacknet_node_purchase_cost_mult], mult: "Bladeburner Max Stamina",
["Hacknet Node RAM upgrade cost", player.hacknet_node_ram_cost_mult], value: player.bladeburner_max_stamina_mult,
["Hacknet Node Core purchase cost", player.hacknet_node_core_cost_mult], },
["Hacknet Node level upgrade cost", player.hacknet_node_level_cost_mult], {
mult: "Bladeburner Stamina Gain",
value: player.bladeburner_stamina_gain_mult,
},
{
mult: "Bladeburner Field Analysis",
value: player.bladeburner_analysis_mult,
},
]} ]}
color={Settings.theme.primary} color={Settings.theme.primary}
noMargin
/> />
<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],
]}
color={Settings.theme.money}
/>
<MultiplierTable
rows={[
["Crime success", player.crime_success_mult],
["Crime money", player.crime_money_mult, player.crime_money_mult * BitNodeMultipliers.CrimeMoney],
]}
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],
]}
color={Settings.theme.primary}
noMargin
/>
)}
</Box>
</Box> </Box>
</Paper> </Box>
</Box> </Paper>
<Paper sx={{ p: 1, mb: 1 }}>
<Typography variant="h5">Time Played</Typography>
<Table>
<TableBody>
{timeRows.map(([name, content]) => (
<StatsRow key={name} name={name} color={Settings.theme.primary} data={{ content: content }} />
))}
</TableBody>
</Table>
</Paper>
<Box sx={{ mb: 1 }}>
<Paper sx={{ p: 1 }}>
<Typography variant="h5">Time Played</Typography>
<Table>
<TableBody>
{timeRows.map(([name, content]) => (
<StatsRow key={name} name={name} color={Settings.theme.primary} data={{ content: content }} />
))}
</TableBody>
</Table>
</Paper>
</Box>
<CurrentBitNode /> <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)} /> <MoneyModal open={moneyOpen} onClose={() => setMoneyOpen(false)} />
<EmployersModal open={employersOpen} onClose={() => setEmployersOpen(false)} /> <EmployersModal open={employersOpen} onClose={() => setEmployersOpen(false)} />
</Container> </Container>

@ -35,7 +35,7 @@ export function NSSelection(props: IProps): React.ReactElement {
} }
return ( 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}> <Tabs variant="fullWidth" value={value} onChange={handleChange}>
<Tab label="NS1" /> <Tab label="NS1" />
<Tab label="NS2" /> <Tab label="NS2" />

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

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

@ -1,5 +1,3 @@
import { jest, describe, expect } from "@jest/globals";
import { Player } from "../../../src/Player"; import { Player } from "../../../src/Player";
import { getRamCost, RamCostConstants } from "../../../src/Netscript/RamCostGenerator"; import { getRamCost, RamCostConstants } from "../../../src/Netscript/RamCostGenerator";
import { calculateRamUsage } from "../../../src/Script/RamCalculations"; 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 // Player is needed for calculating costs like Singularity functions, that depend on acquired source files
import { Player } from "../../../src/Player"; 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 { Script } from "../../../src/Script/Script";
import { Player } from "../../../src/Player"; 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 { CONSTANTS } from "../../src/Constants";
import { Player } from "../../src/Player"; import { Player } from "../../src/Player";
import { IMap } from "../../src/types"; 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"; import { convertTimeMsToTimeElapsedString } from "../../src/utils/StringHelperFunctions";
describe("StringHelperFunctions Tests", function () { 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"; import * as dirHelpers from "../../../src/Terminal/DirectoryHelpers";
describe("Terminal Directory Tests", function () { describe("Terminal Directory Tests", function () {
@ -102,17 +100,28 @@ describe("Terminal Directory Tests", function () {
expect(isValidDirectoryName(".a1")).toEqual(true); expect(isValidDirectoryName(".a1")).toEqual(true);
expect(isValidDirectoryName("._foo")).toEqual(true); expect(isValidDirectoryName("._foo")).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 () { it("should return false for invalid directory names", function () {
expect(isValidDirectoryName("")).toEqual(false); expect(isValidDirectoryName("")).toEqual(false);
expect(isValidDirectoryName("foo.dir")).toEqual(false); expect(isValidDirectoryName("👨‍💻")).toEqual(false);
expect(isValidDirectoryName("1.")).toEqual(false);
expect(isValidDirectoryName("foo.")).toEqual(false);
expect(isValidDirectoryName("dir#")).toEqual(false); expect(isValidDirectoryName("dir#")).toEqual(false);
expect(isValidDirectoryName("dir!")).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("..")).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"; import { CityName } from "./../../../src/Locations/data/CityNames";
/* eslint-disable no-await-in-loop */ /* 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 { Player } from "../../../src/Player";
import { determineAllPossibilitiesForTabCompletion } from "../../../src/Terminal/determineAllPossibilitiesForTabCompletion"; import { determineAllPossibilitiesForTabCompletion } from "../../../src/Terminal/determineAllPossibilitiesForTabCompletion";

@ -1,4 +1,3 @@
import { describe, expect, test } from "@jest/globals";
import { numeralWrapper } from "../../../src/ui/numeralFormat"; import { numeralWrapper } from "../../../src/ui/numeralFormat";
let decimalFormat = "0.[000000]"; 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, "esModuleInterop": true,
"isolatedModules": true, "isolatedModules": true,
"jsx": "react", "jsx": "react",
"lib": ["es2016", "dom", "es2017.object", "es2019"],
"module": "commonjs", "module": "commonjs",
"target": "es6", "noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true, "sourceMap": true,
"strict": true, "strict": true,
"resolveJsonModule": true, "target": "es2019"
"types": ["cypress", "@testing-library/cypress", "node"]
}, },
"exclude": ["node_modules"] "include": ["src/**/*"]
} }