* Make command `cd` without arguments an alias for `cd /` (#853)

In most shells `cd` without arguments takes you to the home directory
of the current user. I keep trying to do this due to muscle memory
from working in terminals, so I figured I'd make it do something useful.

There is no home directory in the game, but going to / is the closest
thing we have, since that is the starting point for the user in the
game.

* Add new `backdoor` terminal command (#852)

* Add the backdoor command to the terminal

This command will perform a manual hack without rewarding money. It will be used for the story, mainly for faction hacking tests

* Add tab completion for backdoor command

* Add help text for backdoor command

* Change condition syntax to be more consistent with others

* Extract reused code block so it is always called after actions

* Update documentation for new backdoor command

Modified references to manual hack as it isn't for factions anymore

* Remove extra parenthesis

* Rename manuallyHacked to backdoorInstalled

* Fix typo

* Change faction test messages to use backdoor instad of hack

* Rename more instances of manuallyHacked

* fixed typo in helptext of darkweb buy (#858)

* Fix typos and unify descriptions of augmentations (#859)

Made an attempt to...
- give all "+rep% company/faction" the same text
- make all augmentations with a single effect use a single line to describe the effect
- make all effects end with a period

* Made Cashroot starter kit display its tooltip with the money formatted properly and in gold

* fix typo in docs (#860)

* Initial code for Casino Card Deck implementation

* Casino Blackjack Implementation

* Update some tools (eslint, typescript)

* Blackjack code cleanup

* Update README_contribution

* Update ScriptHelpers.js (#861)

expand error message

* More augmentation typo fixes (#862)

* Add Netscript function getCurrentScript (#856)

Add netscript function that returns the current script.

* Added milestones menu to guide new players. (#865)

Milestone menu

* fix typos in milestones (#866)

Co-authored-by: sschmidTU <s.schmid@phonicscore.com>

* Corrupt location title when backdoor is installed (#864)

* Add corruptableText component

* Corrupt location title if backdoor is installed

* Formatting

* Add helper to check value of backdoorInstalled

Helper could be oneline but it would make it less readable

* Fix some formatting

* Add settings option to disable text effects

* Import useState

* getRunningScript (#867)

* Replaced getCurrentScript with getRunningScript

* Bunch of smaller fixes (#904)

Fix #884
Fix #879
Fix #878
Fix #876
Fix #874
Fix #873
Fix #887
Fix #891
Fix #895

* rework the early servers to be more noob friendly (#903)

* v0.51.6

Co-authored-by: Andreas Eriksson <2691182+AndreasTPC@users.noreply.github.com>
Co-authored-by: Jack <jackdewinter1@gmail.com>
Co-authored-by: Teun Pronk <5228255+Crownie88@users.noreply.github.com>
Co-authored-by: Pimvgd <Pimvgd@gmail.com>
Co-authored-by: Daniel Xie <daniel.xie@flockfreight.com>
Co-authored-by: Simon <33069673+sschmidTU@users.noreply.github.com>
Co-authored-by: sschmidTU <s.schmid@phonicscore.com>
This commit is contained in:
hydroflame 2021-04-28 20:07:26 -04:00 committed by GitHub
parent b2aafea656
commit 52a80ad236
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
82 changed files with 10182 additions and 6069 deletions

@ -2,91 +2,104 @@ module.exports = {
"env": { "env": {
"browser": true, "browser": true,
"commonjs": true, "commonjs": true,
"es6": false "es6": false,
}, },
"extends": "eslint:recommended", "extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
],
"parser": "@typescript-eslint/parser",
"parserOptions": { "parserOptions": {
"ecmaVersion": 8, "ecmaVersion": 8,
"sourceType": "module", "sourceType": "module",
"ecmaFeatures": { "ecmaFeatures": {
"experimentalObjectRestSpread": true "experimentalObjectRestSpread": true,
} },
}, },
"plugins": [
'@typescript-eslint',
],
"rules": { "rules": {
"accessor-pairs": [ "accessor-pairs": [
"error", "error",
{ {
"setWithoutGet": true, "setWithoutGet": true,
"getWithoutSet": false "getWithoutSet": false,
} },
], ],
"array-bracket-newline": [ "array-bracket-newline": [
"off" "off",
], ],
"array-bracket-spacing": [ "array-bracket-spacing": [
"off" "off",
], ],
"array-callback-return": [ "array-callback-return": [
"off" "off",
], ],
"array-element-newline": [ "array-element-newline": [
"off" "off",
], ],
"arrow-body-style": [ "arrow-body-style": [
"off" "off",
], ],
"arrow-parens": [ "arrow-parens": [
"off" "off",
], ],
"arrow-spacing": [ "arrow-spacing": [
"off" "off",
], ],
"block-scoped-var": [ "block-scoped-var": [
"off" "off",
], ],
"block-spacing": [ "block-spacing": [
"off" "off",
], ],
"brace-style": [ "brace-style": [
"off" "off",
], ],
"callback-return": [ "callback-return": [
"error" "error",
], ],
"camelcase": [ "camelcase": [
"off" "off",
], ],
"capitalized-comments": [ "capitalized-comments": [
"off" "off",
], ],
"class-methods-use-this": [ "class-methods-use-this": [
"error" "off",
], ],
"comma-dangle": [ "comma-dangle": [
"off" "error", {
"arrays": "always-multiline",
"objects": "always-multiline",
"imports": "always-multiline",
"exports": "always-multiline",
"functions": "always-multiline",
}
], ],
"comma-spacing": [ "comma-spacing": [
"off" "off",
], ],
"comma-style": [ "comma-style": [
"error", "error",
"last" "last",
], ],
"complexity": [ "complexity": [
"off" "off",
], ],
"computed-property-spacing": [ "computed-property-spacing": [
"off", "off",
"never" "never",
], ],
"consistent-return": [ "consistent-return": [
"off" "off",
], ],
"consistent-this": [ "consistent-this": [
"off" "off",
], ],
"constructor-super": [ "constructor-super": [
"error" "error",
], ],
"curly": [ "curly": [
"off" "off"
@ -99,203 +112,202 @@ module.exports = {
"property" "property"
], ],
"dot-notation": [ "dot-notation": [
"off" "off",
], ],
"eol-last": [ "eol-last": [
"off" "off",
], ],
"eqeqeq": [ "eqeqeq": [
"off" "off",
], ],
"for-direction": [ "for-direction": [
"error" "error",
], ],
"func-call-spacing": [ "func-call-spacing": [
"off" "off",
], ],
"func-name-matching": [ "func-name-matching": [
"error" "error",
], ],
"func-names": [ "func-names": [
"off", "off",
"never" "never",
], ],
"func-style": [ "func-style": [
"off" "off",
], ],
"function-paren-newline": [ "function-paren-newline": [
"off" "off",
], ],
"generator-star-spacing": [ "generator-star-spacing": [
"error", "error",
"before" "before",
], ],
"getter-return": [ "getter-return": [
"error", "error",
{ {
"allowImplicit": false "allowImplicit": false,
} },
], ],
"global-require": [ "global-require": [
"off" "off",
], ],
"guard-for-in": [ "guard-for-in": [
"off" "off",
], ],
"handle-callback-err": [ "handle-callback-err": [
"error" "error",
], ],
"id-blacklist": [ "id-blacklist": [
"error" "error",
], ],
"id-length": [ "id-length": [
"off" "off",
], ],
"id-match": [ "id-match": [
"error" "error",
], ],
"implicit-arrow-linebreak": [ "implicit-arrow-linebreak": [
"error", "error",
"beside" "beside",
], ],
"indent": [ "indent": [
"off" "off",
], ],
"indent-legacy": [ "indent-legacy": [
"off" "off",
], ],
"init-declarations": [ "init-declarations": [
"off" "off",
], ],
"jsx-quotes": [ "jsx-quotes": [
"error" "error",
], ],
"key-spacing": [ "key-spacing": [
"off" "off",
], ],
"keyword-spacing": [ "keyword-spacing": [
"off" "off",
], ],
"line-comment-position": [ "line-comment-position": [
"off" "off",
], ],
"linebreak-style": [ "linebreak-style": [
"error", "off", // Line endings automatically converted to LF on git commit so probably shouldn't care about it here
"windows"
], ],
"lines-around-comment": [ "lines-around-comment": [
"off" "off",
], ],
"lines-around-directive": [ "lines-around-directive": [
"error" "error",
], ],
"lines-between-class-members": [ "lines-between-class-members": [
"error" "error",
], ],
"max-depth": [ "max-depth": [
"off" "off",
], ],
"max-len": [ "max-len": [
"off" "off",
], ],
"max-lines": [ "max-lines": [
"off" "off",
], ],
"max-nested-callbacks": [ "max-nested-callbacks": [
"error" "error",
], ],
"max-params": [ "max-params": [
"off" "off",
], ],
"max-statements": [ "max-statements": [
"off" "off",
], ],
"max-statements-per-line": [ "max-statements-per-line": [
"off" "off",
], ],
"multiline-comment-style": [ "multiline-comment-style": [
"off", "off",
"starred-block" "starred-block",
], ],
"multiline-ternary": [ "multiline-ternary": [
"off", "off",
"never" "never",
], ],
"new-cap": [ "new-cap": [
"off" "off",
], ],
"new-parens": [ "new-parens": [
"off" "off",
], ],
"newline-after-var": [ "newline-after-var": [
"off" "off",
], ],
"newline-before-return": [ "newline-before-return": [
"off" "off",
], ],
"newline-per-chained-call": [ "newline-per-chained-call": [
"off" "off",
], ],
"no-alert": [ "no-alert": [
"error" "error",
], ],
"no-array-constructor": [ "no-array-constructor": [
"error" "error",
], ],
"no-await-in-loop": [ "no-await-in-loop": [
"error" "error",
], ],
"no-bitwise": [ "no-bitwise": [
"off" "off",
], ],
"no-buffer-constructor": [ "no-buffer-constructor": [
"error" "error",
], ],
"no-caller": [ "no-caller": [
"error" "error",
], ],
"no-case-declarations": [ "no-case-declarations": [
"error" "error",
], ],
"no-catch-shadow": [ "no-catch-shadow": [
"error" "error",
], ],
"no-class-assign": [ "no-class-assign": [
"error" "error",
], ],
"no-compare-neg-zero": [ "no-compare-neg-zero": [
"error" "error",
], ],
"no-cond-assign": [ "no-cond-assign": [
"off", "off",
"except-parens" "except-parens",
], ],
"no-confusing-arrow": [ "no-confusing-arrow": [
"error" "error",
], ],
"no-console": [ "no-console": [
"off" "off",
], ],
"no-const-assign": [ "no-const-assign": [
"error" "error",
], ],
"no-constant-condition": [ "no-constant-condition": [
"error", "error",
{ {
"checkLoops": false "checkLoops": false,
} },
], ],
"no-continue": [ "no-continue": [
"off" "off",
], ],
"no-control-regex": [ "no-control-regex": [
"error" "error",
], ],
"no-debugger": [ "no-debugger": [
"error" "error",
], ],
"no-delete-var": [ "no-delete-var": [
"error" "error",
], ],
"no-div-regex": [ "no-div-regex": [
"error" "error"
@ -346,11 +358,7 @@ module.exports = {
"error" "error"
], ],
"no-extra-parens": [ "no-extra-parens": [
"error", "off"
"all",
{
"conditionalAssign": false
}
], ],
"no-extra-semi": [ "no-extra-semi": [
"off" "off"
@ -367,9 +375,6 @@ module.exports = {
"no-extra-label": [ "no-extra-label": [
"error" "error"
], ],
"no-extra-parens": [
"off"
],
"no-fallthrough": [ "no-fallthrough": [
"off" "off"
], ],
@ -853,5 +858,53 @@ module.exports = {
"error", "error",
"never" "never"
] ]
} },
"overrides": [
{
// TypeScript configuration
"files": [ "**/*.ts", "**/*.tsx" ],
"parser": "@typescript-eslint/parser",
"plugins": [ "@typescript-eslint" ],
"extends": [
"plugin:@typescript-eslint/recommended",
],
"rules": {
"lines-between-class-members": "off",
"no-empty-pattern": "off",
"no-useless-constructor": [
"off", // Valid for typescript due to property ctor shorthand
],
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/camelcase": "off",
"@typescript-eslint/explicit-function-return-type": ["error", {
"allowExpressions": true,
}],
"@typescript-eslint/member-delimiter-style": ["error", {
"multiline": {
"delimiter": "semi",
"requireLast": true,
},
"singleline": {
"delimiter": "semi",
"requireLast": false,
}
}],
"@typescript-eslint/member-ordering": ["error", {
"default": [
"signature",
"static-field",
"instance-field",
"abstract-field",
"constructor",
"instance-method",
"abstract-method",
"static-method",
]
}],
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-use-before-define": "off",
}
}
]
}; };

@ -7,4 +7,17 @@ Update the following
- `doc/source/conf.py` `version` and `release` - `doc/source/conf.py` `version` and `release`
- `doc/source/changelog.rst` - `doc/source/changelog.rst`
- post to discord - post to discord
- post to reddit.com/r/Bitburner - post to reddit.com/r/Bitburner
Deploying `dev` to the Beta Branch
----------------------------------
TODO
Development Workflow Best Practices
-----------------------------------
- Work in a new branch forked from the `dev` branch to isolate your new code
- Keep code-changes on a branch as small as possible. This makes it easier for code review. Each branch should be its own independent feature.
- Regularly rebase your branch against `dev` to make sure you have the latest updates pulled.
- When merging, always merge your branch into `dev`. When releasing a new update, then merge `dev` into `master`

24
css/casino.scss Normal file

@ -0,0 +1,24 @@
.casino-card {
padding: 10px;
border: solid 1px #808080;
background-color: white;
display: inline-block;
border-radius: 10px;
font-size: 14pt;
text-align: center;
margin: 3px;
font-weight: bold;
}
.casino-card .value {
font-size:15pt;
font-family: sans-serif;
}
.casino-card.red {
color: red;
}
.casino-card.black {
color: black;
}

5
css/milestones.scss Normal file

@ -0,0 +1,5 @@
#milestones-container {
position: fixed;
padding: 6px;
width: 60%;
}

File diff suppressed because one or more lines are too long

@ -1,2 +1,2 @@
!function(n){function t(t){for(var e,i,f=t[0],c=t[1],l=t[2],p=0,s=[];p<f.length;p++)i=f[p],u[i]&&s.push(u[i][0]),u[i]=0;for(e in c)Object.prototype.hasOwnProperty.call(c,e)&&(n[e]=c[e]);for(a&&a(t);s.length;)s.shift()();return r.push.apply(r,l||[]),o()}function o(){for(var n,t=0;t<r.length;t++){for(var o=r[t],e=!0,f=1;f<o.length;f++){var c=o[f];0!==u[c]&&(e=!1)}e&&(r.splice(t--,1),n=i(i.s=o[0]))}return n}var e={},u={1:0},r=[];function i(t){if(e[t])return e[t].exports;var o=e[t]={i:t,l:!1,exports:{}};return n[t].call(o.exports,o,o.exports,i),o.l=!0,o.exports}i.m=n,i.c=e,i.d=function(n,t,o){i.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:o})},i.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},i.t=function(n,t){if(1&t&&(n=i(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var o=Object.create(null);if(i.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var e in n)i.d(o,e,function(t){return n[t]}.bind(null,e));return o},i.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return i.d(t,"a",t),t},i.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},i.p="";var f=window.webpackJsonp=window.webpackJsonp||[],c=f.push.bind(f);f.push=t,f=f.slice();for(var l=0;l<f.length;l++)t(f[l]);var a=c;r.push([402,0]),o()}({345:function(n,t,o){},347:function(n,t,o){},349:function(n,t,o){},351:function(n,t,o){},353:function(n,t,o){},355:function(n,t,o){},357:function(n,t,o){},359:function(n,t,o){},361:function(n,t,o){},363:function(n,t,o){},365:function(n,t,o){},367:function(n,t,o){},369:function(n,t,o){},371:function(n,t,o){},373:function(n,t,o){},375:function(n,t,o){},377:function(n,t,o){},379:function(n,t,o){},381:function(n,t,o){},383:function(n,t,o){},385:function(n,t,o){},387:function(n,t,o){},389:function(n,t,o){},391:function(n,t,o){},393:function(n,t,o){},395:function(n,t,o){},397:function(n,t,o){},399:function(n,t,o){},402:function(n,t,o){"use strict";o.r(t);o(401),o(399),o(397),o(395),o(393),o(391),o(389),o(387),o(385),o(383),o(381),o(379),o(377),o(375),o(373),o(371),o(369),o(367),o(365),o(363),o(361),o(359),o(357),o(355),o(353),o(351),o(349),o(347),o(345)}}); !function(n){function t(t){for(var e,i,f=t[0],c=t[1],l=t[2],p=0,s=[];p<f.length;p++)i=f[p],u[i]&&s.push(u[i][0]),u[i]=0;for(e in c)Object.prototype.hasOwnProperty.call(c,e)&&(n[e]=c[e]);for(a&&a(t);s.length;)s.shift()();return r.push.apply(r,l||[]),o()}function o(){for(var n,t=0;t<r.length;t++){for(var o=r[t],e=!0,f=1;f<o.length;f++){var c=o[f];0!==u[c]&&(e=!1)}e&&(r.splice(t--,1),n=i(i.s=o[0]))}return n}var e={},u={1:0},r=[];function i(t){if(e[t])return e[t].exports;var o=e[t]={i:t,l:!1,exports:{}};return n[t].call(o.exports,o,o.exports,i),o.l=!0,o.exports}i.m=n,i.c=e,i.d=function(n,t,o){i.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:o})},i.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},i.t=function(n,t){if(1&t&&(n=i(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var o=Object.create(null);if(i.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var e in n)i.d(o,e,function(t){return n[t]}.bind(null,e));return o},i.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return i.d(t,"a",t),t},i.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},i.p="";var f=window.webpackJsonp=window.webpackJsonp||[],c=f.push.bind(f);f.push=t,f=f.slice();for(var l=0;l<f.length;l++)t(f[l]);var a=c;r.push([842,0]),o()}({781:function(n,t,o){},783:function(n,t,o){},785:function(n,t,o){},787:function(n,t,o){},789:function(n,t,o){},791:function(n,t,o){},793:function(n,t,o){},795:function(n,t,o){},797:function(n,t,o){},799:function(n,t,o){},801:function(n,t,o){},803:function(n,t,o){},805:function(n,t,o){},807:function(n,t,o){},809:function(n,t,o){},811:function(n,t,o){},813:function(n,t,o){},815:function(n,t,o){},817:function(n,t,o){},819:function(n,t,o){},821:function(n,t,o){},823:function(n,t,o){},825:function(n,t,o){},827:function(n,t,o){},829:function(n,t,o){},831:function(n,t,o){},833:function(n,t,o){},835:function(n,t,o){},837:function(n,t,o){},839:function(n,t,o){},842:function(n,t,o){"use strict";o.r(t);o(841),o(839),o(837),o(835),o(833),o(831),o(829),o(827),o(825),o(823),o(821),o(819),o(817),o(815),o(813),o(811),o(809),o(807),o(805),o(803),o(801),o(799),o(797),o(795),o(793),o(791),o(789),o(787),o(785),o(783),o(781)}});
//# sourceMappingURL=engineStyle.bundle.js.map //# sourceMappingURL=engineStyle.bundle.js.map

26
dist/engineStyle.css vendored

@ -5007,5 +5007,31 @@ html {
margin-left: 0px; margin-left: 0px;
margin-right: 0px; } margin-right: 0px; }
.casino-card {
padding: 10px;
border: solid 1px #808080;
background-color: white;
display: inline-block;
border-radius: 10px;
font-size: 14pt;
text-align: center;
margin: 3px;
font-weight: bold; }
.casino-card .value {
font-size: 15pt;
font-family: sans-serif; }
.casino-card.red {
color: red; }
.casino-card.black {
color: black; }
#milestones-container {
position: fixed;
padding: 6px;
width: 60%; }
/*# sourceMappingURL=engineStyle.css.map*/ /*# sourceMappingURL=engineStyle.css.map*/

78
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

@ -35,7 +35,7 @@ List of Factions and their Requirements
| Early Game | Faction Name | Requirements | Joining this Faction prevents | | Early Game | Faction Name | Requirements | Joining this Faction prevents |
| Factions | | | you from joining: | | Factions | | | you from joining: |
+ +----------------+-----------------------------------------+-------------------------------+ + +----------------+-----------------------------------------+-------------------------------+
| | CyberSec | * Hack CSEC Manually | | | | CyberSec | * Install a backdoor on the CSEC server | |
+ +----------------+-----------------------------------------+-------------------------------+ + +----------------+-----------------------------------------+-------------------------------+
| | Tian Di Hui | * $1m | | | | Tian Di Hui | * $1m | |
| | | * Hacking Level 50 | | | | | * Hacking Level 50 | |
@ -74,11 +74,17 @@ List of Factions and their Requirements
| | | | * New Tokyo | | | | | * New Tokyo |
| | | | * Ishima | | | | | * Ishima |
+---------------------+----------------+-----------------------------------------+-------------------------------+ +---------------------+----------------+-----------------------------------------+-------------------------------+
| Hacking | NiteSec | * Hack avmnite-02h manually | | | Hacking | NiteSec | * Install a backdoor on the avmnite-02h | |
| Groups | | server | |
| | | * Home Computer RAM of at least 32GB | |
+ +----------------+-----------------------------------------+-------------------------------+ + +----------------+-----------------------------------------+-------------------------------+
| | The Black Hand | * Hack I.I.I.I manually | | | | The Black Hand | * Install a backdoor on the I.I.I.I | |
| | | server | |
| | | * Home Computer RAM of at least 64GB | |
+ +----------------+-----------------------------------------+-------------------------------+ + +----------------+-----------------------------------------+-------------------------------+
| | Bitrunners | * Hack run4theh111z manually | | | | Bitrunners | * Install a backdoor on the run4theh111z| |
| | | server | |
| | | * Home Computer RAM of at least 128GB | |
+---------------------+----------------+-----------------------------------------+-------------------------------+ +---------------------+----------------+-----------------------------------------+-------------------------------+
| Megacorporations | ECorp | * Have 200k reputation with | | | Megacorporations | ECorp | * Have 200k reputation with | |
| | | the Corporation | | | | | the Corporation | |
@ -109,7 +115,8 @@ List of Factions and their Requirements
+ +----------------+-----------------------------------------+-------------------------------+ + +----------------+-----------------------------------------+-------------------------------+
| | Fulcrum Secret | * Have 250k reputation with | | | | Fulcrum Secret | * Have 250k reputation with | |
| | Technologies | the Corporation | | | | Technologies | the Corporation | |
| | | * Hack fulcrumassets manually | | | | | * Install a backdoor on the | |
| | | fulcrumassets server | |
+---------------------+----------------+-----------------------------------------+-------------------------------+ +---------------------+----------------+-----------------------------------------+-------------------------------+
| Criminal | Slum Snakes | * All Combat Stats of 30 | | | Criminal | Slum Snakes | * All Combat Stats of 30 | |
| Organizations | | * -9 Karma | | | Organizations | | * -9 Karma | |

@ -148,6 +148,14 @@ has root access, what ports are opened/closed, and also hacking-related informat
such as an estimated chance to successfully hack, an estimate of how much money is such as an estimated chance to successfully hack, an estimate of how much money is
available on the server, etc. available on the server, etc.
backdoor
^^^^^^^^
Installs a backdoor on the current server. Root access is required to do this.
Servers will give different bonuses when you install a backdoor.
This can pass faction tests or give bonsues such as discounts from companies.
buy buy
^^^ ^^^

@ -3,10 +3,57 @@
Changelog Changelog
========= =========
v0.51.6 - 2021-04-28 Backdoor! (Community)
-------
**Backdoor**
* a new terminal command, backdoor, has been added to help differentiate
between the terminal hack command and the netscript hack function. (@dewint)
**Milestones**
* A new tab under the Help menu has been added to guide players through the
game.
**Casino**
* Blackjack has been added (@BigD)
**Netscript**
* 'prompt' now converts input to JSON.
* 'getRunningScript' is a new netscript function that returns a bunch of
data related to a running script.
**Coding contracts**
* trivial puzzles should no longer appear.
**Infiltration**
* All numbers are formatted like the rest of the game.
**Misc.**
* Server security is capped at 100.
* Added option to quit a job.
* 'cd' no longer works on unexistent folders.
* cd with no arguments brings you back to top level folder (@Andreas)
* 'softReset' documentation udpated.
* Money tracker now accounts for going to the hospital manually.
* codemirror is now the default editor (for new save files)
* fix typo in dark web help text (@Rodeth)
* so many documentation and typos fixes (@Pimgd)
* A corruption visual effect has been added to location with servers that
have backdoor installed. (@dewint)
v0.51.5 - 2021-04-20 Flags! (hydroflame) v0.51.5 - 2021-04-20 Flags! (hydroflame)
---------------------------------------- ----------------------------------------
Netscript **Netscript**
* 'flags' is a new function that helps script handle flags. * 'flags' is a new function that helps script handle flags.
This is subject to change if it doesn't meet the need of the players. This is subject to change if it doesn't meet the need of the players.
@ -16,11 +63,11 @@ Netscript
* 'isRunning' hostname defaults to current server. * 'isRunning' hostname defaults to current server.
* 'isRunning' now works with pid as first argument. * 'isRunning' now works with pid as first argument.
Gang **Gang**
* Nerfed ascension mechanic once again :( * Nerfed ascension mechanic once again :(
Misc. **Misc.**
* Souce-File typo fix * Souce-File typo fix
* Fix 'while you were away' screen. * Fix 'while you were away' screen.
@ -29,28 +76,28 @@ Misc.
v0.51.4 - 2021-04-19 Manual hacking is fun (hydroflame) v0.51.4 - 2021-04-19 Manual hacking is fun (hydroflame)
------------------------------------------------------- -------------------------------------------------------
Manual hacking **Manual hacking**
* These bonus require an install or a soft reset to take effect. * These bonus require an install or a soft reset to take effect.
* Manual hacking gyms and university gives you a 10% discount. * Manual hacking gyms and university gives you a 10% discount.
* Manual hacking a corporation server decreases the penalty for leaving work * Manual hacking a corporation server decreases the penalty for leaving work
early. early.
BladeBurner **BladeBurner**
* nerfed int exp gained. * nerfed int exp gained.
Documentation **Documentation**
* purchaseServer specifies what happens on failure. * purchaseServer specifies what happens on failure.
* Fixed typo in recommended bitnode page. * Fixed typo in recommended bitnode page.
* Removed misleading ram requirements for hacking factions. * Removed misleading ram requirements for hacking factions.
Netscript **Netscript**
* growthAnalyze handles Infinity correctly. * growthAnalyze handles Infinity correctly.
Misc. **Misc.**
* Faction Augmentation will list how much reputation is required even after * Faction Augmentation will list how much reputation is required even after
that goal has been reached. that goal has been reached.

@ -634,7 +634,7 @@ This tells me that I can reach :code:`CSEC` by going through :code:`iron-gym`::
Make sure you notice the required hacking skill for the :code:`CSEC` server. Make sure you notice the required hacking skill for the :code:`CSEC` server.
This is a random value between 51 and 60. Although you receive the message This is a random value between 51 and 60. Although you receive the message
from CSEC once you hit 50 hacking, you cannot actually pass their test from CSEC once you hit 50 hacking, you cannot actually pass their test
until your hacking is high enough to hack their server. until your hacking is high enough to install a backdoor on their server.
After you are connected to the :code:`CSEC` server, you can hack it. Note that this After you are connected to the :code:`CSEC` server, you can hack it. Note that this
server requires one open port in order to gain root access. We can open the SSH port server requires one open port in order to gain root access. We can open the SSH port
@ -642,10 +642,10 @@ using the :code:`BruteSSH.exe` program we created earlier. In |Terminal|::
$ run BruteSSH.exe $ run BruteSSH.exe
$ run NUKE.exe $ run NUKE.exe
$ hack $ backdoor
Keep hacking the server until you are successful. After you successfully hack it, you should After you successfully install the backdoor, you should receive a faction
receive a faction invitation from |CyberSec| shortly afterwards. Accept it. If you accidentally invitation from |CyberSec| shortly afterwards. Accept it. If you accidentally
reject the invitation, that's okay. Just go to the :code:`Factions` tab reject the invitation, that's okay. Just go to the :code:`Factions` tab
(|Keyboard shortcut| Alt + f) and you should see an option that lets you (|Keyboard shortcut| Alt + f) and you should see an option that lets you
accept the invitation. accept the invitation.

@ -293,7 +293,7 @@ Source-File
* Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode * Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode
(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT (Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT
when installing Augmentation when installing Augmentations.)
Difficulty Difficulty
Hard Hard

@ -27,7 +27,7 @@ getServer() Netscript Function
sshPortOpen sshPortOpen
baseDifficulty baseDifficulty
hackDifficulty hackDifficulty
manuallyHacked backdoorInstalled
minDifficulty minDifficulty
moneyAvailable moneyAvailable
moneyMax moneyMax

@ -0,0 +1,88 @@
getRunningScript() Netscript Function
=====================================
.. js:function:: getRunningScript()
:RAM cost: 0.3 GB
:returns: Script object or null if not found
The object has the following properties:
.. code-block:: javascript
{
// Script arguments
args
// Script filename
filename
// This script's logs. An array of log entries
logs
// Flag indicating whether the logs have been updated since
// the last time the UI was updated
logUpd
// Total amount of hacking experience earned from this script when offline
offlineExpGained
// Total amount of money made by this script when offline
offlineMoneyMade
// Number of seconds that the script has been running offline
offlineRunningTime
// Total amount of hacking experience earned from this script when online
onlineExpGained
// Total amount of money made by this script when online
onlineMoneyMade
// Number of seconds that this script has been running online
onlineRunningTime
// Process ID.
pid
// How much RAM this script uses for ONE thread
ramUsage
// IP of the server on which this script is running
server
// Number of threads that this script is running with
threads
}
Examples:
.. code-block:: javascript
getRunningScript(); // get the current script.
.. js:function:: getRunningScript(pid)
:RAM cost: 0.3 GB
:param number pid: PID of the script
:returns: Script object or null if not found
Examples:
.. code-block:: javascript
getRunningScript(42); // get the script with pid 42.
.. js:function:: getRunningScript(fn, hostname[, args])
:RAM cost: 0.3 GB
:param number fn: filename of the target script
:param number hostname: hostname of the server running the script
:param number args: arguments to the script.
:returns: Script object or null if not found
Examples:
.. code-block:: javascript
getRunningScript("example.script", "home", "foodnstuff"); // get the script called "example.script" on "home" with argument "foodnstuff"

@ -40,7 +40,7 @@ isRunning() Netscript Function
isRunning("foo.script", "joesguns", 1, 5, "test"); isRunning("foo.script", "joesguns", 1, 5, "test");
.. js:function:: isRunning(scriptPid[, hostname=current hostname]) .. js:function:: isRunning(scriptPid)
:RAM cost: 0.1 GB :RAM cost: 0.1 GB
:param number scriptPid: PID of the script to check. :param number scriptPid: PID of the script to check.
@ -52,5 +52,3 @@ isRunning() Netscript Function
.. code-block:: javascript .. code-block:: javascript
isRunning(39); isRunning(39);
isRunning(39, getHostname());

@ -30,7 +30,7 @@ tail() Netscript Function
// Open logs from foo.script on the foodnstuff server that was run with the arguments [1, "test"] // Open logs from foo.script on the foodnstuff server that was run with the arguments [1, "test"]
tail("foo.script", "foodnstuff", 1, "test"); tail("foo.script", "foodnstuff", 1, "test");
.. js:function:: tail(scriptPid[, hostname=current hostname]) .. js:function:: tail(scriptPid)
:RAM cost: 0 GB :RAM cost: 0 GB
:param number scriptPid: PID of the script to tail. :param number scriptPid: PID of the script to tail.
@ -46,3 +46,16 @@ tail() Netscript Function
// Open logs from process with id 42 on the foodnstuff server // Open logs from process with id 42 on the foodnstuff server
tail(42, "foodnstuff"); tail(42, "foodnstuff");
.. js:function:: tail()
:RAM cost: 0 GB
Opens the current script logs.
Example:
.. code-block:: javascript
// Open the current script logs.
tail();

@ -75,6 +75,7 @@ This includes information such as function signatures, what they do, and their r
scriptKill() <basicfunctions/scriptKill> scriptKill() <basicfunctions/scriptKill>
getScriptName() <basicfunctions/getScriptName> getScriptName() <basicfunctions/getScriptName>
getScriptRam() <basicfunctions/getScriptRam> getScriptRam() <basicfunctions/getScriptRam>
getRunningScript() <basicfunctions/getRunningScript>
getHackTime() <basicfunctions/getHackTime> getHackTime() <basicfunctions/getHackTime>
getGrowTime() <basicfunctions/getGrowTime> getGrowTime() <basicfunctions/getGrowTime>
getWeakenTime() <basicfunctions/getWeakenTime> getWeakenTime() <basicfunctions/getWeakenTime>

@ -9,13 +9,12 @@ manualHack() Netscript Function
If you are not in BitNode-4, then you must have Level 1 of Source-File 4 in order to use this function. If you are not in BitNode-4, then you must have Level 1 of Source-File 4 in order to use this function.
This function will perform a manual hack on the server you are currently connected to. This function will perform a manual hack on the server you are currently connected to.
This is typically required to join factions.
Examples: Examples:
.. code-block:: javascript .. code-block:: javascript
connect("CSEC"); connect("foodnstuff");
manualHack(); manualHack();
.. warning:: .. warning::

@ -1,9 +1,13 @@
softReset() Netscript Function softReset() Netscript Function
=================================== ===================================
.. js:function:: softReset() .. js:function:: softReset([callbackScript])
:RAM cost: 5 GB :RAM cost: 5 GB
:param string cbScript:
Optional callback script. This is a script that will automatically be
run after the soft reset. This script will be run with no arguments and
1 thread. It must be located on your home computer.
If you are not in BitNode-4, then you must have Level 3 of Source-File 4 in order to use this function. If you are not in BitNode-4, then you must have Level 3 of Source-File 4 in order to use this function.

@ -100,9 +100,12 @@
<li id="help-menu-header-li"> <li id="help-menu-header-li">
<button id="help-menu-header" class="mainmenu-accordion-header"> Help </button> <button id="help-menu-header" class="mainmenu-accordion-header"> Help </button>
</li> </li>
<li id="tutorial-tab" class="mainmenu-accordion-panel"> <li id="milestones-tab" class="mainmenu-accordion-panel">
<button id="tutorial-menu-link"> Tutorial </button> <button id="milestones-menu-link"> Milestones </button>
</li> </li>
<li id="tutorial-tab" class="mainmenu-accordion-panel">
<button id="tutorial-menu-link"> Tutorial </button>
</li>
<li id="options-tab" class="mainmenu-accordion-panel"> <li id="options-tab" class="mainmenu-accordion-panel">
<button id="options-menu-link"> Options </button> <button id="options-menu-link"> Options </button>
</li> </li>
@ -226,6 +229,10 @@
<!-- Augmentations --> <!-- Augmentations -->
<div id="augmentations-container" class="generic-menupage-container"></div> <div id="augmentations-container" class="generic-menupage-container"></div>
<!-- Milestones content -->
<div id="milestones-container" class="generic-menupage-container">
</div>
<!-- Tutorial content --> <!-- Tutorial content -->
<div id="tutorial-container" class="generic-menupage-container"> <div id="tutorial-container" class="generic-menupage-container">
<h1> Tutorial (AKA Links to Documentation) </h1> <h1> Tutorial (AKA Links to Documentation) </h1>
@ -521,6 +528,16 @@
<input class="optionCheckbox" type="checkbox" name="settingsDisableASCIIArt" id="settingsDisableASCIIArt"> <input class="optionCheckbox" type="checkbox" name="settingsDisableASCIIArt" id="settingsDisableASCIIArt">
</fieldset> </fieldset>
<!-- Disable text effects such as corruption. -->
<fieldset>
<label for="settingsDisableTextEffects" class="tooltip">Disable Text Effects:
<span class="tooltiptexthigh">
If this is set, text effects will not be displayed. This can help if text is difficult to read in certain areas.
</span>
</label>
<input class="optionCheckbox" type="checkbox" name="settingsDisableTextEffects" id="settingsDisableTextEffects">
</fieldset>
<!-- Locale for displaying numbers --> <!-- Locale for displaying numbers -->
<fieldset> <fieldset>
<label for="settingsLocale" class="tooltip">Locale: <label for="settingsLocale" class="tooltip">Locale:

13753
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -6,6 +6,7 @@
"url": "https://github.com/danielyxie/bitburner/issues" "url": "https://github.com/danielyxie/bitburner/issues"
}, },
"dependencies": { "dependencies": {
"@material-ui/core": "^4.11.3",
"@types/numeral": "0.0.25", "@types/numeral": "0.0.25",
"@types/react": "^16.8.6", "@types/react": "^16.8.6",
"@types/react-dom": "^16.8.2", "@types/react-dom": "^16.8.2",
@ -47,7 +48,10 @@
"@babel/core": "^7.3.4", "@babel/core": "^7.3.4",
"@babel/preset-react": "^7.0.0", "@babel/preset-react": "^7.0.0",
"@types/chai": "^4.1.7", "@types/chai": "^4.1.7",
"@types/lodash": "^4.14.168",
"@types/mocha": "^5.2.7", "@types/mocha": "^5.2.7",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
"babel-loader": "^8.0.5", "babel-loader": "^8.0.5",
"beautify-lint": "^1.0.3", "beautify-lint": "^1.0.3",
"benchmark": "^2.1.1", "benchmark": "^2.1.1",
@ -55,8 +59,8 @@
"chai": "^4.2.0", "chai": "^4.2.0",
"css-loader": "^0.28.11", "css-loader": "^0.28.11",
"es6-promise-polyfill": "^1.1.1", "es6-promise-polyfill": "^1.1.1",
"eslint": "^4.19.1", "eslint": "^7.24.0",
"eslint-plugin-node": "^6.0.1", "eslint-plugin-node": "^11.1.0",
"file-loader": "^1.1.11", "file-loader": "^1.1.11",
"html-webpack-plugin": "^3.2.0", "html-webpack-plugin": "^3.2.0",
"i18n-webpack-plugin": "^1.0.0", "i18n-webpack-plugin": "^1.0.0",
@ -86,8 +90,7 @@
"stylelint-declaration-use-variable": "^1.6.1", "stylelint-declaration-use-variable": "^1.6.1",
"stylelint-order": "^0.8.1", "stylelint-order": "^0.8.1",
"ts-loader": "^4.5.0", "ts-loader": "^4.5.0",
"tslint": "^5.10.0", "typescript": "^4.2.4",
"typescript": "^2.9.2",
"uglify-es": "^3.3.9", "uglify-es": "^3.3.9",
"uglifyjs-webpack-plugin": "^1.3.0", "uglifyjs-webpack-plugin": "^1.3.0",
"url-loader": "^1.0.1", "url-loader": "^1.0.1",
@ -113,10 +116,9 @@
"build": "webpack --mode production", "build": "webpack --mode production",
"build:dev": "webpack --mode development", "build:dev": "webpack --mode development",
"build:test": "webpack --config webpack.config-test.js", "build:test": "webpack --config webpack.config-test.js",
"lint": "npm run lint:typescript & npm run lint:javascript & npm run lint:style", "lint": "npm run lint:jsts & npm run lint:style",
"lint:javascript": "eslint *.js ./src/**/*.js ./tests/**/*.js ./utils/**/*.js", "lint:jsts": "eslint '*.{js,jsx,ts,tsx}' './src/**/*.{js,jsx,ts,tsx}' './test/**/*.{js,jsx,ts,tsx}' './utils/**/*.{js,jsx,ts,tsx}'",
"lint:style": "stylelint ./css/*", "lint:style": "stylelint ./css/*",
"lint:typescript": "tslint --project . --exclude **/*.d.ts --format stylish src/**/*.ts utils/**/*.ts",
"preinstall": "node ./scripts/engines-check.js", "preinstall": "node ./scripts/engines-check.js",
"test": "mochapack --webpack-config webpack.config-test.js -r jsdom-global/register ./test/index.js", "test": "mochapack --webpack-config webpack.config-test.js -r jsdom-global/register ./test/index.js",
"watch": "webpack --watch --mode production", "watch": "webpack --watch --mode production",

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

@ -31,6 +31,7 @@ import { clearObject } from "../../utils/helpers/clearObject";
import { createElement } from "../../utils/uiHelpers/createElement"; import { createElement } from "../../utils/uiHelpers/createElement";
import { isString } from "../../utils/helpers/isString"; import { isString } from "../../utils/helpers/isString";
import { removeChildrenFromElement } from "../../utils/uiHelpers/removeChildrenFromElement"; import { removeChildrenFromElement } from "../../utils/uiHelpers/removeChildrenFromElement";
import { Money } from "../ui/React/Money";
import React from "react"; import React from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
@ -177,7 +178,7 @@ function initAugmentations() {
const CombatRib3 = new Augmentation({ const CombatRib3 = new Augmentation({
name:AugmentationNames.CombatRib3, repCost:14e3, moneyCost:24e6, name:AugmentationNames.CombatRib3, repCost:14e3, moneyCost:24e6,
info:"This is an upgrade to the Combat Rib II augmentation, and is capable of releasing even more potent combat-enhancing " + info:"This is an upgrade to the Combat Rib II augmentation, and is capable of releasing even more potent combat-enhancing " +
"drugs into the bloodstream<br><br>." + "drugs into the bloodstream.<br><br>" +
"This augmentation increases the player's strength and defense by 18%.", "This augmentation increases the player's strength and defense by 18%.",
prereqs:[AugmentationNames.CombatRib2], prereqs:[AugmentationNames.CombatRib2],
strength_mult: 1.18, strength_mult: 1.18,
@ -880,9 +881,7 @@ function initAugmentations() {
info:"The body is genetically re-engineered so that it produces the ADR-V1 pheromone, " + info:"The body is genetically re-engineered so that it produces the ADR-V1 pheromone, " +
"an artificial pheromone discovered by scientists. The ADR-V1 pheromone, when excreted, " + "an artificial pheromone discovered by scientists. The ADR-V1 pheromone, when excreted, " +
"triggers feelings of admiration and approval in other people.<br><br>" + "triggers feelings of admiration and approval in other people.<br><br>" +
"This augmentation:<br>" + "This augmentation increases the amount of reputation the player gains when working for a faction or company by 10%.",
"Increases the amount of reputation the player gains when working for a company by 10% <br>" +
"Increases the amount of reputation the player gains for a faction by 10%.",
company_rep_mult: 1.1, company_rep_mult: 1.1,
faction_rep_mult: 1.1, faction_rep_mult: 1.1,
}); });
@ -897,8 +896,7 @@ function initAugmentations() {
info:"The body is genetically re-engineered so that it produces the ADR-V2 pheromone, " + info:"The body is genetically re-engineered so that it produces the ADR-V2 pheromone, " +
"which is similar to but more potent than ADR-V1. This pheromone, when excreted, " + "which is similar to but more potent than ADR-V1. This pheromone, when excreted, " +
"triggers feelings of admiration, approval, and respect in others.<br><br>" + "triggers feelings of admiration, approval, and respect in others.<br><br>" +
"This augmentation:<br>" + "This augmentation increases the amount of reputation the player gains when working for a faction or company by 20%.",
"Increases the amount of reputation the player gains for a faction and company by 20%.",
company_rep_mult: 1.2, company_rep_mult: 1.2,
faction_rep_mult: 1.2, faction_rep_mult: 1.2,
}); });
@ -915,8 +913,7 @@ function initAugmentations() {
"criminal organizations and allows the user to project and control holographic " + "criminal organizations and allows the user to project and control holographic " +
"simulacrums within a large radius. These simulacrums are commonly used for " + "simulacrums within a large radius. These simulacrums are commonly used for " +
"espionage and surveillance work.<br><br>" + "espionage and surveillance work.<br><br>" +
"This augmentation:<br>" + "This augmentation increases the amount of reputation the player gains when working for a faction or company by 15%.",
"Increases the amount of reputation the player gains when working for a faction or company by 15%.",
company_rep_mult: 1.15, company_rep_mult: 1.15,
faction_rep_mult: 1.15, faction_rep_mult: 1.15,
}); });
@ -1152,7 +1149,7 @@ function initAugmentations() {
"cells, when powered, have a negative refractive index. As a result, they bend light " + "cells, when powered, have a negative refractive index. As a result, they bend light " +
"around the skin, making the user much harder to see from the naked eye.<br><br>" + "around the skin, making the user much harder to see from the naked eye.<br><br>" +
"This augmentation:<br>" + "This augmentation:<br>" +
"Increases the player's agility by 5% <br>" + "Increases the player's agility by 5%.<br>" +
"Increases the amount of money the player gains from crimes by 10%.", "Increases the amount of money the player gains from crimes by 10%.",
agility_mult: 1.05, agility_mult: 1.05,
crime_money_mult: 1.1, crime_money_mult: 1.1,
@ -1170,8 +1167,8 @@ function initAugmentations() {
"cells, when powered, are capable of not only bending light but also of bending heat, " + "cells, when powered, are capable of not only bending light but also of bending heat, " +
"making the user more resilient as well as stealthy.<br><br>" + "making the user more resilient as well as stealthy.<br><br>" +
"This augmentation:<br>" + "This augmentation:<br>" +
"Increases the player's agility by 10% <br>" + "Increases the player's agility by 10%.<br>" +
"Increases the player's defense by 10% <br>" + "Increases the player's defense by 10%.<br>" +
"Increases the amount of money the player gains from crimes by 25%.", "Increases the amount of money the player gains from crimes by 25%.",
prereqs:[AugmentationNames.LuminCloaking1], prereqs:[AugmentationNames.LuminCloaking1],
agility_mult: 1.1, agility_mult: 1.1,
@ -1372,7 +1369,7 @@ function initAugmentations() {
name:AugmentationNames.Xanipher, repCost:350e3, moneyCost:850e6, name:AugmentationNames.Xanipher, repCost:350e3, moneyCost:850e6,
info:"A concoction of advanced nanobots that is orally ingested into the " + info:"A concoction of advanced nanobots that is orally ingested into the " +
"body. These nanobots induce physiological change and significantly " + "body. These nanobots induce physiological change and significantly " +
"improve the body's functionining in all aspects.<br><br>" + "improve the body's functioning in all aspects.<br><br>" +
"This augmentation:<br>" + "This augmentation:<br>" +
"Increases all of the player's stats by 20%.<br>" + "Increases all of the player's stats by 20%.<br>" +
"Increases the player's experience gain rate for all stats by 15%.", "Increases the player's experience gain rate for all stats by 15%.",
@ -1554,12 +1551,12 @@ function initAugmentations() {
// Sector12 // Sector12
const CashRoot = new Augmentation({ const CashRoot = new Augmentation({
name:AugmentationNames.CashRoot, repCost:5e3, moneyCost:25e6, name:AugmentationNames.CashRoot, repCost:5e3, moneyCost:25e6,
info:"A collection of digital assets saved on a small chip. The chip is implanted " + info:<>A collection of digital assets saved on a small chip. The chip is implanted
"into your wrist. A small jack in the chip allows you to connect it to a computer " + into your wrist. A small jack in the chip allows you to connect it to a computer
"and upload the assets.<br><br>" + and upload the assets.<br /><br />
"This augmentation:<br>" + This augmentation:<br />
"Lets the player start with $1,000,000 after a reset.<br>" + Lets the player start with {Money(1e6)} after a reset.<br />
"Lets the player start with the BruteSSH.exe program after a reset." Lets the player start with the BruteSSH.exe program after a reset.</>
}); });
CashRoot.addToFactions(["Sector-12"]); CashRoot.addToFactions(["Sector-12"]);
if (augmentationExists(AugmentationNames.CashRoot)) { if (augmentationExists(AugmentationNames.CashRoot)) {
@ -1574,8 +1571,7 @@ function initAugmentations() {
"synthesizes glucose, amino acids, and vitamins and redistributes them " + "synthesizes glucose, amino acids, and vitamins and redistributes them " +
"across the body. The device is powered by the body's naturally wasted " + "across the body. The device is powered by the body's naturally wasted " +
"energy in the form of heat.<br><br>" + "energy in the form of heat.<br><br>" +
"This augmentation:<br>" + "This augmentation increases the player's experience gain rate for all combat stats by 20%.",
"Increases the player's experience gain rate for all combat stats by 20%.",
strength_exp_mult: 1.2, strength_exp_mult: 1.2,
defense_exp_mult: 1.2, defense_exp_mult: 1.2,
dexterity_exp_mult: 1.2, dexterity_exp_mult: 1.2,
@ -1785,8 +1781,7 @@ function initAugmentations() {
"nature of the plasma disrupts the electrical systems of Augmentations. However, " + "nature of the plasma disrupts the electrical systems of Augmentations. However, " +
"it can also be effective against non-augmented enemies due to its high temperature " + "it can also be effective against non-augmented enemies due to its high temperature " +
"and concussive force.<br><br>" + "and concussive force.<br><br>" +
"This augmentation:<br>" + "This augmentation increases the player's success chance in Bladeburner contracts/operations by 6%.",
"Increases the player's success chance in Bladeburner contracts/operations by 6%.",
bladeburner_success_chance_mult: 1.06, bladeburner_success_chance_mult: 1.06,
isSpecial: true, isSpecial: true,
}); });
@ -1799,8 +1794,7 @@ function initAugmentations() {
"is more advanced and powerful than the original V1 model. This V2 model is " + "is more advanced and powerful than the original V1 model. This V2 model is " +
"more power-efficiency, more accurate, and can fire plasma bolts at a much " + "more power-efficiency, more accurate, and can fire plasma bolts at a much " +
"higher velocity than the V1 model.<br><br>" + "higher velocity than the V1 model.<br><br>" +
"This augmentation:<br>" + "This augmentation increases the player's success chance in Bladeburner contracts/operations by 8%.",
"Increases the player's success chance in Bladeburner contracts/operations by 8%.",
prereqs:[AugmentationNames.HyperionV1], prereqs:[AugmentationNames.HyperionV1],
bladeburner_success_chance_mult: 1.08, bladeburner_success_chance_mult: 1.08,
isSpecial: true, isSpecial: true,
@ -1958,8 +1952,7 @@ function initAugmentations() {
info:"Upgrades the BLADE-51b Tesla Armor with a concentrated deuterium-fluoride laser " + info:"Upgrades the BLADE-51b Tesla Armor with a concentrated deuterium-fluoride laser " +
"weapon. It's precision an accuracy makes it useful for quickly neutralizing " + "weapon. It's precision an accuracy makes it useful for quickly neutralizing " +
"threats while keeping casualties to a minimum.<br><br>" + "threats while keeping casualties to a minimum.<br><br>" +
"This augmentation:<br>" + "This augmentation increases the player's success chance in Bladeburner contracts/operations by 8%.",
"Increases the player's success chance in Bladeburner contracts/operations by 8%.",
prereqs:[AugmentationNames.BladeArmor], prereqs:[AugmentationNames.BladeArmor],
bladeburner_success_chance_mult: 1.08, bladeburner_success_chance_mult: 1.08,
isSpecial: true, isSpecial: true,
@ -1973,8 +1966,7 @@ function initAugmentations() {
"multiple-fiber system. The upgraded weapon uses multiple fiber laser " + "multiple-fiber system. The upgraded weapon uses multiple fiber laser " +
"modules that combine together to form a single, more powerful beam of up to " + "modules that combine together to form a single, more powerful beam of up to " +
"2000MW.<br><br>" + "2000MW.<br><br>" +
"This augmentation:<br>" + "This augmentation increases the player's success chance in Bladeburner contracts/operations by 10%.",
"Increases the player's success chance in Bladeburner contracts/operations by 10%.",
prereqs:[AugmentationNames.BladeArmorUnibeam], prereqs:[AugmentationNames.BladeArmorUnibeam],
bladeburner_success_chance_mult: 1.1, bladeburner_success_chance_mult: 1.1,
isSpecial: true, isSpecial: true,

@ -26,8 +26,8 @@ export function PlayerMultipliers(): React.ReactElement {
let elems: any[] = []; let elems: any[] = [];
if(r) { if(r) {
elems = [ elems = [
<td key='2'>&nbsp;=>&nbsp;</td>, <td key="2">&nbsp;{"=>"}&nbsp;</td>,
<td key='3'>{numeralWrapper.formatPercentage(r)}</td> <td key="3">{numeralWrapper.formatPercentage(r)}</td>
]; ];
} }
return elems; return elems;
@ -36,8 +36,8 @@ export function PlayerMultipliers(): React.ReactElement {
return <table> return <table>
<tbody> <tbody>
{rows.map((r: any) => <tr key={r[0]}> {rows.map((r: any) => <tr key={r[0]}>
<td key='0'><span>{r[0]} multiplier:&nbsp;</span></td> <td key="0"><span>{r[0]} multiplier:&nbsp;</span></td>
<td key='1' style={{textAlign: 'right'}}>{numeralWrapper.formatPercentage(r[1])}</td> <td key="1" style={{textAlign: 'right'}}>{numeralWrapper.formatPercentage(r[1])}</td>
{improvements(r[2])} {improvements(r[2])}
</tr>)} </tr>)}
</tbody> </tbody>

424
src/Casino/Blackjack.tsx Normal file

@ -0,0 +1,424 @@
import * as React from "react";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Money } from "../ui/React/Money";
import { Game } from "./Game";
import { Deck } from "./CardDeck/Deck";
import { Hand } from "./CardDeck/Hand";
import { InputAdornment } from "@material-ui/core";
import { ReactCard } from "./CardDeck/ReactCard";
import { MuiTextField } from "../ui/React/MuiTextField";
import { MuiButton } from "../ui/React/MuiButton";
import { MuiPaper } from "../ui/React/MuiPaper";
const MAX_BET = 100e6;
enum Result {
Pending = "",
PlayerWon = "You won!",
PlayerWonByBlackjack = "You Won! Blackjack!",
DealerWon = "You lost!",
Tie = "Push! (Tie)",
}
type Props = {
p: IPlayer;
}
type State ={
playerHand: Hand;
dealerHand: Hand;
bet: number;
betInput: string;
gameInProgress: boolean;
result: Result;
gains: number; // Track gains only for this session
wagerInvalid: boolean;
wagerInvalidHelperText: string;
}
export class Blackjack extends Game<Props, State> {
deck: Deck;
constructor(props: Props) {
super(props);
this.deck = new Deck(5); // 5-deck multideck
const initialBet = 1e6;
this.state = {
playerHand: new Hand([]),
dealerHand: new Hand([]),
bet: initialBet,
betInput: String(initialBet),
gameInProgress: false,
result: Result.Pending,
gains: 0,
wagerInvalid: false,
wagerInvalidHelperText: "",
}
}
canStartGame = (): boolean => {
const { p } = this.props;
const { bet } = this.state;
return p.canAfford(bet);
}
startGame = (): void => {
if (!this.canStartGame()) { return; }
// Take money from player right away so that player's dont just "leave" to avoid the loss (I mean they could
// always reload without saving but w.e)
this.props.p.loseMoney(this.state.bet);
const playerHand = new Hand([ this.deck.safeDrawCard(), this.deck.safeDrawCard() ]);
const dealerHand = new Hand([ this.deck.safeDrawCard(), this.deck.safeDrawCard() ]);
this.setState({
playerHand,
dealerHand,
gameInProgress: true,
result: Result.Pending,
});
// If the player is dealt a blackjack and the dealer is not, then the player
// immediately wins
if (this.getTrueHandValue(playerHand) === 21) {
if (this.getTrueHandValue(dealerHand) === 21) {
this.finishGame(Result.Tie);
} else {
this.finishGame(Result.PlayerWonByBlackjack);
}
} else if (this.getTrueHandValue(dealerHand) === 21) {
// Check if dealer won by blackjack. We know at this point that the player does not also have blackjack.
this.finishGame(Result.DealerWon);
}
}
// Returns an array of numbers representing all possible values of the given Hand. The reason it needs to be
// an array is because an Ace can count as both 1 and 11.
getHandValue = (hand: Hand): number[] => {
let result: number[] = [ 0 ];
for (let i = 0 ; i < hand.cards.length; ++i) {
const value = hand.cards[i].value;
if (value >= 10) {
result = result.map((x) => x + 10);
} else if (value === 1) {
result = result.flatMap((x) => [ x + 1, x + 11 ]);
} else {
result = result.map((x) => x + value);
}
}
return result;
}
// Returns the single hand value used for determine things like victory and whether or not
// the dealer has to hit. Essentially this uses the biggest value that's 21 or under. If no such value exists,
// then it means the hand is busted and we can just return whatever
getTrueHandValue = (hand: Hand): number => {
const handValues = this.getHandValue(hand);
const valuesUnder21 = handValues.filter((x) => (x <= 21));
if (valuesUnder21.length > 0) {
valuesUnder21.sort((a, b) => a - b);
return valuesUnder21[valuesUnder21.length - 1];
} else {
// Just return the first value. It doesnt really matter anyways since hand is buted
return handValues[0];
}
}
// Returns all hand values that are 21 or under. If no values are 21 or under, then the first value is returned.
getHandDisplayValues = (hand: Hand): number[] => {
const handValues = this.getHandValue(hand);
if (this.isHandBusted(hand)) {
// Hand is busted so just return the 1st value, doesn't really matter
return [ ...new Set([ handValues[0] ]) ];
} else {
return [ ...new Set(handValues.filter((x) => x <= 21)) ];
}
}
isHandBusted = (hand: Hand): boolean => {
return this.getTrueHandValue(hand) > 21;
}
playerHit = (event: React.MouseEvent): void => {
if (!event.isTrusted) { return; }
const newHand = this.state.playerHand.addCards(this.deck.safeDrawCard());
this.setState({
playerHand: newHand,
});
// Check if player busted, and finish the game if so
if (this.isHandBusted(newHand)) {
this.finishGame(Result.DealerWon);
}
}
playerStay = (event: React.MouseEvent): void => {
if (!event.isTrusted) { return; }
// Determine if Dealer needs to hit. A dealer must hit if they have 16 or lower.
// If the dealer has a Soft 17 (Ace + 6), then they stay.
let newDealerHand = this.state.dealerHand;
while (true) {
// The dealer's "true" hand value is the 2nd one if its 21 or less (the 2nd value is always guaranteed
// to be equal or larger). Otherwise its the 1st.
const dealerHandValue = this.getTrueHandValue(newDealerHand);
if (dealerHandValue <= 16) {
newDealerHand = newDealerHand.addCards(this.deck.safeDrawCard());
} else {
break;
}
}
this.setState({
dealerHand: newDealerHand,
})
// If dealer has busted, then player wins
if (this.isHandBusted(newDealerHand)) {
this.finishGame(Result.PlayerWon);
} else {
const dealerHandValue = this.getTrueHandValue(newDealerHand);
const playerHandValue = this.getTrueHandValue(this.state.playerHand);
console.log(`dealerHandValue: ${dealerHandValue}`);
console.log(`playerHandValue: ${playerHandValue}`);
// We expect nobody to have busted. If someone busted, there is an error
// in our game logic
if (dealerHandValue > 21 || playerHandValue > 21) {
throw new Error("Someone busted when not expected to");
}
if (playerHandValue > dealerHandValue) {
this.finishGame(Result.PlayerWon);
} else if (playerHandValue < dealerHandValue) {
this.finishGame(Result.DealerWon);
} else {
this.finishGame(Result.Tie);
}
}
}
finishGame = (result: Result): void => {
let gains = 0;
if (this.isPlayerWinResult(result)) {
gains = this.state.bet;
// We 2x the gains because we took away money at the start, so we need to give the original bet back.
this.win(this.props.p, 2 * gains);
} else if (result === Result.DealerWon) {
gains = -1 * this.state.bet;
// Dont need to take money here since we already did it at the start
} else if (result === Result.Tie) {
this.win(this.props.p, this.state.bet); // Get the original bet back
}
this.setState({
gameInProgress: false,
result,
gains: this.state.gains + gains,
});
}
isPlayerWinResult = (result: Result): boolean => {
return (result === Result.PlayerWon || result === Result.PlayerWonByBlackjack);
}
wagerOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
const { p } = this.props;
const betInput = event.target.value;
const wager = Math.round(parseFloat(betInput));
if (isNaN(wager)) {
this.setState({
bet: 0,
betInput,
wagerInvalid: true,
wagerInvalidHelperText: "Not a valid number",
});
} else if (wager <= 0) {
this.setState({
bet: 0,
betInput,
wagerInvalid: true,
wagerInvalidHelperText: "Must bet a postive amount",
});
} else if (wager > MAX_BET) {
this.setState({
bet: 0,
betInput,
wagerInvalid: true,
wagerInvalidHelperText: "Exceeds max bet",
});
} else if (!p.canAfford(wager)) {
this.setState({
bet: 0,
betInput,
wagerInvalid: true,
wagerInvalidHelperText: "Not enough money",
});
} else {
// Valid wager
this.setState({
bet: wager,
betInput,
wagerInvalid: false,
wagerInvalidHelperText: "",
result: Result.Pending, // Reset previous game status to clear the win/lose text UI
});
}
}
// Start game button
startOnClick = (event: React.MouseEvent): void => {
// Protect against scripting...although maybe this would be fun to automate
if (!event.isTrusted) { return; }
if (!this.state.wagerInvalid) {
this.startGame();
}
}
render(): React.ReactNode {
const {
betInput,
playerHand,
dealerHand,
gameInProgress,
result,
wagerInvalid,
wagerInvalidHelperText,
gains,
} = this.state;
// Get the player totals to display.
const playerHandValues = this.getHandDisplayValues(playerHand);
const dealerHandValues = this.getHandDisplayValues(dealerHand);
return (
<div>
{/* Wager input */}
<div>
<MuiTextField
value={betInput}
label={
<>
{"Wager (Max: "}
{Money(MAX_BET)}
{")"}
</>
}
disabled={gameInProgress}
onChange={this.wagerOnChange}
error={wagerInvalid}
helperText={wagerInvalid ? wagerInvalidHelperText : ""}
type="number"
variant="filled"
style={{
width: "200px",
}}
InputProps={{
startAdornment: <InputAdornment position="start" >$</InputAdornment>,
}} />
<p>
{"Total earnings this session: "}
{Money(gains)}
</p>
</div>
{/* Buttons */}
{!gameInProgress ? (
<div>
<MuiButton
color="primary"
onClick={this.startOnClick}
disabled={wagerInvalid || !this.canStartGame()}>
Start
</MuiButton>
</div>
) : (
<div>
<MuiButton color="primary" onClick={this.playerHit} >
Hit
</MuiButton>
<MuiButton color="secondary" onClick={this.playerStay} >
Stay
</MuiButton>
</div>
)}
{/* Main game part. Displays both if the game is in progress OR if there's a result so you can see
* the cards that led to that result. */}
{(gameInProgress || result !== Result.Pending) && (
<div>
<MuiPaper variant="outlined" elevation={2}>
<pre>Player</pre>
{playerHand.cards.map((card, i) => (
<ReactCard card={card} key={i} />
))}
<pre>Value(s): </pre>
{playerHandValues.map((value, i) => (
<pre key={i}>{value}</pre>
))}
</MuiPaper>
<br />
<MuiPaper variant="outlined" elevation={2}>
<pre>Dealer</pre>
{dealerHand.cards.map((card, i) => (
// Hide every card except the first while game is in progress
<ReactCard card={card} hidden={gameInProgress && i !== 0} key={i} />
))}
{!gameInProgress && (
<>
<pre>Value(s): </pre>
{dealerHandValues.map((value, i) => (
<pre key={i}>{value}</pre>
))}
</>
)}
</MuiPaper>
</div>
)}
{/* Results from previous round */}
{result !== Result.Pending && (
<p>
{result}
{this.isPlayerWinResult(result) && (
<>
{" You gained "}
{Money(this.state.bet)}
</>
)}
{result === Result.DealerWon && (
<>
{" You lost "}
{Money(this.state.bet)}
</>
)}
</p>
)}
</div>
)
}
}

@ -0,0 +1,42 @@
// Enum values are lowercased to match css classes
export enum Suit {
Clubs = "clubs",
Diamonds = "diamonds",
Hearts = "hearts",
Spades = "spades",
}
export class Card {
constructor(readonly value: number, readonly suit: Suit) {
if (value < 1 || value > 13) {
throw new Error(`Card instantiated with improper value: ${value}`);
}
}
formatValue(): string {
switch (this.value) {
case 1:
return "A";
case 11:
return "J";
case 12:
return "Q";
case 13:
return "K";
default:
return `${this.value}`;
}
}
isRedSuit(): boolean {
return this.suit === Suit.Hearts || this.suit === Suit.Diamonds;
}
getStringRepresentation(): string {
const value = this.formatValue();
return `${value} of ${this.suit}`;
}
}

@ -0,0 +1,58 @@
import { Card, Suit } from "./Card";
import { shuffle } from "lodash";
export class Deck {
private cards: Card[] = [];
// Support multiple decks
constructor(private numDecks = 1) {
this.reset();
}
shuffle(): void {
this.cards = shuffle(this.cards); // Just use lodash
}
drawCard(): Card {
if (this.cards.length == 0) {
throw new Error("Tried to draw card from empty deck");
}
return this.cards.shift() as Card; // Guaranteed to return a Card since we throw an Error if array is empty
}
// Draws a card, resetting the deck beforehands if the Deck is empty
safeDrawCard(): Card {
if (this.cards.length === 0) {
this.reset();
}
return this.drawCard();
}
// Reset the deck back to the original 52 cards and shuffle it
reset(): void {
this.cards = [];
for (let i = 1; i <= 13; ++i) {
for (let j = 0; j < this.numDecks; ++j) {
this.cards.push(new Card(i, Suit.Clubs));
this.cards.push(new Card(i, Suit.Diamonds));
this.cards.push(new Card(i, Suit.Hearts));
this.cards.push(new Card(i, Suit.Spades));
}
}
this.shuffle();
}
size(): number {
return this.cards.length;
}
isEmpty(): boolean {
return this.cards.length === 0;
}
}

@ -0,0 +1,25 @@
/**
* Represents a Hand of cards.
*
* This class is IMMUTABLE
*/
import { Card } from "./Card";
export class Hand {
constructor(readonly cards: readonly Card[]) {}
addCards(...cards: Card[]): Hand {
return new Hand([ ...this.cards, ...cards ]);
}
removeByIndex(i: number): Hand {
if (i >= this.cards.length) {
throw new Error(`Tried to remove invalid card from Hand by index: ${i}`);
}
return new Hand([ ...this.cards.slice().splice(i, 1) ])
}
}

@ -0,0 +1,40 @@
import React, { FC } from "react";
import { Card, Suit } from "./Card";
type Props = {
card: Card;
hidden?: boolean;
}
export const ReactCard: FC<Props> = ({ card, hidden }) => {
let suit : React.ReactNode;
switch (card.suit) {
case Suit.Clubs:
suit = <span>&#9827;</span>;
break;
case Suit.Diamonds:
suit = <span>&#9830;</span>;
break;
case Suit.Hearts:
suit = <span>&#9829;</span>;
break;
case Suit.Spades:
suit = <span>&#9824;</span>;
break;
default:
throw new Error(`MissingCaseException: ${card.suit}`);
}
return (
<div className={`casino-card ${card.isRedSuit() ? "red" : "black"}`}>
<>
<div className="value">
{hidden ? " - " : card.formatValue()}
</div>
<div className={`suit`}>
{hidden ? " - " : suit}
</div>
</>
</div>
)
}

@ -5,7 +5,7 @@ import { dialogBoxCreate } from "../../utils/DialogBox";
const gainLimit = 10e9; const gainLimit = 10e9;
export class Game<T,U> extends React.Component<T, U> { export class Game<T,U> extends React.Component<T, U> {
win(p: IPlayer, n: number) { win(p: IPlayer, n: number): void{
p.gainMoney(n); p.gainMoney(n);
p.recordMoneySource(n, "casino"); p.recordMoneySource(n, "casino");
} }

@ -6,7 +6,7 @@
import { IMap } from "./types"; import { IMap } from "./types";
export let CONSTANTS: IMap<any> = { export let CONSTANTS: IMap<any> = {
Version: "0.51.5", Version: "0.51.6",
/** Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience /** Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience
* and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then * and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then
@ -228,24 +228,42 @@ export let CONSTANTS: IMap<any> = {
LatestUpdate: LatestUpdate:
` `
v0.51.5 - 2021-04-20 Flags! (hydroflame) v0.51.6 - 2021-04-28 Backdoor! (Community)
------- -------
Netscript Backdoor
* 'flags' is a new function that helps script handle flags. * a new terminal command, backdoor, has been added to help differentiate
This is subject to change if it doesn't meet the need of the players. between the terminal hack command and the netscript hack function. (@dewint)
* 'ps' now returns the pid.
* 'tail' now works with pid as first argument.
* 'tail' hostname defaults to current server. (like the documentation says)
* 'isRunning' hostname defaults to current server.
* 'isRunning' now works with pid as first argument.
Gang Milestones
* Nerfed ascension mechanic once again :( * A new tab under the Help menu has been added to guide players through the
game.
Casino
* Blackjack has been added (@BigD)
Netscript
* 'prompt' now converts input to JSON.
* 'getRunningScript' is a new netscript function that returns a bunch of
data related to a running script.
Coding contracts
* trivial puzzles should no longer appear.
Infiltration
* All numbers are formatted like the rest of the game.
Misc. Misc.
* Souce-File typo fix * Server security is capped at 100.
* Fix 'while you were away' screen. * Added option to quit a job.
* Bladeburner team size can no longer be set to negative amounts. * 'cd' no longer works on unexistent folders.
* cd with no arguments brings you back to top level folder (@Andreas)
* 'softReset' documentation udpated.
* Money tracker now accounts for going to the hospital manually.
* codemirror is now the default editor (for new save files)
* fix typo in dark web help text (@Rodeth)
* so many documentation and typos fixes (@Pimgd)
* A corruption visual effect has been added to location with servers that
have backdoor installed. (@dewint)
` `
} }

@ -210,9 +210,13 @@ export class Product {
} }
//Delete unneeded variables //Delete unneeded variables
// @ts-ignore
delete this.prog; delete this.prog;
// @ts-ignore
delete this.createCity; delete this.createCity;
// @ts-ignore
delete this.designCost; delete this.designCost;
// @ts-ignore
delete this.advCost; delete this.advCost;
} }

@ -7,6 +7,7 @@ import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners";
import { getRandomInt } from "../utils/helpers/getRandomInt"; import { getRandomInt } from "../utils/helpers/getRandomInt";
import { infiltrationBoxCreate } from "../utils/InfiltrationBox"; import { infiltrationBoxCreate } from "../utils/InfiltrationBox";
import { formatNumber } from "../utils/StringHelperFunctions"; import { formatNumber } from "../utils/StringHelperFunctions";
import { numeralWrapper } from "./ui/numeralFormat";
let InfiltrationScenarios = { let InfiltrationScenarios = {
Guards: "You see an armed security guard patrolling the area.", Guards: "You see an armed security guard patrolling the area.",
@ -451,9 +452,9 @@ function endInfiltrationLevel(inst) {
BitNodeMultipliers.InfiltrationMoney; BitNodeMultipliers.InfiltrationMoney;
inst.secretsStolen.push(baseSecretValue); inst.secretsStolen.push(baseSecretValue);
dialogBoxCreate("You found and stole a set of classified documents from the company. " + dialogBoxCreate("You found and stole a set of classified documents from the company. " +
"These classified secrets could probably be sold for money (<span class='money-gold'>$" + "These classified secrets could probably be sold for money (<span class='money-gold'>" +
formatNumber(secretMoneyValue, 2) + "</span>), or they " + numeralWrapper.formatMoney(secretMoneyValue) + "</span>), or they " +
"could be given to factions for reputation (<span class='light-yellow'>" + formatNumber(secretValue, 3) + " rep</span>)"); "could be given to factions for reputation (<span class='light-yellow'>" + numeralWrapper.formatReputation(secretValue) + " rep</span>)");
} }
// Increase security level based on difficulty // Increase security level based on difficulty
@ -495,16 +496,16 @@ function updateInfiltrationLevelText(inst) {
document.getElementById("infiltration-level-text").innerHTML = document.getElementById("infiltration-level-text").innerHTML =
"Facility name:    " + inst.companyName + "<br>" + "Facility name:    " + inst.companyName + "<br>" +
"Clearance Level:  " + inst.clearanceLevel + "<br>" + "Clearance Level:  " + inst.clearanceLevel + "<br>" +
"Security Level:   " + formatNumber(inst.securityLevel, 3) + "<br><br>" + "Security Level:   " + numeralWrapper.formatInfiltrationSecurity(inst.securityLevel) + "<br><br>" +
"Total value of stolen secrets<br>" + "Total value of stolen secrets<br>" +
"Reputation:       <span class='light-yellow'>" + formatNumber(totalValue, 3) + "</span><br>" + "Reputation:       <span class='light-yellow'>" + numeralWrapper.formatReputation(totalValue, 3) + "</span><br>" +
"Money:           <span class='money-gold'>$" + formatNumber(totalMoneyValue, 2) + "</span><br><br>" + "Money:           <span class='money-gold'>" + numeralWrapper.formatMoney(totalMoneyValue, 2) + "</span><br><br>" +
"Hack exp gained:  " + formatNumber(inst.calcGainedHackingExp(), 3) + "<br>" + "Hack exp gained:  " + numeralWrapper.formatExp(inst.calcGainedHackingExp(), 3) + "<br>" +
"Str exp gained:   " + formatNumber(inst.calcGainedStrengthExp(), 3) + "<br>" + "Str exp gained:   " + numeralWrapper.formatExp(inst.calcGainedStrengthExp(), 3) + "<br>" +
"Def exp gained:   " + formatNumber(inst.calcGainedDefenseExp(), 3) + "<br>" + "Def exp gained:   " + numeralWrapper.formatExp(inst.calcGainedDefenseExp(), 3) + "<br>" +
"Dex exp gained:   " + formatNumber(inst.calcGainedDexterityExp(), 3) + "<br>" + "Dex exp gained:   " + numeralWrapper.formatExp(inst.calcGainedDexterityExp(), 3) + "<br>" +
"Agi exp gained:   " + formatNumber(inst.calcGainedAgilityExp(), 3) + "<br>" + "Agi exp gained:   " + numeralWrapper.formatExp(inst.calcGainedAgilityExp(), 3) + "<br>" +
"Cha exp gained:   " + formatNumber(inst.calcGainedCharismaExp(), 3); "Cha exp gained:   " + numeralWrapper.formatExp(inst.calcGainedCharismaExp(), 3);
/* eslint-enable no-irregular-whitespace */ /* eslint-enable no-irregular-whitespace */
} }
@ -524,7 +525,7 @@ function updateInfiltrationButtons(inst, scenario) {
"<span class='tooltiptext'>" + "<span class='tooltiptext'>" +
"Attempt to escape the facility with the classified secrets and " + "Attempt to escape the facility with the classified secrets and " +
"documents you have stolen. You have a " + "documents you have stolen. You have a " +
formatNumber(escapeChance*100, 2) + "% chance of success. If you fail, " + numeralWrapper.formatPercentage(escapeChance, 2) + " chance of success. If you fail, " +
"the security level will increase by 5%.</span>"; "the security level will increase by 5%.</span>";
switch(scenario) { switch(scenario) {
@ -532,55 +533,55 @@ function updateInfiltrationButtons(inst, scenario) {
document.getElementById("infiltration-pickdoor").innerHTML = "Lockpick" + document.getElementById("infiltration-pickdoor").innerHTML = "Lockpick" +
"<span class='tooltiptext'>" + "<span class='tooltiptext'>" +
"Attempt to pick the locked door. You have a " + "Attempt to pick the locked door. You have a " +
formatNumber(lockpickChance*100, 2) + "% chance of success. " + numeralWrapper.formatPercentage(lockpickChance, 2) + " chance of success. " +
"If you succeed, the security level will increased by 1%. If you fail, the " + "If you succeed, the security level will increased by 1%. If you fail, the " +
"security level will increase by 3%.</span>"; "security level will increase by 3%.</span>";
case InfiltrationScenarios.TechOnly: case InfiltrationScenarios.TechOnly:
document.getElementById("infiltration-hacksecurity").innerHTML = "Hack" + document.getElementById("infiltration-hacksecurity").innerHTML = "Hack" +
"<span class='tooltiptext'>" + "<span class='tooltiptext'>" +
"Attempt to hack and disable the security system. You have a " + "Attempt to hack and disable the security system. You have a " +
formatNumber(hackChance*100, 2) + "% chance of success. " + numeralWrapper.formatPercentage(hackChance, 2) + " chance of success. " +
"If you succeed, the security level will increase by 3%. If you fail, " + "If you succeed, the security level will increase by 3%. If you fail, " +
"the security level will increase by 5%.</span>"; "the security level will increase by 5%.</span>";
document.getElementById("infiltration-destroysecurity").innerHTML = "Destroy security" + document.getElementById("infiltration-destroysecurity").innerHTML = "Destroy security" +
"<span class='tooltiptext'>" + "<span class='tooltiptext'>" +
"Attempt to violently destroy the security system. You have a " + "Attempt to violently destroy the security system. You have a " +
formatNumber(destroySecurityChance*100, 2) + "% chance of success. " + numeralWrapper.formatPercentage(destroySecurityChance, 2) + " chance of success. " +
"If you succeed, the security level will increase by 5%. If you fail, the " + "If you succeed, the security level will increase by 5%. If you fail, the " +
"security level will increase by 10%. </span>"; "security level will increase by 10%. </span>";
document.getElementById("infiltration-sneak").innerHTML = "Sneak" + document.getElementById("infiltration-sneak").innerHTML = "Sneak" +
"<span class='tooltiptext'>" + "<span class='tooltiptext'>" +
"Attempt to sneak past the security system. You have a " + "Attempt to sneak past the security system. You have a " +
formatNumber(sneakChance*100, 2) + "% chance of success. " + numeralWrapper.formatPercentage(sneakChance, 2) + " chance of success. " +
"If you fail, the security level will increase by 8%. </span>"; "If you fail, the security level will increase by 8%. </span>";
break; break;
case InfiltrationScenarios.Bots: case InfiltrationScenarios.Bots:
document.getElementById("infiltration-kill").innerHTML = "Destroy bots" + document.getElementById("infiltration-kill").innerHTML = "Destroy bots" +
"<span class='tooltiptext'>" + "<span class='tooltiptext'>" +
"Attempt to destroy the security bots through combat. You have a " + "Attempt to destroy the security bots through combat. You have a " +
formatNumber(killChance*100, 2) + "% chance of success. " + numeralWrapper.formatPercentage(killChance, 2) + " chance of success. " +
"If you succeed, the security level will increase by 5%. If you fail, " + "If you succeed, the security level will increase by 5%. If you fail, " +
"the security level will increase by 10%. </span>"; "the security level will increase by 10%. </span>";
document.getElementById("infiltration-assassinate").innerHTML = "Assassinate bots" + document.getElementById("infiltration-assassinate").innerHTML = "Assassinate bots" +
"<span class='tooltiptext'>" + "<span class='tooltiptext'>" +
"Attempt to stealthily destroy the security bots through assassination. You have a " + "Attempt to stealthily destroy the security bots through assassination. You have a " +
formatNumber(assassinateChance*100, 2) + "% chance of success. " + numeralWrapper.formatPercentage(assassinateChance, 2) + " chance of success. " +
"If you fail, the security level will increase by 10%. </span>"; "If you fail, the security level will increase by 10%. </span>";
document.getElementById("infiltration-hacksecurity").innerHTML = "Hack bots" + document.getElementById("infiltration-hacksecurity").innerHTML = "Hack bots" +
"<span class='tooltiptext'>" + "<span class='tooltiptext'>" +
"Attempt to disable the security bots by hacking them. You have a " + "Attempt to disable the security bots by hacking them. You have a " +
formatNumber(hackChance*100, 2) + "% chance of success. " + numeralWrapper.formatPercentage(hackChance, 2) + " chance of success. " +
"If you succeed, the security level will increase by 3%. If you fail, " + "If you succeed, the security level will increase by 3%. If you fail, " +
"the security level will increase by 5%. </span>"; "the security level will increase by 5%. </span>";
document.getElementById("infiltration-sneak").innerHTML = "Sneak" + document.getElementById("infiltration-sneak").innerHTML = "Sneak" +
"<span class='tooltiptext'>" + "<span class='tooltiptext'>" +
"Attempt to sneak past the security bots. You have a " + "Attempt to sneak past the security bots. You have a " +
formatNumber(sneakChance*100, 2) + "% chance of success. " + numeralWrapper.formatPercentage(sneakChance, 2) + " chance of success. " +
"If you fail, the security level will increase by 8%. </span>"; "If you fail, the security level will increase by 8%. </span>";
break; break;
@ -589,39 +590,39 @@ function updateInfiltrationButtons(inst, scenario) {
document.getElementById("infiltration-kill").innerHTML = "Kill" + document.getElementById("infiltration-kill").innerHTML = "Kill" +
"<span class='tooltiptext'>" + "<span class='tooltiptext'>" +
"Attempt to kill the security guard. You have a " + "Attempt to kill the security guard. You have a " +
formatNumber(killChance*100, 2) + "% chance of success. " + numeralWrapper.formatPercentage(killChance, 2) + " chance of success. " +
"If you succeed, the security level will increase by 5%. If you fail, " + "If you succeed, the security level will increase by 5%. If you fail, " +
"the security level will decrease by 10%. </span>"; "the security level will decrease by 10%. </span>";
document.getElementById("infiltration-knockout").innerHTML = "Knockout" + document.getElementById("infiltration-knockout").innerHTML = "Knockout" +
"<span class='tooltiptext'>" + "<span class='tooltiptext'>" +
"Attempt to knockout the security guard. You have a " + "Attempt to knockout the security guard. You have a " +
formatNumber(knockoutChance*100, 2) + "% chance of success. " + numeralWrapper.formatPercentage(knockoutChance, 2) + " chance of success. " +
"If you succeed, the security level will increase by 3%. If you fail, the " + "If you succeed, the security level will increase by 3%. If you fail, the " +
"security level will increase by 10%. </span>"; "security level will increase by 10%. </span>";
document.getElementById("infiltration-stealthknockout").innerHTML = "Stealth Knockout" + document.getElementById("infiltration-stealthknockout").innerHTML = "Stealth Knockout" +
"<span class='tooltiptext'>" + "<span class='tooltiptext'>" +
"Attempt to stealthily knockout the security guard. You have a " + "Attempt to stealthily knockout the security guard. You have a " +
formatNumber(stealthKnockoutChance*100, 2) + "% chance of success. " + numeralWrapper.formatPercentage(stealthKnockoutChance, 2) + " chance of success. " +
"If you fail, the security level will increase by 10%. </span>"; "If you fail, the security level will increase by 10%. </span>";
document.getElementById("infiltration-assassinate").innerHTML = "Assassinate" + document.getElementById("infiltration-assassinate").innerHTML = "Assassinate" +
"<span class='tooltiptext'>" + "<span class='tooltiptext'>" +
"Attempt to assassinate the security guard. You have a " + "Attempt to assassinate the security guard. You have a " +
formatNumber(assassinateChance*100, 2) + "% chance of success. " + numeralWrapper.formatPercentage(assassinateChance, 2) + " chance of success. " +
"If you fail, the security level will increase by 5%. </span>"; "If you fail, the security level will increase by 5%. </span>";
document.getElementById("infiltration-sneak").innerHTML = "Sneak" + document.getElementById("infiltration-sneak").innerHTML = "Sneak" +
"<span class='tooltiptext'>" + "<span class='tooltiptext'>" +
"Attempt to sneak past the security guard. You have a " + "Attempt to sneak past the security guard. You have a " +
formatNumber(sneakChance*100, 2) + "% chance of success. " + numeralWrapper.formatPercentage(sneakChance, 2) + " chance of success. " +
"If you fail, the security level will increase by 8%. </span>"; "If you fail, the security level will increase by 8%. </span>";
document.getElementById("infiltration-bribe").innerHTML = "Bribe" + document.getElementById("infiltration-bribe").innerHTML = "Bribe" +
"<span class='tooltiptext'>" + "<span class='tooltiptext'>" +
"Attempt to bribe the security guard. You have a " + "Attempt to bribe the security guard. You have a " +
formatNumber(bribeChance*100, 2) + "% chance of success. " + numeralWrapper.formatPercentage(bribeChance, 2) + " chance of success. " +
"If you fail, the security level will increase by 15%. </span>"; "If you fail, the security level will increase by 15%. </span>";
break; break;
} }

@ -9,4 +9,4 @@ export enum CityName {
NewTokyo = "New Tokyo", NewTokyo = "New Tokyo",
Sector12 = "Sector-12", Sector12 = "Sector-12",
Volhaven = "Volhaven", Volhaven = "Volhaven",
}; }

@ -4,25 +4,28 @@
* This subcomponent renders all of the buttons for training at the gym * This subcomponent renders all of the buttons for training at the gym
*/ */
import * as React from "react"; import * as React from "react";
import { Blackjack } from "../../Casino/Blackjack";
import { CoinFlip } from "../../Casino/CoinFlip";
import { Roulette } from "../../Casino/Roulette";
import { SlotMachine } from "../../Casino/SlotMachine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { StdButton } from "../../ui/React/StdButton";
import { Location } from "../Location";
import { CONSTANTS } from "../../Constants"; enum GameType {
import { IPlayer } from "../../PersonObjects/IPlayer"; None = 'none',
Coin = 'coin',
import { numeralWrapper } from "../../ui/numeralFormat"; Slots = 'slots',
import { StdButton } from "../../ui/React/StdButton"; Roulette = 'roulette',
import { Money } from "../../ui/React/Money"; Blackjack = 'blackjack',
import { SlotMachine } from "../../Casino/SlotMachine"; }
import { CoinFlip } from "../../Casino/CoinFlip";
import { Roulette } from "../../Casino/Roulette";
type IProps = { type IProps = {
p: IPlayer; p: IPlayer;
} }
type IState = { type IState = {
game: string; game: GameType;
} }
export class CasinoLocation extends React.Component<IProps, IState> { export class CasinoLocation extends React.Component<IProps, IState> {
@ -30,57 +33,70 @@ export class CasinoLocation extends React.Component<IProps, IState> {
super(props); super(props);
this.state = { this.state = {
game: '', game: GameType.None,
} }
this.updateGame = this.updateGame.bind(this); this.updateGame = this.updateGame.bind(this);
} }
updateGame(game: string) { updateGame(game: GameType): void {
this.setState({ this.setState({
game: game, game,
}); });
} }
renderGames() { renderGames(): React.ReactNode {
return (<> return (<>
<StdButton <StdButton
onClick={() => this.updateGame('coin')} onClick={() => this.updateGame(GameType.Coin)}
text={"Play coin flip"} text={"Play coin flip"}
/><br /> /><br />
<StdButton <StdButton
onClick={() => this.updateGame('slots')} onClick={() => this.updateGame(GameType.Slots)}
text={"Play slots"} text={"Play slots"}
/><br /> /><br />
<StdButton <StdButton
onClick={() => this.updateGame('roulette')} onClick={() => this.updateGame(GameType.Roulette)}
text={"Play roulette"} text={"Play roulette"}
/><br />
<StdButton
onClick={() => this.updateGame(GameType.Blackjack)}
text={"Play blackjack"}
/> />
</>) </>)
} }
renderGame() { renderGame(): React.ReactNode {
let elem; let elem = null;
switch(this.state.game) { switch(this.state.game) {
case 'coin': case GameType.Coin:
elem = <CoinFlip p={this.props.p} /> elem = <CoinFlip p={this.props.p} />
break; break;
case 'slots': case GameType.Slots:
elem = <SlotMachine p={this.props.p} /> elem = <SlotMachine p={this.props.p} />
break; break;
case 'roulette': case GameType.Roulette:
elem = <Roulette p={this.props.p} /> elem = <Roulette p={this.props.p} />
break; break;
case GameType.Blackjack:
elem = <Blackjack p={this.props.p} />
break;
case GameType.None:
break;
default:
throw new Error(`MissingCaseException: ${this.state.game}`);
} }
return (<> return (
<StdButton onClick={() => this.updateGame('')} text={"Stop playing"} /> <>
{elem} <StdButton onClick={() => this.updateGame(GameType.None)} text={"Stop playing"} />
</>) {elem}
</>
)
} }
render() { render(): React.ReactNode {
if(!this.state.game) { if(this.state.game === GameType.None) {
return this.renderGames(); return this.renderGames();
} else { } else {
return this.renderGame(); return this.renderGame();

@ -19,9 +19,9 @@ type IProps = {
export class LocationCity extends React.Component<IProps, any> { export class LocationCity extends React.Component<IProps, any> {
asciiCity() { asciiCity() {
const thiscity = this; const thiscity = this;
const topprop = this.props const topprop = this.props;
function LocationLetter(location: string) { function LocationLetter(location: LocationName) {
if (location) if (location)
return <span key={location} className='tooltip' style={{color: 'blue', whiteSpace: 'nowrap', margin: '0px', padding: '0px', cursor: 'pointer'}} onClick={topprop.enterLocation.bind(thiscity, location)}> return <span key={location} className='tooltip' style={{color: 'blue', whiteSpace: 'nowrap', margin: '0px', padding: '0px', cursor: 'pointer'}} onClick={topprop.enterLocation.bind(thiscity, location)}>
X X

@ -26,6 +26,13 @@ import { StdButton } from "../../ui/React/StdButton";
import { Reputation } from "../../ui/React/Reputation"; import { Reputation } from "../../ui/React/Reputation";
import { Favor } from "../../ui/React/Favor"; import { Favor } from "../../ui/React/Favor";
import {
yesNoBoxGetYesButton,
yesNoBoxGetNoButton,
yesNoBoxClose,
yesNoBoxCreate
} from "../../../utils/YesNoBox";
type IProps = { type IProps = {
engine: IEngine; engine: IEngine;
locName: LocationName; locName: LocationName;
@ -73,6 +80,7 @@ export class CompanyLocation extends React.Component<IProps, IState> {
this.btnStyle = { display: "block" }; this.btnStyle = { display: "block" };
this.quit = this.quit.bind(this);
this.applyForAgentJob = this.applyForAgentJob.bind(this); this.applyForAgentJob = this.applyForAgentJob.bind(this);
this.applyForBusinessConsultantJob = this.applyForBusinessConsultantJob.bind(this); this.applyForBusinessConsultantJob = this.applyForBusinessConsultantJob.bind(this);
this.applyForBusinessJob = this.applyForBusinessJob.bind(this); this.applyForBusinessJob = this.applyForBusinessJob.bind(this);
@ -207,6 +215,26 @@ export class CompanyLocation extends React.Component<IProps, IState> {
} }
} }
quit(e: React.MouseEvent<HTMLElement>) {
if (!e.isTrusted) { return false; }
var yesBtn = yesNoBoxGetYesButton();
var noBtn = yesNoBoxGetNoButton();
if (yesBtn == null || noBtn == null) { return; }
yesBtn.innerHTML = "Quit job";
noBtn.innerHTML = "Cancel";
yesBtn.addEventListener("click", () => {
this.props.p.quitJob(this.props.locName);
this.checkIfEmployedHere(true);
yesNoBoxClose();
});
noBtn.addEventListener("click", () => {
yesNoBoxClose();
});
yesNoBoxCreate(<>Would you like to quit your job at {this.company.name}?</>);
}
render() { render() {
const isEmployedHere = this.jobTitle != null; const isEmployedHere = this.jobTitle != null;
const favorGain = this.company.getFavorGain(); const favorGain = this.company.getFavorGain();
@ -236,10 +264,12 @@ export class CompanyLocation extends React.Component<IProps, IState> {
</p><br /> </p><br />
<br /><p style={blockStyleMarkup}>-------------------------</p><br /> <br /><p style={blockStyleMarkup}>-------------------------</p><br />
<StdButton <StdButton
id={"foo-work-button-id"}
onClick={this.work} onClick={this.work}
style={this.btnStyle}
text={"Work"} text={"Work"}
/>&nbsp;&nbsp;&nbsp;&nbsp;
<StdButton
onClick={this.quit}
text={"Quit"}
/> />
</div> </div>
} }

@ -6,24 +6,29 @@
*/ */
import * as React from "react"; import * as React from "react";
import { CompanyLocation } from "./CompanyLocation"; import { CompanyLocation } from "./CompanyLocation";
import { GymLocation } from "./GymLocation"; import { GymLocation } from "./GymLocation";
import { HospitalLocation } from "./HospitalLocation"; import { HospitalLocation } from "./HospitalLocation";
import { SlumsLocation } from "./SlumsLocation"; import { SlumsLocation } from "./SlumsLocation";
import { SpecialLocation } from "./SpecialLocation"; import { SpecialLocation } from "./SpecialLocation";
import { TechVendorLocation } from "./TechVendorLocation"; import { TechVendorLocation } from "./TechVendorLocation";
import { TravelAgencyLocation } from "./TravelAgencyLocation"; import { TravelAgencyLocation } from "./TravelAgencyLocation";
import { UniversityLocation } from "./UniversityLocation"; import { UniversityLocation } from "./UniversityLocation";
import { CasinoLocation } from "./CasinoLocation"; import { CasinoLocation } from "./CasinoLocation";
import { Location } from "../Location"; import { Location } from "../Location";
import { LocationType } from "../LocationTypeEnum"; import { LocationType } from "../LocationTypeEnum";
import { CityName } from "../data/CityNames"; import { CityName } from "../data/CityNames";
import { IEngine } from "../../IEngine"; import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { Settings } from "../../Settings/Settings";
import { StdButton } from "../../ui/React/StdButton"; import { SpecialServerIps } from "../../Server/SpecialServerIps";
import { getServer, isBackdoorInstalled } from "../../Server/ServerHelpers";
import { StdButton } from "../../ui/React/StdButton";
import { CorruptableText } from "../../ui/React/CorruptableText";
type IProps = { type IProps = {
engine: IEngine; engine: IEngine;
@ -146,11 +151,19 @@ export class GenericLocation extends React.Component<IProps, any> {
render() { render() {
const locContent: React.ReactNode[] = this.getLocationSpecificContent(); const locContent: React.ReactNode[] = this.getLocationSpecificContent();
const ip = SpecialServerIps.getIp(this.props.loc.name);
const server = getServer(ip);
const backdoorInstalled = server !== null && isBackdoorInstalled(server);
return ( return (
<div> <div>
<StdButton onClick={this.props.returnToCity} style={this.btnStyle} text={"Return to World"} /> <StdButton onClick={this.props.returnToCity} style={this.btnStyle} text={"Return to World"} />
<h1>{this.props.loc.name}</h1> <h1>
{backdoorInstalled && !Settings.DisableTextEffects
? <CorruptableText content={this.props.loc.name}/>
: this.props.loc.name
}
</h1>
{locContent} {locContent}
</div> </div>
) )

@ -45,8 +45,8 @@ export class GymLocation extends React.Component<IProps, any> {
const ip = SpecialServerIps.getIp(this.props.loc.name); const ip = SpecialServerIps.getIp(this.props.loc.name);
console.log(`ip: ${ip}`); console.log(`ip: ${ip}`);
const server = getServer(ip); const server = getServer(ip);
if(server == null || !server.hasOwnProperty('manuallyHacked')) return this.props.loc.costMult; if(server == null || !server.hasOwnProperty('backdoorInstalled')) return this.props.loc.costMult;
const discount = (server as Server).manuallyHacked? 0.9 : 1; const discount = (server as Server).backdoorInstalled? 0.9 : 1;
return this.props.loc.costMult * discount; return this.props.loc.costMult * discount;
} }

@ -55,6 +55,7 @@ export class HospitalLocation extends React.Component<IProps, IState> {
const cost = this.getCost(); const cost = this.getCost();
this.props.p.loseMoney(cost); this.props.p.loseMoney(cost);
this.props.p.hp = this.props.p.max_hp; this.props.p.hp = this.props.p.max_hp;
this.props.p.recordMoneySource(-1 * cost, 'hospitalization');
// This just forces a re-render to update the cost // This just forces a re-render to update the cost
this.setState({ this.setState({

@ -82,7 +82,7 @@ export class TravelAgencyLocation extends React.Component<IProps, any> {
listWorldMap() { listWorldMap() {
const travelBtns: React.ReactNode[] = []; const travelBtns: React.ReactNode[] = [];
for (const key in CityName) { for (const key in CityName) {
const city = CityName[key]; const city: CityName = (CityName as any)[key];
// Skip current city // Skip current city
if (city === this.props.p.city) { continue; } if (city === this.props.p.city) { continue; }

@ -48,8 +48,8 @@ export class UniversityLocation extends React.Component<IProps, any> {
const ip = SpecialServerIps.getIp(this.props.loc.name); const ip = SpecialServerIps.getIp(this.props.loc.name);
console.log(`ip: ${ip}`); console.log(`ip: ${ip}`);
const server = getServer(ip); const server = getServer(ip);
if(server == null || !server.hasOwnProperty('manuallyHacked')) return this.props.loc.costMult; if(server == null || !server.hasOwnProperty('backdoorInstalled')) return this.props.loc.costMult;
const discount = (server as Server).manuallyHacked? 0.9 : 1; const discount = (server as Server).backdoorInstalled? 0.9 : 1;
return this.props.loc.costMult * discount; return this.props.loc.costMult * discount;
} }

@ -153,7 +153,7 @@ function initMessages() {
"We've been watching you. Your skills are very impressive. But you're wasting " + "We've been watching you. Your skills are very impressive. But you're wasting " +
"your talents. If you join us, you can put your skills to good use and change " + "your talents. If you join us, you can put your skills to good use and change " +
"the world for the better. If you join us, we can unlock your full potential. <br><br>" + "the world for the better. If you join us, we can unlock your full potential. <br><br>" +
"But first, you must pass our test. Find and hack our server using the Terminal. <br><br>" + "But first, you must pass our test. Find and install the backdoor on our server. <br><br>" +
"-CyberSec")); "-CyberSec"));
AddToAllMessages(new Message(MessageFilenames.NiteSecTest, AddToAllMessages(new Message(MessageFilenames.NiteSecTest,
"People say that the corrupted governments and corporations rule the world. " + "People say that the corrupted governments and corporations rule the world. " +
@ -161,7 +161,7 @@ function initMessages() {
"like us. Because they can't hide from us. Because they can't fight shadows " + "like us. Because they can't hide from us. Because they can't fight shadows " +
"and ideas with bullets. <br><br>" + "and ideas with bullets. <br><br>" +
"Join us, and people will fear you, too. <br><br>" + "Join us, and people will fear you, too. <br><br>" +
"Find and hack our hidden server using the Terminal. Then, we will contact you again." + "Find and install the backdoor on our server. Then, we will contact you again." +
"<br><br>-NiteSec")); "<br><br>-NiteSec"));
AddToAllMessages(new Message(MessageFilenames.BitRunnersTest, AddToAllMessages(new Message(MessageFilenames.BitRunnersTest,
"We know what you are doing. We know what drives you. We know " + "We know what you are doing. We know what drives you. We know " +

@ -0,0 +1,6 @@
import { IPlayer } from "../PersonObjects/IPlayer";
export type Milestone = {
title: string;
fulfilled: (p: IPlayer) => boolean;
}

@ -0,0 +1,29 @@
import { Page, routing } from ".././ui/navigationTracking";
import { Root } from "./ui/Root";
import { Player } from "../Player";
import * as React from "react";
import * as ReactDOM from "react-dom";
let milestonesContainer: HTMLElement | null = null;
(function(){
function setContainer() {
milestonesContainer = document.getElementById("milestones-container");
document.removeEventListener("DOMContentLoaded", setContainer);
}
document.addEventListener("DOMContentLoaded", setContainer);
})();
export function displayMilestonesContent() {
if (!routing.isOn(Page.Milestones)) {
return;
}
if (milestonesContainer instanceof HTMLElement) {
ReactDOM.render(
<Root player={Player}/>,
milestonesContainer
);
}
}

@ -0,0 +1,99 @@
import { Milestone } from "./Milestone";
import { IMap } from "../types";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Factions } from "../Faction/Factions";
import { Faction } from "../Faction/Faction";
import { GetServerByHostname } from "../Server/ServerHelpers";
function allFactionAugs(p: IPlayer, f: Faction): boolean {
const factionAugs = f.augmentations.slice().filter((aug)=> "NeuroFlux Governor" !== aug);
for(const factionAug of factionAugs) {
if(!p.augmentations.some(aug => {return aug.name == factionAug})) return false;
}
return true;
}
export const Milestones: Milestone[] = [
{
title: "Gain root access on CSEC",
fulfilled: (p: IPlayer) => {
const server = GetServerByHostname("CSEC");
if(!server || !server.hasOwnProperty('hasAdminRights')) return false;
return (server as any).hasAdminRights;
},
},
{
title: "Install the backdoor on CSEC",
fulfilled: (p: IPlayer) => {
const server = GetServerByHostname("CSEC");
if(!server || !server.hasOwnProperty('backdoorInstalled')) return false;
return (server as any).backdoorInstalled;
},
},
{
title: "Join the faction hinted at in j1.msg",
fulfilled: (p: IPlayer) => {
return p.factions.includes("CyberSec");
},
},
{
title: "Install all the Augmentations from CSEC",
fulfilled: (p: IPlayer) => {
return allFactionAugs(p, Factions["CyberSec"]);
},
},
{
title: "Join the faction hinted at in j2.msg",
fulfilled: (p: IPlayer) => {
return p.factions.includes("NiteSec");
},
},
{
title: "Install all the Augmentations from NiteSec",
fulfilled: (p: IPlayer) => {
return allFactionAugs(p, Factions["NiteSec"]);
},
},
{
title: "Join the faction hinted at in j3.msg",
fulfilled: (p: IPlayer) => {
return p.factions.includes("The Black Hand");
},
},
{
title: "Install all the Augmentations from The Black Hand",
fulfilled: (p: IPlayer) => {
return allFactionAugs(p, Factions["The Black Hand"]);
},
},
{
title: "Join the faction hinted at in j4.msg",
fulfilled: (p: IPlayer) => {
return p.factions.includes("BitRunners");
},
},
{
title: "Install all the Augmentations from BitRunners",
fulfilled: (p: IPlayer) => {
return allFactionAugs(p, Factions["BitRunners"]);
},
},
{
title: "Join the final faction",
fulfilled: (p: IPlayer) => {
return p.factions.includes("Daedalus");
},
},
{
title: "Install the special Augmentation from Daedalus",
fulfilled: (p: IPlayer) => {
return p.augmentations.some(aug => aug.name == "The Red Pill")
},
},
{
title: "Install the final backdoor and free yourself.",
fulfilled: () => {
return false;
},
},
];

11
src/Milestones/Quest.ts Normal file

@ -0,0 +1,11 @@
import { Milestone } from "./Milestone";
export class Quest {
title: string;
milestones: Milestone[];
constructor(title: string, milestones: Milestone[]) {
this.title = title;
this.milestones = milestones;
}
}

@ -0,0 +1,37 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Milestones } from "../Milestones";
import { Milestone } from "../Milestone";
import * as React from "react";
interface IProps {
player: IPlayer;
}
function highestMilestone(p: IPlayer, milestones: Milestone[]): number {
let n = -1;
for(let i = 0; i < milestones.length; i++) {
if(milestones[i].fulfilled(p)) n = i;
}
return n;
}
export function Root(props: IProps) {
const n = highestMilestone(props.player, Milestones);
const milestones = Milestones.map((milestone: Milestone, i: number) => {
if (i<=n+1) {
return (<ul key={i}>
<p>[{milestone.fulfilled(props.player)?"x":" "}] {milestone.title}</p>
</ul>)
}
})
return (<>
<h1>Milestones</h1>
<p>Milestones don't reward you for completing them. They are here to guide you if you're lost. They will reset when you install Augmentations.</p><br />
<h2>Completing fl1ght.exe</h2>
<li>
{milestones}
</li>
</>);
}

@ -39,6 +39,7 @@ export const RamCostConstants: IMap<number> = {
ScriptReadWriteRamCost: 1.0, ScriptReadWriteRamCost: 1.0,
ScriptArbScriptRamCost: 1.0, ScriptArbScriptRamCost: 1.0,
ScriptGetScriptRamCost: 0.1, ScriptGetScriptRamCost: 0.1,
ScriptGetRunningScriptRamCost: 0.3,
ScriptGetHackTimeRamCost: 0.05, ScriptGetHackTimeRamCost: 0.05,
ScriptGetFavorToDonate: 0.10, ScriptGetFavorToDonate: 0.10,
ScriptCodingContractBaseRamCost: 10, ScriptCodingContractBaseRamCost: 10,
@ -165,6 +166,7 @@ export const RamCosts: IMap<any> = {
getWeakenTime: () => RamCostConstants.ScriptGetHackTimeRamCost, getWeakenTime: () => RamCostConstants.ScriptGetHackTimeRamCost,
getScriptIncome: () => RamCostConstants.ScriptGetScriptRamCost, getScriptIncome: () => RamCostConstants.ScriptGetScriptRamCost,
getScriptExpGain: () => RamCostConstants.ScriptGetScriptRamCost, getScriptExpGain: () => RamCostConstants.ScriptGetScriptRamCost,
getRunningScript: () => RamCostConstants.ScriptGetRunningScriptRamCost,
nFormat: () => 0, nFormat: () => 0,
getTimeSinceLastAug: () => RamCostConstants.ScriptGetHackTimeRamCost, getTimeSinceLastAug: () => RamCostConstants.ScriptGetHackTimeRamCost,
prompt: () => 0, prompt: () => 0,

@ -266,6 +266,7 @@ const defaultInterpreter = new Interpreter('', function(){});
// the acorn interpreter has a bug where it doesn't convert arrays correctly. // the acorn interpreter has a bug where it doesn't convert arrays correctly.
// so we have to more or less copy it here. // so we have to more or less copy it here.
function toNative(pseudoObj) { function toNative(pseudoObj) {
if(pseudoObj == null) return null;
if(!pseudoObj.hasOwnProperty('properties') || if(!pseudoObj.hasOwnProperty('properties') ||
!pseudoObj.hasOwnProperty('getter') || !pseudoObj.hasOwnProperty('getter') ||
!pseudoObj.hasOwnProperty('setter') || !pseudoObj.hasOwnProperty('setter') ||
@ -366,13 +367,17 @@ function NetscriptFunctions(workerScript) {
return workerScript.scriptRef; return workerScript.scriptRef;
} }
const getRunningScriptByPid = function(pid, ip, callingFnName) { const getRunningScriptByPid = function(pid, callingFnName) {
if (typeof callingFnName !== "string" || callingFnName === "") { if (typeof callingFnName !== "string" || callingFnName === "") {
callingFnName = "getRunningScriptgetRunningScriptByPid"; callingFnName = "getRunningScriptgetRunningScriptByPid";
} }
const server = safeGetServer(ip, callingFnName);
for(const name of Object.keys(AllServers)) {
return findRunningScriptByPid(pid, server); const server = AllServers[name];
const runningScript = findRunningScriptByPid(pid, server);
if (runningScript) return runningScript;
}
return null;
} }
/** /**
@ -718,7 +723,7 @@ function NetscriptFunctions(workerScript) {
influenceStockThroughServerHack(server, moneyGained); influenceStockThroughServerHack(server, moneyGained);
} }
if(manual) { if(manual) {
server.manuallyHacked = true; server.backdoorInstalled = true;
} }
return Promise.resolve(moneyGained); return Promise.resolve(moneyGained);
} else { } else {
@ -1052,8 +1057,10 @@ function NetscriptFunctions(workerScript) {
}, },
tail: function(fn, ip=workerScript.serverIp, ...scriptArgs) { tail: function(fn, ip=workerScript.serverIp, ...scriptArgs) {
let runningScriptObj; let runningScriptObj;
if(typeof fn === 'number') { if(arguments.length === 0) {
runningScriptObj = getRunningScriptByPid(fn, ip, 'tail'); runningScriptObj = workerScript.scriptRef;
} else if(typeof fn === 'number') {
runningScriptObj = getRunningScriptByPid(fn, 'tail');
} else { } else {
runningScriptObj = getRunningScript(fn, ip, "tail", scriptArgs); runningScriptObj = getRunningScript(fn, ip, "tail", scriptArgs);
} }
@ -1754,7 +1761,7 @@ function NetscriptFunctions(workerScript) {
throw makeRuntimeErrorMsg("isRunning", "Usage: isRunning(scriptname, server, [arg1], [arg2]...)"); throw makeRuntimeErrorMsg("isRunning", "Usage: isRunning(scriptname, server, [arg1], [arg2]...)");
} }
if(typeof fn === 'number') { if(typeof fn === 'number') {
return getRunningScriptByPid(fn, ip, 'isRunning') != null; return getRunningScriptByPid(fn, 'isRunning') != null;
} else { } else {
return getRunningScript(fn, ip, "isRunning", scriptArgs) != null; return getRunningScript(fn, ip, "isRunning", scriptArgs) != null;
} }
@ -2429,6 +2436,38 @@ function NetscriptFunctions(workerScript) {
} }
return 0; return 0;
}, },
getRunningScript: function(fn, ip) {
updateDynamicRam("getRunningScript", getRamCost("getRunningScript"));
let runningScript;
if(arguments.length === 0) {
runningScript = workerScript.scriptRef;
} else if(typeof fn === 'number') {
runningScript = getRunningScriptByPid(fn, 'getRunningScript');
} else {
const scriptArgs = [];
for (var i = 2; i < arguments.length; ++i) {
scriptArgs.push(arguments[i]);
}
runningScript = getRunningScript(fn, ip, 'getRunningScript', scriptArgs);
}
if (runningScript === null) return null;
return {
args: runningScript.args.slice(),
filename: runningScript.filename,
logs: runningScript.logs.slice(),
offlineExpGained: runningScript.offlineExpGained,
offlineMoneyMade: runningScript.offlineMoneyMade,
offlineRunningTime: runningScript.offlineRunningTime,
onlineExpGained: runningScript.onlineExpGained,
onlineMoneyMade: runningScript.onlineMoneyMade,
onlineRunningTime: runningScript.onlineRunningTime,
pid: runningScript.pid,
ramUsage: runningScript.ramUsage,
server: runningScript.server,
threads: runningScript.threads,
};
},
getHackTime: function(ip, hack, int) { getHackTime: function(ip, hack, int) {
updateDynamicRam("getHackTime", getRamCost("getHackTime")); updateDynamicRam("getHackTime", getRamCost("getHackTime"));
const server = safeGetServer(ip, "getHackTime"); const server = safeGetServer(ip, "getHackTime");
@ -2524,7 +2563,7 @@ function NetscriptFunctions(workerScript) {
return Player.playtimeSinceLastAug; return Player.playtimeSinceLastAug;
}, },
prompt : function(txt) { prompt : function(txt) {
if (!isString(txt)) {txt = String(txt);} if (!isString(txt)) {txt = JSON.stringify(txt);}
// The id for this popup will consist of the first 20 characters of the prompt string.. // The id for this popup will consist of the first 20 characters of the prompt string..
// Thats hopefully good enough to be unique // Thats hopefully good enough to be unique

@ -180,4 +180,5 @@ export interface IPlayer {
queryStatFromString(str: string): number; queryStatFromString(str: string): number;
getIntelligenceBonus(weight: number): number; getIntelligenceBonus(weight: number): number;
getCasinoWinnings(): number; getCasinoWinnings(): number;
quitJob(company: string): void;
} }

@ -618,7 +618,7 @@ export function cancelationPenalty() {
const specialIp = SpecialServerIps[this.companyName]; const specialIp = SpecialServerIps[this.companyName];
if(specialIp) { if(specialIp) {
const server = AllServers[specialIp]; const server = AllServers[specialIp];
if(server && server.manuallyHacked) return 0.75; if(server && server.backdoorInstalled) return 0.75;
} }
return 0.5; return 0.5;
} }
@ -1745,6 +1745,11 @@ export function getNextCompanyPosition(company, entryPosType) {
return entryPosType; return entryPosType;
} }
export function quitJob(company) {
this.companyName = "";
delete this.jobs[company];
}
export function applyForSoftwareJob(sing=false) { export function applyForSoftwareJob(sing=false) {
return this.applyForJob(CompanyPositions[posNames.SoftwareCompanyPositions[0]], sing); return this.applyForJob(CompanyPositions[posNames.SoftwareCompanyPositions[0]], sing);
} }
@ -2066,7 +2071,7 @@ export function checkForFactionInvitations() {
} else { } else {
if (!fulcrumsecrettechonologiesFac.isBanned && !fulcrumsecrettechonologiesFac.isMember && if (!fulcrumsecrettechonologiesFac.isBanned && !fulcrumsecrettechonologiesFac.isMember &&
!fulcrumsecrettechonologiesFac.alreadyInvited && !fulcrumsecrettechonologiesFac.alreadyInvited &&
fulcrumSecretServer.manuallyHacked && fulcrumSecretServer.backdoorInstalled &&
checkMegacorpRequirements(LocationName.AevumFulcrumTechnologies, 250e3)) { checkMegacorpRequirements(LocationName.AevumFulcrumTechnologies, 250e3)) {
invitedFactions.push(fulcrumsecrettechonologiesFac); invitedFactions.push(fulcrumsecrettechonologiesFac);
} }
@ -2078,7 +2083,7 @@ export function checkForFactionInvitations() {
var bitrunnersServer = AllServers[SpecialServerIps[SpecialServerNames.BitRunnersServer]]; var bitrunnersServer = AllServers[SpecialServerIps[SpecialServerNames.BitRunnersServer]];
if (bitrunnersServer == null) { if (bitrunnersServer == null) {
console.error("Could not find BitRunners Server"); console.error("Could not find BitRunners Server");
} else if (!bitrunnersFac.isBanned && !bitrunnersFac.isMember && bitrunnersServer.manuallyHacked && } else if (!bitrunnersFac.isBanned && !bitrunnersFac.isMember && bitrunnersServer.backdoorInstalled &&
!bitrunnersFac.alreadyInvited && homeComp.maxRam >= 128) { !bitrunnersFac.alreadyInvited && homeComp.maxRam >= 128) {
invitedFactions.push(bitrunnersFac); invitedFactions.push(bitrunnersFac);
} }
@ -2088,7 +2093,7 @@ export function checkForFactionInvitations() {
var blackhandServer = AllServers[SpecialServerIps[SpecialServerNames.TheBlackHandServer]]; var blackhandServer = AllServers[SpecialServerIps[SpecialServerNames.TheBlackHandServer]];
if (blackhandServer == null) { if (blackhandServer == null) {
console.error("Could not find The Black Hand Server"); console.error("Could not find The Black Hand Server");
} else if (!theblackhandFac.isBanned && !theblackhandFac.isMember && blackhandServer.manuallyHacked && } else if (!theblackhandFac.isBanned && !theblackhandFac.isMember && blackhandServer.backdoorInstalled &&
!theblackhandFac.alreadyInvited && homeComp.maxRam >= 64) { !theblackhandFac.alreadyInvited && homeComp.maxRam >= 64) {
invitedFactions.push(theblackhandFac); invitedFactions.push(theblackhandFac);
} }
@ -2098,7 +2103,7 @@ export function checkForFactionInvitations() {
var nitesecServer = AllServers[SpecialServerIps[SpecialServerNames.NiteSecServer]]; var nitesecServer = AllServers[SpecialServerIps[SpecialServerNames.NiteSecServer]];
if (nitesecServer == null) { if (nitesecServer == null) {
console.error("Could not find NiteSec Server"); console.error("Could not find NiteSec Server");
} else if (!nitesecFac.isBanned && !nitesecFac.isMember && nitesecServer.manuallyHacked && } else if (!nitesecFac.isBanned && !nitesecFac.isMember && nitesecServer.backdoorInstalled &&
!nitesecFac.alreadyInvited && homeComp.maxRam >= 32) { !nitesecFac.alreadyInvited && homeComp.maxRam >= 32) {
invitedFactions.push(nitesecFac); invitedFactions.push(nitesecFac);
} }
@ -2242,7 +2247,7 @@ export function checkForFactionInvitations() {
var cybersecServer = AllServers[SpecialServerIps[SpecialServerNames.CyberSecServer]]; var cybersecServer = AllServers[SpecialServerIps[SpecialServerNames.CyberSecServer]];
if (cybersecServer == null) { if (cybersecServer == null) {
console.error("Could not find CyberSec Server"); console.error("Could not find CyberSec Server");
} else if (!cybersecFac.isBanned && !cybersecFac.isMember && cybersecServer.manuallyHacked && } else if (!cybersecFac.isBanned && !cybersecFac.isMember && cybersecServer.backdoorInstalled &&
!cybersecFac.alreadyInvited) { !cybersecFac.alreadyInvited) {
invitedFactions.push(cybersecFac); invitedFactions.push(cybersecFac);
} }

@ -271,7 +271,7 @@ function saveAndCloseScriptEditor() {
} }
if (filename !== ".fconf" && !isValidFilePath(filename)) { if (filename !== ".fconf" && !isValidFilePath(filename)) {
dialogBoxCreate("Script filename can contain only alphanumerics, hyphens, and underscores"); dialogBoxCreate("Script filename can contain only alphanumerics, hyphens, and underscores, and must end with an extension.");
return; return;
} }
@ -424,4 +424,4 @@ export function findRunningScriptByPid(pid, server) {
} }
} }
return null; return null;
} }

@ -30,6 +30,9 @@ export class Server extends BaseServer {
return Generic_fromJSON(Server, value.data); return Generic_fromJSON(Server, value.data);
} }
// Flag indicating whether this server has a backdoor installed by a player
backdoorInstalled: boolean = false;
// Initial server security level // Initial server security level
// (i.e. security level when the server was created) // (i.e. security level when the server was created)
baseDifficulty: number = 1; baseDifficulty: number = 1;
@ -37,10 +40,6 @@ export class Server extends BaseServer {
// Server Security Level // Server Security Level
hackDifficulty: number = 1; hackDifficulty: number = 1;
// Flag indicating whether this server has been manually hacked (ie.
// hacked through Terminal) by the player
manuallyHacked: boolean = false;
// Minimum server security level that this server can be weakened to // Minimum server security level that this server can be weakened to
minDifficulty: number = 1; minDifficulty: number = 1;
@ -103,7 +102,7 @@ export class Server extends BaseServer {
// Place some arbitrarily limit that realistically should never happen unless someone is // Place some arbitrarily limit that realistically should never happen unless someone is
// screwing around with the game // screwing around with the game
if (this.hackDifficulty > 1000000) {this.hackDifficulty = 1000000;} if (this.hackDifficulty > 100) {this.hackDifficulty = 100;}
} }
/** /**

@ -147,3 +147,10 @@ export function getServerOnNetwork(server: Server, i: number) {
return AllServers[server.serversOnNetwork[i]]; return AllServers[server.serversOnNetwork[i]];
} }
export function isBackdoorInstalled(server: Server | HacknetServer): boolean {
if ("backdoorInstalled" in server) {
return server.backdoorInstalled;
}
return false;
}

@ -1202,39 +1202,39 @@ export const serverMetadata: IServerMetadata[] = [
}, },
}, },
{ {
hackDifficulty: 10, hackDifficulty: 1,
hostname: "foodnstuff", hostname: "foodnstuff",
literature: [LiteratureNames.Sector12Crime], literature: [LiteratureNames.Sector12Crime],
maxRamExponent: 4, maxRamExponent: 4,
moneyAvailable: 2000000, moneyAvailable: 40000,
networkLayer: 1, networkLayer: 1,
numOpenPortsRequired: 0, numOpenPortsRequired: 0,
organizationName: LocationName.Sector12FoodNStuff, organizationName: LocationName.Sector12FoodNStuff,
requiredHackingSkill: 1, requiredHackingSkill: 1,
serverGrowth: 5, serverGrowth: 3000,
specialName: LocationName.Sector12FoodNStuff, specialName: LocationName.Sector12FoodNStuff,
}, },
{ {
hackDifficulty: 10, hackDifficulty: 3,
hostname: "sigma-cosmetics", hostname: "sigma-cosmetics",
maxRamExponent: 4, maxRamExponent: 4,
moneyAvailable: 2300000, moneyAvailable: 70000,
networkLayer: 1, networkLayer: 1,
numOpenPortsRequired: 0, numOpenPortsRequired: 0,
organizationName: "Sigma Cosmetics", organizationName: "Sigma Cosmetics",
requiredHackingSkill: 5, requiredHackingSkill: 5,
serverGrowth: 10, serverGrowth: 3000,
}, },
{ {
hackDifficulty: 15, hackDifficulty: 9,
hostname: "joesguns", hostname: "joesguns",
maxRamExponent: 4, maxRamExponent: 4,
moneyAvailable: 2500000, moneyAvailable: 600000,
networkLayer: 1, networkLayer: 1,
numOpenPortsRequired: 0, numOpenPortsRequired: 0,
organizationName: LocationName.Sector12JoesGuns, organizationName: LocationName.Sector12JoesGuns,
requiredHackingSkill: 10, requiredHackingSkill: 10,
serverGrowth: 20, serverGrowth: 500,
specialName: LocationName.Sector12JoesGuns, specialName: LocationName.Sector12JoesGuns,
}, },
{ {

@ -29,6 +29,11 @@ interface IDefaultSettings {
* Whether global keyboard shortcuts should be recognized throughout the game. * Whether global keyboard shortcuts should be recognized throughout the game.
*/ */
DisableHotkeys: boolean; DisableHotkeys: boolean;
/**
* Whether text effects such as corruption should be visible.
*/
DisableTextEffects: boolean;
/** /**
* Locale used for display numbers * Locale used for display numbers
@ -108,6 +113,7 @@ const defaultSettings: IDefaultSettings = {
CodeInstructionRunTime: 50, CodeInstructionRunTime: 50,
DisableASCIIArt: false, DisableASCIIArt: false,
DisableHotkeys: false, DisableHotkeys: false,
DisableTextEffects: false,
Locale: "en", Locale: "en",
MaxLogCapacity: 50, MaxLogCapacity: 50,
MaxPortCapacity: 50, MaxPortCapacity: 50,
@ -127,8 +133,9 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
CodeInstructionRunTime: 25, CodeInstructionRunTime: 25,
DisableASCIIArt: defaultSettings.DisableASCIIArt, DisableASCIIArt: defaultSettings.DisableASCIIArt,
DisableHotkeys: defaultSettings.DisableHotkeys, DisableHotkeys: defaultSettings.DisableHotkeys,
Editor: EditorSetting.Ace, DisableTextEffects: defaultSettings.DisableTextEffects,
EditorKeybinding: AceKeybindingSetting.Ace, Editor: EditorSetting.CodeMirror,
EditorKeybinding: CodeMirrorKeybindingSetting.Default,
EditorTheme: "Monokai", EditorTheme: "Monokai",
Locale: "en", Locale: "en",
MaxLogCapacity: defaultSettings.MaxLogCapacity, MaxLogCapacity: defaultSettings.MaxLogCapacity,

@ -338,6 +338,7 @@ $(document).keyup(function(e) {
let Terminal = { let Terminal = {
// Flags to determine whether the player is currently running a hack or an analyze // Flags to determine whether the player is currently running a hack or an analyze
hackFlag: false, hackFlag: false,
backdoorFlag: false,
analyzeFlag: false, analyzeFlag: false,
actionStarted: false, actionStarted: false,
actionTime: 0, actionTime: 0,
@ -485,6 +486,14 @@ let Terminal = {
Terminal.startAction(); Terminal.startAction();
}, },
startBackdoor: function() {
Terminal.backdoorFlag = true;
// Backdoor should take the same amount of time as hack
Terminal.actionTime = calculateHackingTime(Player.getCurrentServer(), Player) / 4;
Terminal.startAction();
},
startAnalyze: function() { startAnalyze: function() {
Terminal.analyzeFlag = true; Terminal.analyzeFlag = true;
Terminal.actionTime = 1; Terminal.actionTime = 1;
@ -506,14 +515,22 @@ let Terminal = {
finishAction: function(cancelled = false) { finishAction: function(cancelled = false) {
if (Terminal.hackFlag) { if (Terminal.hackFlag) {
Terminal.finishHack(cancelled); Terminal.finishHack(cancelled);
} else if (Terminal.backdoorFlag) {
Terminal.finishBackdoor(cancelled);
} else if (Terminal.analyzeFlag) { } else if (Terminal.analyzeFlag) {
Terminal.finishAnalyze(cancelled); Terminal.finishAnalyze(cancelled);
} }
// Rename the progress bar so that the next hacks dont trigger it. Re-enable terminal
$("#hack-progress-bar").attr('id', "old-hack-progress-bar");
$("#hack-progress").attr('id', "old-hack-progress");
Terminal.resetTerminalInput();
$('input[class=terminal-input]').prop('disabled', false);
}, },
// Complete the hack/analyze command // Complete the hack/analyze command
finishHack: function(cancelled = false) { finishHack: function(cancelled = false) {
if (cancelled == false) { if (!cancelled) {
var server = Player.getCurrentServer(); var server = Player.getCurrentServer();
// Calculate whether hack was successful // Calculate whether hack was successful
@ -530,7 +547,7 @@ let Terminal = {
hackWorldDaemon(Player.bitNodeN); hackWorldDaemon(Player.bitNodeN);
return; return;
} }
server.manuallyHacked = true; server.backdoorInstalled = true;
var moneyGained = calculatePercentMoneyHacked(server, Player); var moneyGained = calculatePercentMoneyHacked(server, Player);
moneyGained = Math.floor(server.moneyAvailable * moneyGained); moneyGained = Math.floor(server.moneyAvailable * moneyGained);
@ -548,21 +565,23 @@ let Terminal = {
} else { // Failure } else { // Failure
// Player only gains 25% exp for failure? TODO Can change this later to balance // Player only gains 25% exp for failure? TODO Can change this later to balance
Player.gainHackingExp(expGainedOnFailure) Player.gainHackingExp(expGainedOnFailure)
post(`Failed to hack ${server.hostname}. Gained ${numeralWrapper.formatExp(expGainedOnFailure)} hacking exp`); post(`Failed to hack ${server.hostname}. Gained ${numeralWrapper.formatExp(expGainedOnFailure)} hacking exp`);
} }
} }
// Rename the progress bar so that the next hacks dont trigger it. Re-enable terminal
$("#hack-progress-bar").attr('id', "old-hack-progress-bar");
$("#hack-progress").attr('id', "old-hack-progress");
Terminal.resetTerminalInput();
$('input[class=terminal-input]').prop('disabled', false);
Terminal.hackFlag = false; Terminal.hackFlag = false;
}, },
finishBackdoor: function(cancelled = false) {
if(!cancelled){
let server = Player.getCurrentServer();
server.backdoorInstalled = true;
postElement(<>Backdoor successful!</>);
}
Terminal.backdoorFlag = false;
},
finishAnalyze: function(cancelled = false) { finishAnalyze: function(cancelled = false) {
if (cancelled == false) { if (!cancelled) {
let currServ = Player.getCurrentServer(); let currServ = Player.getCurrentServer();
const isHacknet = currServ instanceof HacknetServer; const isHacknet = currServ instanceof HacknetServer;
post(currServ.hostname + ": "); post(currServ.hostname + ": ");
@ -609,12 +628,6 @@ let Terminal = {
} }
} }
Terminal.analyzeFlag = false; Terminal.analyzeFlag = false;
// Rename the progress bar so that the next hacks dont trigger it. Re-enable terminal
$("#hack-progress-bar").attr('id', "old-hack-progress-bar");
$("#hack-progress").attr('id', "old-hack-progress");
Terminal.resetTerminalInput();
$('input[class=terminal-input]').prop('disabled', false);
}, },
executeCommands : function(commands) { executeCommands : function(commands) {
@ -728,8 +741,8 @@ let Terminal = {
return args; return args;
}, },
executeCommand : function(command) { executeCommand : function(command) {
if (Terminal.hackFlag || Terminal.analyzeFlag) { if (Terminal.hackFlag || Terminal.backdoorFlag || Terminal.analyzeFlag) {
postError(`Cannot execute command (${command}) while an action is in progress`); postError(`Cannot execute command (${command}) while an action is in progress`);
return; return;
} }
@ -893,6 +906,24 @@ let Terminal = {
return; return;
} }
Terminal.startAnalyze(); Terminal.startAnalyze();
break;
case "backdoor":
if (commandArray.length !== 1) {
post("Incorrect usage of backdoor command. Usage: backdoor");
return;
}
if (s.purchasedByPlayer) {
postError("Cannot use backdoor on your own machines! You are currently connected to your home PC or one of your purchased servers");
} else if (!s.hasAdminRights) {
postError("You do not have admin rights for this machine! Cannot backdoor");
} else if (s.requiredHackingSkill > Player.hacking_skill) {
postError("Your hacking skill is not high enough to use backdoor on this machine. Try analyzing the machine to determine the required hacking skill");
} else if (s instanceof HacknetServer) {
postError("Cannot use backdoor on this type of Server")
} else {
Terminal.startBackdoor();
}
break; break;
case "buy": case "buy":
if (SpecialServerIps.hasOwnProperty("Darkweb Server")) { if (SpecialServerIps.hasOwnProperty("Darkweb Server")) {
@ -938,10 +969,10 @@ let Terminal = {
break; break;
} }
case "cd": { case "cd": {
if (commandArray.length !== 2) { if (commandArray.length > 2) {
postError("Incorrect number of arguments. Usage: cd [dir]"); postError("Incorrect number of arguments. Usage: cd [dir]");
} else { } else {
let dir = commandArray[1]; let dir = commandArray.length === 2 ? commandArray[1] : "/";
let evaledDir; let evaledDir;
if (dir === "/") { if (dir === "/") {
@ -955,6 +986,12 @@ let Terminal = {
postError("Invalid path. Failed to change directories"); postError("Invalid path. Failed to change directories");
return; return;
} }
const server = Player.getCurrentServer();
if(!server.scripts.some(script => script.filename.startsWith(evaledDir))) {
postError("Invalid path. Failed to change directories");
return;
}
} }
Terminal.currDir = evaledDir; Terminal.currDir = evaledDir;
@ -1785,7 +1822,7 @@ let Terminal = {
let code = "" let code = ""
if(filename.endsWith(".ns") || filename.endsWith(".js")) { if(filename.endsWith(".ns") || filename.endsWith(".js")) {
code = `export async function main(ns) { code = `export async function main(ns) {
}`; }`;
} }
Engine.loadScriptEditorContent(filepath, code); Engine.loadScriptEditorContent(filepath, code);
@ -2329,7 +2366,7 @@ let Terminal = {
runningScriptObj.threads = numThreads; runningScriptObj.threads = numThreads;
if (startWorkerScript(runningScriptObj, server)) { if (startWorkerScript(runningScriptObj, server)) {
post("Running script with " + numThreads + " thread(s) and args: " + arrayToString(args) + "."); post(`Running script with ${numThreads} thread(s), pid ${runningScriptObj.pid} and args: ${arrayToString(args)}.`);
} else { } else {
postError(`Failed to start script`); postError(`Failed to start script`);
} }
@ -2380,4 +2417,4 @@ let Terminal = {
}, },
}; };
export {postNetburnerText, Terminal}; export {postNetburnerText, Terminal};

@ -5,6 +5,7 @@ export const TerminalHelpText: string =
"Type 'help name' to learn more about the command 'name'<br><br>" + "Type 'help name' to learn more about the command 'name'<br><br>" +
'alias [-g] [name="value"] Create or display Terminal aliases<br>' + 'alias [-g] [name="value"] Create or display Terminal aliases<br>' +
"analyze Get information about the current machine <br>" + "analyze Get information about the current machine <br>" +
'backdoor Install a backdoor on the current machine <br>' +
"buy [-l/program] Purchase a program through the Dark Web<br>" + "buy [-l/program] Purchase a program through the Dark Web<br>" +
"cat [file] Display a .msg, .lit, or .txt file<br>" + "cat [file] Display a .msg, .lit, or .txt file<br>" +
"cd [dir] Change to a new directory<br>" + "cd [dir] Change to a new directory<br>" +
@ -65,11 +66,14 @@ export const HelpTexts: IMap<string> = {
"server details such as the hostname, whether the player has root access, what ports are opened/closed, and also " + "server details such as the hostname, whether the player has root access, what ports are opened/closed, and also " +
"hacking-related information such as an estimated chance to successfully hack, an estimate of how much money is " + "hacking-related information such as an estimated chance to successfully hack, an estimate of how much money is " +
"available on the server, etc.", "available on the server, etc.",
backdoor: "backdoor<br>" +
"Install a backdoor on the current machine, grants a secret bonus depending on the machine.<br>" +
"Requires root access to run.<br>",
buy: "buy [-l / program]<br>" + buy: "buy [-l / program]<br>" +
"Purchase a program through the Dark Web. Requires a TOR router to use.<br><br>" + "Purchase a program through the Dark Web. Requires a TOR router to use.<br><br>" +
"If this command is ran with the '-l' flag, it will display a list of all programs that can be bought through the " + "If this command is ran with the '-l' flag, it will display a list of all programs that can be bought through the " +
"dark web to the Terminal, as well as their costs.<br><br>" + "dark web to the Terminal, as well as their costs.<br><br>" +
"Otherwise, the name of the program must be passed in as a parameter. This is name is NOT case-sensitive.", "Otherwise, the name of the program must be passed in as a parameter. This name is NOT case-sensitive.",
cat: "cat [file]<br>" + cat: "cat [file]<br>" +
"Display message (.msg), literature (.lit), or text (.txt) files. Examples:<br><br>" + "Display message (.msg), literature (.lit), or text (.txt) files. Examples:<br><br>" +
"cat j1.msg<br>" + "cat j1.msg<br>" +

@ -17,6 +17,7 @@ import { AllServers } from "../Server/AllServers";
const commands = [ const commands = [
"alias", "alias",
"analyze", "analyze",
"backdoor",
"cat", "cat",
"cd", "cd",
"check", "check",

@ -247,7 +247,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
}, },
difficulty: 2.5, difficulty: 2.5,
gen: () => { gen: () => {
const len: number = getRandomInt(1, 25); const len: number = getRandomInt(3, 25);
const arr: number[] = []; const arr: number[] = [];
arr.length = len; arr.length = len;
for (let i: number = 0; i < arr.length; ++i) { for (let i: number = 0; i < arr.length; ++i) {
@ -291,7 +291,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
difficulty: 3, difficulty: 3,
gen: () => { gen: () => {
const intervals: number[][] = []; const intervals: number[][] = [];
const numIntervals: number = getRandomInt(1, 20); const numIntervals: number = getRandomInt(3, 20);
for (let i: number = 0; i < numIntervals; ++i) { for (let i: number = 0; i < numIntervals; ++i) {
const start: number = getRandomInt(1, 25); const start: number = getRandomInt(1, 25);
const end: number = start + getRandomInt(1, 10); const end: number = start + getRandomInt(1, 10);
@ -403,7 +403,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
}, },
difficulty: 1, difficulty: 1,
gen: () => { gen: () => {
const len: number = getRandomInt(1, 50); const len: number = getRandomInt(3, 50);
const arr: number[] = []; const arr: number[] = [];
arr.length = len; arr.length = len;
for (let i: number = 0; i < len; ++i) { for (let i: number = 0; i < len; ++i) {
@ -439,7 +439,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
}, },
difficulty: 2, difficulty: 2,
gen: () => { gen: () => {
const len: number = getRandomInt(1, 50); const len: number = getRandomInt(3, 50);
const arr: number[] = []; const arr: number[] = [];
arr.length = len; arr.length = len;
for (let i: number = 0; i < len; ++i) { for (let i: number = 0; i < len; ++i) {
@ -473,7 +473,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
}, },
difficulty: 5, difficulty: 5,
gen: () => { gen: () => {
const len: number = getRandomInt(1, 50); const len: number = getRandomInt(3, 50);
const arr: number[] = []; const arr: number[] = [];
arr.length = len; arr.length = len;
for (let i: number = 0; i < len; ++i) { for (let i: number = 0; i < len; ++i) {
@ -518,7 +518,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
difficulty: 8, difficulty: 8,
gen: () => { gen: () => {
const k: number = getRandomInt(2, 10); const k: number = getRandomInt(2, 10);
const len: number = getRandomInt(1, 50); const len: number = getRandomInt(3, 50);
const prices: number[] = []; const prices: number[] = [];
prices.length = len; prices.length = len;
for (let i = 0; i < len; ++i) { for (let i = 0; i < len; ++i) {
@ -602,7 +602,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
difficulty: 5, difficulty: 5,
gen: () => { gen: () => {
const triangle: number[][] = []; const triangle: number[][] = [];
const levels: number = getRandomInt(1, 12); const levels: number = getRandomInt(3, 12);
triangle.length = levels; triangle.length = levels;
for (let row = 0; row < levels; ++row) { for (let row = 0; row < levels; ++row) {
@ -645,8 +645,8 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
}, },
difficulty: 3, difficulty: 3,
gen: () => { gen: () => {
const numRows: number = getRandomInt(1, 14); const numRows: number = getRandomInt(2, 14);
const numColumns: number = getRandomInt(1, 14); const numColumns: number = getRandomInt(2, 14);
return [numRows, numColumns]; return [numRows, numColumns];
}, },
@ -687,8 +687,8 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
}, },
difficulty: 5, difficulty: 5,
gen: () => { gen: () => {
const numRows: number = getRandomInt(1, 12); const numRows: number = getRandomInt(2, 12);
const numColumns: number = getRandomInt(1, 12); const numColumns: number = getRandomInt(2, 12);
const grid: number[][] = []; const grid: number[][] = [];
grid.length = numRows; grid.length = numRows;
@ -754,7 +754,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
}, },
difficulty: 10, difficulty: 10,
gen: () => { gen: () => {
const len: number = getRandomInt(2, 20); const len: number = getRandomInt(6, 20);
let chars: string[] = []; let chars: string[] = [];
chars.length = len; chars.length = len;

@ -72,6 +72,7 @@ import {
processStockPrices, processStockPrices,
displayStockMarketContent displayStockMarketContent
} from "./StockMarket/StockMarket"; } from "./StockMarket/StockMarket";
import { displayMilestonesContent } from "./Milestones/MilestoneHelpers";
import { Terminal, postNetburnerText } from "./Terminal"; import { Terminal, postNetburnerText } from "./Terminal";
import { Sleeve } from "./PersonObjects/Sleeve/Sleeve"; import { Sleeve } from "./PersonObjects/Sleeve/Sleeve";
import { import {
@ -216,6 +217,7 @@ const Engine = {
factionsContent: null, factionsContent: null,
factionContent: null, factionContent: null,
augmentationsContent: null, augmentationsContent: null,
milestonesContent: null,
tutorialContent: null, tutorialContent: null,
infiltrationContent: null, infiltrationContent: null,
stockMarketContent: null, stockMarketContent: null,
@ -311,6 +313,14 @@ const Engine = {
MainMenuLinks.Augmentations.classList.add("active"); MainMenuLinks.Augmentations.classList.add("active");
}, },
loadMilestonesContent: function() {
Engine.hideAllContent();
Engine.Display.milestonesContent.style.display = "block";
routing.navigateTo(Page.Milestones);
displayMilestonesContent();
MainMenuLinks.Milestones.classList.add("active");
},
loadTutorialContent: function() { loadTutorialContent: function() {
Engine.hideAllContent(); Engine.hideAllContent();
Engine.Display.tutorialContent.style.display = "block"; Engine.Display.tutorialContent.style.display = "block";
@ -496,6 +506,7 @@ const Engine = {
Engine.Display.augmentationsContent.style.display = "none"; Engine.Display.augmentationsContent.style.display = "none";
ReactDOM.unmountComponentAtNode(Engine.Display.augmentationsContent); ReactDOM.unmountComponentAtNode(Engine.Display.augmentationsContent);
Engine.Display.milestonesContent.style.display = "none";
Engine.Display.tutorialContent.style.display = "none"; Engine.Display.tutorialContent.style.display = "none";
Engine.Display.locationContent.style.display = "none"; Engine.Display.locationContent.style.display = "none";
@ -550,6 +561,7 @@ const Engine = {
MainMenuLinks.Bladeburner.classList.remove("active"); MainMenuLinks.Bladeburner.classList.remove("active");
MainMenuLinks.Corporation.classList.remove("active"); MainMenuLinks.Corporation.classList.remove("active");
MainMenuLinks.Gang.classList.remove("active"); MainMenuLinks.Gang.classList.remove("active");
MainMenuLinks.Milestones.classList.remove("active");
MainMenuLinks.Tutorial.classList.remove("active"); MainMenuLinks.Tutorial.classList.remove("active");
MainMenuLinks.Options.classList.remove("active"); MainMenuLinks.Options.classList.remove("active");
MainMenuLinks.DevMenu.classList.remove("active"); MainMenuLinks.DevMenu.classList.remove("active");
@ -1055,6 +1067,7 @@ const Engine = {
const bladeburner = document.getElementById("bladeburner-tab"); const bladeburner = document.getElementById("bladeburner-tab");
const corp = document.getElementById("corporation-tab"); const corp = document.getElementById("corporation-tab");
const gang = document.getElementById("gang-tab"); const gang = document.getElementById("gang-tab");
const milestones = document.getElementById("milestones-tab");
const tutorial = document.getElementById("tutorial-tab"); const tutorial = document.getElementById("tutorial-tab");
const options = document.getElementById("options-tab"); const options = document.getElementById("options-tab");
const dev = document.getElementById("dev-tab"); const dev = document.getElementById("dev-tab");
@ -1155,7 +1168,8 @@ const Engine = {
</>); </>);
// Close main menu accordions for loaded game // Close main menu accordions for loaded game
var visibleMenuTabs = [terminal, createScript, activeScripts, stats, var visibleMenuTabs = [terminal, createScript, activeScripts, stats,
hacknetnodes, city, tutorial, options, dev]; hacknetnodes, city, milestones, tutorial,
options, dev];
if (Player.firstFacInvRecvd) {visibleMenuTabs.push(factions);} if (Player.firstFacInvRecvd) {visibleMenuTabs.push(factions);}
else {factions.style.display = "none";} else {factions.style.display = "none";}
if (Player.firstAugPurchased) {visibleMenuTabs.push(augmentations);} if (Player.firstAugPurchased) {visibleMenuTabs.push(augmentations);}
@ -1214,7 +1228,7 @@ const Engine = {
Engine.openMainMenuHeader( Engine.openMainMenuHeader(
[terminal, createScript, activeScripts, stats, [terminal, createScript, activeScripts, stats,
hacknetnodes, city, hacknetnodes, city, milestones,
tutorial, options] tutorial, options]
); );
@ -1257,6 +1271,8 @@ const Engine = {
Engine.Display.augmentationsContent = document.getElementById("augmentations-container"); Engine.Display.augmentationsContent = document.getElementById("augmentations-container");
Engine.Display.augmentationsContent.style.display = "none"; Engine.Display.augmentationsContent.style.display = "none";
Engine.Display.milestonesContent = document.getElementById("milestones-container");
Engine.Display.milestonesContent.style.display = "none";
Engine.Display.tutorialContent = document.getElementById("tutorial-container"); Engine.Display.tutorialContent = document.getElementById("tutorial-container");
Engine.Display.tutorialContent.style.display = "none"; Engine.Display.tutorialContent.style.display = "none";
@ -1398,6 +1414,11 @@ const Engine = {
return false; return false;
}); });
MainMenuLinks.Milestones.addEventListener("click", function() {
Engine.loadMilestonesContent();
return false;
});
MainMenuLinks.Tutorial.addEventListener("click", function() { MainMenuLinks.Tutorial.addEventListener("click", function() {
Engine.loadTutorialContent(); Engine.loadTutorialContent();
return false; return false;
@ -1480,6 +1501,7 @@ const Engine = {
document.getElementById("active-scripts-menu-link").removeAttribute("class"); document.getElementById("active-scripts-menu-link").removeAttribute("class");
document.getElementById("hacknet-nodes-menu-link").removeAttribute("class"); document.getElementById("hacknet-nodes-menu-link").removeAttribute("class");
document.getElementById("city-menu-link").removeAttribute("class"); document.getElementById("city-menu-link").removeAttribute("class");
document.getElementById("milestones-menu-link").removeAttribute("class");
document.getElementById("tutorial-menu-link").removeAttribute("class"); document.getElementById("tutorial-menu-link").removeAttribute("class");
// Copy Save Data to Clipboard // Copy Save Data to Clipboard

@ -29,3 +29,5 @@ import "../css/resleeving.scss";
import "../css/treant.css"; import "../css/treant.css";
import "../css/grid.min.css"; import "../css/grid.min.css";
import "../css/dev-menu.css"; import "../css/dev-menu.css";
import "../css/casino.scss";
import "../css/milestones.scss";

@ -102,9 +102,12 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
<li id="help-menu-header-li"> <li id="help-menu-header-li">
<button id="help-menu-header" class="mainmenu-accordion-header"> Help </button> <button id="help-menu-header" class="mainmenu-accordion-header"> Help </button>
</li> </li>
<li id="tutorial-tab" class="mainmenu-accordion-panel"> <li id="milestones-tab" class="mainmenu-accordion-panel">
<button id="tutorial-menu-link"> Tutorial </button> <button id="milestones-menu-link"> Milestones </button>
</li> </li>
<li id="tutorial-tab" class="mainmenu-accordion-panel">
<button id="tutorial-menu-link"> Tutorial </button>
</li>
<li id="options-tab" class="mainmenu-accordion-panel"> <li id="options-tab" class="mainmenu-accordion-panel">
<button id="options-menu-link"> Options </button> <button id="options-menu-link"> Options </button>
</li> </li>
@ -228,6 +231,10 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
<!-- Augmentations --> <!-- Augmentations -->
<div id="augmentations-container" class="generic-menupage-container"></div> <div id="augmentations-container" class="generic-menupage-container"></div>
<!-- Milestones content -->
<div id="milestones-container" class="generic-menupage-container">
</div>
<!-- Tutorial content --> <!-- Tutorial content -->
<div id="tutorial-container" class="generic-menupage-container"> <div id="tutorial-container" class="generic-menupage-container">
<h1> Tutorial (AKA Links to Documentation) </h1> <h1> Tutorial (AKA Links to Documentation) </h1>
@ -534,6 +541,16 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
<input class="optionCheckbox" type="checkbox" name="settingsDisableASCIIArt" id="settingsDisableASCIIArt"> <input class="optionCheckbox" type="checkbox" name="settingsDisableASCIIArt" id="settingsDisableASCIIArt">
</fieldset> </fieldset>
<!-- Disable text effects such as corruption. -->
<fieldset>
<label for="settingsDisableTextEffects" class="tooltip">Disable Text Effects:
<span class="tooltiptexthigh">
If this is set, text effects will not be displayed. This can help if text is difficult to read in certain areas.
</span>
</label>
<input class="optionCheckbox" type="checkbox" name="settingsDisableTextEffects" id="settingsDisableTextEffects">
</fieldset>
<!-- Locale for displaying numbers --> <!-- Locale for displaying numbers -->
<fieldset> <fieldset>
<label for="settingsLocale" class="tooltip">Locale: <label for="settingsLocale" class="tooltip">Locale:

@ -27,7 +27,7 @@ export function WorkerScriptAccordion(props: IProps): React.ReactElement {
const scriptRef = workerScript.scriptRef; const scriptRef = workerScript.scriptRef;
const logClickHandler = logBoxCreate.bind(null, scriptRef); const logClickHandler = logBoxCreate.bind(null, scriptRef);
const killScript = killWorkerScript.bind(null, scriptRef, scriptRef.server); const killScript = killWorkerScript.bind(null, scriptRef as any, scriptRef.server);
function killScriptClickHandler() { function killScriptClickHandler() {
killScript(); killScript();

@ -24,7 +24,7 @@ function toggleHeader(open: boolean, elems: HTMLElement[], links: HTMLElement[])
elems[i].style.maxHeight = elems[i].scrollHeight + "px"; elems[i].style.maxHeight = elems[i].scrollHeight + "px";
} else { } else {
elems[i].style.opacity = "0"; elems[i].style.opacity = "0";
elems[i].style.maxHeight = null; elems[i].style.maxHeight = "";
} }
} }
@ -35,13 +35,13 @@ function toggleHeader(open: boolean, elems: HTMLElement[], links: HTMLElement[])
links[i].style.pointerEvents = "auto"; links[i].style.pointerEvents = "auto";
} else { } else {
links[i].style.opacity = "0"; links[i].style.opacity = "0";
links[i].style.maxHeight = null; links[i].style.maxHeight = "";
links[i].style.pointerEvents = "none"; links[i].style.pointerEvents = "none";
} }
} }
} }
export function initializeMainMenuHeaders(p: IPlayer, dev: boolean=false): boolean { export function initializeMainMenuHeaders(p: IPlayer, dev = false): boolean {
function safeGetElement(id: string): HTMLElement { function safeGetElement(id: string): HTMLElement {
const elem: HTMLElement | null = document.getElementById(id); const elem: HTMLElement | null = document.getElementById(id);
if (elem == null) { if (elem == null) {
@ -68,16 +68,16 @@ export function initializeMainMenuHeaders(p: IPlayer, dev: boolean=false): boole
createProgram.style.display = p.firstProgramAvailable ? "list-item" : "none"; createProgram.style.display = p.firstProgramAvailable ? "list-item" : "none";
this.classList.toggle("opened"); (this as any).classList.toggle("opened");
const elems: HTMLElement[] = [terminal, createScript, activeScripts, createProgram]; const elems: HTMLElement[] = [terminal, createScript, activeScripts, createProgram];
const links: HTMLElement[] = [MainMenuLinks.Terminal!, MainMenuLinks.ScriptEditor!, MainMenuLinks.ActiveScripts!, MainMenuLinks.CreateProgram!]; const links: HTMLElement[] = [MainMenuLinks.Terminal!, MainMenuLinks.ScriptEditor!, MainMenuLinks.ActiveScripts!, MainMenuLinks.CreateProgram!];
if (terminal.style.maxHeight) { if (terminal.style.maxHeight) {
toggleHeader(false, elems, links); toggleHeader(false, elems, links);
createProgramNot!.style.display = "none"; createProgramNot.style.display = "none";
} else { } else {
toggleHeader(true, elems, links); toggleHeader(true, elems, links);
createProgramNot!.style.display = "block" createProgramNot.style.display = "block"
} }
} }
@ -90,7 +90,7 @@ export function initializeMainMenuHeaders(p: IPlayer, dev: boolean=false): boole
sleeves.style.display = p.sleeves.length > 0 ? "list-item" : "none"; sleeves.style.display = p.sleeves.length > 0 ? "list-item" : "none";
this.classList.toggle("opened"); (this as any).classList.toggle("opened");
const elems: HTMLElement[] = [stats, factions, augmentations, hacknetnodes, sleeves]; const elems: HTMLElement[] = [stats, factions, augmentations, hacknetnodes, sleeves];
const links: HTMLElement[] = [MainMenuLinks.Stats!, MainMenuLinks.Factions!, MainMenuLinks.Augmentations!, MainMenuLinks.HacknetNodes!, MainMenuLinks.Sleeves!]; const links: HTMLElement[] = [MainMenuLinks.Stats!, MainMenuLinks.Factions!, MainMenuLinks.Augmentations!, MainMenuLinks.HacknetNodes!, MainMenuLinks.Sleeves!];
@ -117,7 +117,7 @@ export function initializeMainMenuHeaders(p: IPlayer, dev: boolean=false): boole
corporation.style.display = p.hasCorporation() ? "list-item" : "none"; corporation.style.display = p.hasCorporation() ? "list-item" : "none";
gang.style.display = p.inGang() ? "list-item" : "none"; gang.style.display = p.inGang() ? "list-item" : "none";
this.classList.toggle("opened"); (this as any).classList.toggle("opened");
const elems: HTMLElement[] = [city, travel, job, stockmarket, bladeburner, corporation, gang]; const elems: HTMLElement[] = [city, travel, job, stockmarket, bladeburner, corporation, gang];
const links: HTMLElement[] = [MainMenuLinks.City!, MainMenuLinks.Travel!, MainMenuLinks.Job!, MainMenuLinks.StockMarket!, MainMenuLinks.Bladeburner!, MainMenuLinks.Corporation!, MainMenuLinks.Gang!]; const links: HTMLElement[] = [MainMenuLinks.City!, MainMenuLinks.Travel!, MainMenuLinks.Job!, MainMenuLinks.StockMarket!, MainMenuLinks.Bladeburner!, MainMenuLinks.Corporation!, MainMenuLinks.Gang!];
@ -129,13 +129,14 @@ export function initializeMainMenuHeaders(p: IPlayer, dev: boolean=false): boole
} }
MainMenuHeaders.Help.onclick = function() { MainMenuHeaders.Help.onclick = function() {
const milestones: HTMLElement = safeGetElement("milestones-tab");
const tutorial: HTMLElement = safeGetElement("tutorial-tab"); const tutorial: HTMLElement = safeGetElement("tutorial-tab");
const options: HTMLElement = safeGetElement("options-tab"); const options: HTMLElement = safeGetElement("options-tab");
this.classList.toggle("opened"); (this as any).classList.toggle("opened");
const elems: HTMLElement[] = [tutorial, options]; const elems: HTMLElement[] = [milestones, tutorial, options];
const links: HTMLElement[] = [MainMenuLinks.Tutorial!, MainMenuLinks.Options!]; const links: HTMLElement[] = [MainMenuLinks.Milestones!, MainMenuLinks.Tutorial!, MainMenuLinks.Options!];
if (dev) { if (dev) {
elems.push(safeGetElement("dev-tab")); elems.push(safeGetElement("dev-tab"));

@ -19,6 +19,7 @@ interface IMainMenuLinks {
Bladeburner: HTMLElement | null; Bladeburner: HTMLElement | null;
Corporation: HTMLElement | null; Corporation: HTMLElement | null;
Gang: HTMLElement | null; Gang: HTMLElement | null;
Milestones: HTMLElement | null;
Tutorial: HTMLElement | null; Tutorial: HTMLElement | null;
Options: HTMLElement | null; Options: HTMLElement | null;
DevMenu: HTMLElement | null; DevMenu: HTMLElement | null;
@ -41,6 +42,7 @@ export const MainMenuLinks: IMainMenuLinks = {
Bladeburner: null, Bladeburner: null,
Corporation: null, Corporation: null,
Gang: null, Gang: null,
Milestones: null,
Tutorial: null, Tutorial: null,
Options: null, Options: null,
DevMenu: null, DevMenu: null,
@ -54,7 +56,7 @@ export function initializeMainMenuLinks(): boolean {
throw new Error(`clearEventListeners() failed for element with id: ${id}`); throw new Error(`clearEventListeners() failed for element with id: ${id}`);
} }
return elem!; return elem;
} }
MainMenuLinks.Terminal = safeGetLink("terminal-menu-link"); MainMenuLinks.Terminal = safeGetLink("terminal-menu-link");
@ -73,6 +75,7 @@ export function initializeMainMenuLinks(): boolean {
MainMenuLinks.Bladeburner = safeGetLink("bladeburner-menu-link"); MainMenuLinks.Bladeburner = safeGetLink("bladeburner-menu-link");
MainMenuLinks.Corporation = safeGetLink("corporation-menu-link"); MainMenuLinks.Corporation = safeGetLink("corporation-menu-link");
MainMenuLinks.Gang = safeGetLink("gang-menu-link"); MainMenuLinks.Gang = safeGetLink("gang-menu-link");
MainMenuLinks.Milestones = safeGetLink("milestones-menu-link");
MainMenuLinks.Tutorial = safeGetLink("tutorial-menu-link"); MainMenuLinks.Tutorial = safeGetLink("tutorial-menu-link");
MainMenuLinks.Options = document.getElementById("options-menu-link"); // This click listener is already set, so don't clear it MainMenuLinks.Options = document.getElementById("options-menu-link"); // This click listener is already set, so don't clear it
MainMenuLinks.DevMenu = safeGetLink("dev-menu-link"); MainMenuLinks.DevMenu = safeGetLink("dev-menu-link");

@ -0,0 +1,53 @@
import React, { useEffect, useState } from "react";
function replace(str: string, i: number, char: string): string {
return str.substring(0, i) + char + str.substring(i + 1);
}
interface IProps {
content: string;
}
function randomize(char: string): string {
const randFrom = (str: string): string => str[Math.floor(Math.random()*str.length)];
const classes = [
"abcdefghijklmnopqrstuvwxyz",
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"1234567890",
" _",
"()[]{}<>",
];
const other = `!@#$%^&*()_+|\\';"/.,?\`~`;
for(const c of classes) {
if (c.includes(char)) return randFrom(c);
}
return randFrom(other);
}
export function CorruptableText(props: IProps) {
const [content, setContent] = useState(props.content);
useEffect(() => {
let counter = 5;
const id = setInterval(() => {
counter--;
if (counter > 0)
return;
counter = Math.random() * 5;
const index = Math.random() * content.length;
const letter = content.charAt(index);
setContent(replace(content, index, randomize(letter)));
setTimeout(() => {
setContent(content);
}, 50);
}, 100);
return () => {
clearInterval(id);
};
}, []);
return <span>{content}</span>
}

@ -0,0 +1,43 @@
/**
* Wrapper around material-ui's Button component that styles it with
* Bitburner's UI theme
*/
import React from "react";
import { Button, ButtonProps, makeStyles } from "@material-ui/core";
const useStyles = makeStyles({
// Tries to emulate StdButton in buttons.scss
root: {
backgroundColor: "#555",
border: "1px solid #333",
color: "white",
margin: "5px",
padding: "3px 5px",
"&:hover": {
backgroundColor: "#666",
},
},
textPrimary: {
color: "rgb( 144, 202, 249)",
},
textSecondary: {
color: "rgb(244, 143, 177)",
},
disabled: {
backgroundColor: "#333",
color: "#fff",
cursor: "default",
},
});
export const MuiButton: React.FC<ButtonProps> = (props: ButtonProps) => {
return (
<Button {...props}
classes={{
...useStyles(),
...props.classes,
}} />
)
}

29
src/ui/React/MuiPaper.tsx Normal file

@ -0,0 +1,29 @@
/**
* Wrapper around material-ui's Button component that styles it with
* Bitburner's UI theme
*/
import React from "react";
import { Paper, PaperProps, makeStyles } from "@material-ui/core";
const useStyles = makeStyles({
root: {
backgroundColor: "rgb(30, 30, 30)",
border: "2px solid #000",
borderRadius: "10px",
display: "inline-block",
flexWrap: "wrap",
padding: "10px",
},
});
export const MuiPaper: React.FC<PaperProps> = (props: PaperProps) => {
return (
<Paper {...props}
classes={{
...useStyles(),
...props.classes,
}} />
)
}

@ -0,0 +1,77 @@
/**
* Wrapper around material-ui's TextField component that styles it with
* Bitburner's UI theme
*/
import React from "react";
import { makeStyles, TextField, TextFieldProps } from "@material-ui/core";
const backgroundColorStyles = {
backgroundColor: 'rgba(57, 54, 54, 0.9)',
'&:hover': {
backgroundColor: 'rgba(70, 70, 70, 0.9)',
},
};
const formControlStyles = {
border: '1px solid #e2e2e1',
overflow: 'hidden',
borderRadius: 4,
color: 'white',
...backgroundColorStyles,
};
const useStyles = makeStyles({
root: {
...formControlStyles,
},
});
const useInputStyles = makeStyles({
root: {
...backgroundColorStyles,
color: 'white',
},
focused: {
backgroundColor: 'rgba(70, 70, 70, 0.9)',
},
disabled: {
color: 'white',
},
});
const useLabelStyles = makeStyles({
root: {
color: 'white',
},
focused: {
color: 'white !important', // Need important to override styles from FormLabel
},
disabled: {
color: 'white !important', // Need important to override styles from FormLabel
},
})
export const MuiTextField: React.FC<TextFieldProps> = (props: TextFieldProps) => {
return (
<TextField {...props}
classes={{
...useStyles(),
...props.classes,
}}
InputProps={{
classes: {
...useInputStyles(),
...props.InputProps?.classes,
},
...props.InputProps,
}}
InputLabelProps={{
classes: {
...useLabelStyles(),
...props.InputLabelProps?.classes,
},
...props.InputLabelProps,
}} />
)
}

@ -11,7 +11,7 @@ interface IStdButtonProps {
onClick?: (e: React.MouseEvent<HTMLElement>) => any; onClick?: (e: React.MouseEvent<HTMLElement>) => any;
style?: object; style?: object;
text: string | JSX.Element; text: string | JSX.Element;
tooltip?: string; tooltip?: string | JSX.Element;
} }
type IInnerHTMLMarkup = { type IInnerHTMLMarkup = {
@ -30,10 +30,16 @@ export function StdButton(props: IStdButtonProps): React.ReactElement {
} }
// Tooltip will be set using inner HTML // Tooltip will be set using inner HTML
let tooltipMarkup: IInnerHTMLMarkup | null; let tooltip;
if (hasTooltip) { if (hasTooltip) {
tooltipMarkup = { if(typeof props.tooltip === 'string') {
__html: props.tooltip! let tooltipMarkup: IInnerHTMLMarkup | null;
tooltipMarkup = {
__html: props.tooltip!
}
tooltip = <span className={"tooltiptext"} dangerouslySetInnerHTML={tooltipMarkup!}></span>
} else {
tooltip = <span className={"tooltiptext"}>{props.tooltip!}</span>
} }
} }
@ -42,7 +48,7 @@ export function StdButton(props: IStdButtonProps): React.ReactElement {
{props.text} {props.text}
{ {
hasTooltip && hasTooltip &&
<span className={"tooltiptext"} dangerouslySetInnerHTML={tooltipMarkup!}></span> tooltip
} }
</button> </button>
) )

@ -48,6 +48,11 @@ export enum Page {
*/ */
Augmentations = "Augmentations", Augmentations = "Augmentations",
/**
* List of milestones that players should follow.
*/
Milestones = "Milestones",
/** /**
* A collection of in-game material to learn about the game. * A collection of in-game material to learn about the game.
*/ */

@ -1,4 +1,4 @@
import * as numeral from 'numeral'; import numeral from 'numeral';
import 'numeral/locales/bg'; import 'numeral/locales/bg';
import 'numeral/locales/cs'; import 'numeral/locales/cs';
import 'numeral/locales/da-dk'; import 'numeral/locales/da-dk';
@ -18,7 +18,7 @@ import 'numeral/locales/ru';
class NumeralFormatter { class NumeralFormatter {
// Default Locale // Default Locale
defaultLocale: string = "en"; defaultLocale = "en";
constructor() { constructor() {
this.defaultLocale = 'en'; this.defaultLocale = 'en';
@ -80,12 +80,12 @@ class NumeralFormatter {
return this.format(n, "0.00")+"GB"; return this.format(n, "0.00")+"GB";
} }
formatPercentage(n: number, decimalPlaces: number=2): string { formatPercentage(n: number, decimalPlaces = 2): string {
const formatter: string = "0." + "0".repeat(decimalPlaces) + "%"; const formatter: string = "0." + "0".repeat(decimalPlaces) + "%";
return this.format(n, formatter); return this.format(n, formatter);
} }
formatServerSecurity(n: number, decimalPlaces: number=2): string { formatServerSecurity(n: number, decimalPlaces = 2): string {
return this.format(n, "0,0.000"); return this.format(n, "0,0.000");
} }
@ -127,6 +127,10 @@ class NumeralFormatter {
} }
return this.format(n, "0.000a"); return this.format(n, "0.000a");
} }
formatInfiltrationSecurity(n: number): string {
return this.format(n, "0.000a");
}
} }
export const numeralWrapper = new NumeralFormatter(); export const numeralWrapper = new NumeralFormatter();

@ -24,6 +24,7 @@ function setSettingsLabels() {
const autosaveInterval = document.getElementById("settingsAutosaveIntervalValLabel"); const autosaveInterval = document.getElementById("settingsAutosaveIntervalValLabel");
const disableHotkeys = document.getElementById("settingsDisableHotkeys"); const disableHotkeys = document.getElementById("settingsDisableHotkeys");
const disableASCIIArt = document.getElementById("settingsDisableASCIIArt"); const disableASCIIArt = document.getElementById("settingsDisableASCIIArt");
const disableTextEffects = document.getElementById("settingsDisableTextEffects");
const locale = document.getElementById("settingsLocale"); const locale = document.getElementById("settingsLocale");
//Initialize values on labels //Initialize values on labels
@ -38,6 +39,7 @@ function setSettingsLabels() {
setAutosaveLabel(autosaveInterval); setAutosaveLabel(autosaveInterval);
disableHotkeys.checked = Settings.DisableHotkeys; disableHotkeys.checked = Settings.DisableHotkeys;
disableASCIIArt.checked = Settings.CityListView; disableASCIIArt.checked = Settings.CityListView;
disableTextEffects.checked = Settings.DisableTextEffects;
locale.value = Settings.Locale; locale.value = Settings.Locale;
numeralWrapper.updateLocale(Settings.Locale); //Initialize locale numeralWrapper.updateLocale(Settings.Locale); //Initialize locale
@ -105,6 +107,10 @@ function setSettingsLabels() {
Settings.DisableASCIIArt = this.checked; Settings.DisableASCIIArt = this.checked;
} }
disableTextEffects.onclick = function() {
Settings.DisableTextEffects = this.checked;
}
//Locale selector //Locale selector
locale.onchange = function() { locale.onchange = function() {
if (!numeralWrapper.updateLocale(locale.value)) { if (!numeralWrapper.updateLocale(locale.value)) {

@ -5,29 +5,22 @@
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
export class MoneySourceTracker { export class MoneySourceTracker {
// Initiatizes a MoneySourceTracker object from a JSON save state.
static fromJSON(value: any): MoneySourceTracker {
return Generic_fromJSON(MoneySourceTracker, value.data);
}
bladeburner: number = 0;
casino: number = 0;
class: number = 0;
codingcontract: number = 0;
corporation: number = 0;
crime: number = 0;
gang: number = 0;
hacking: number = 0;
hacknetnode: number = 0;
hospitalization: number = 0;
infiltration: number = 0;
stock: number = 0;
total: number = 0;
work: number = 0;
[key: string]: number | Function; [key: string]: number | Function;
constructor() {} bladeburner = 0;
casino = 0;
class = 0;
codingcontract = 0;
corporation = 0;
crime = 0;
gang = 0;
hacking = 0;
hacknetnode = 0;
hospitalization = 0;
infiltration = 0;
stock = 0;
total = 0;
work = 0;
// Record money earned // Record money earned
record(amt: number, source: string): void { record(amt: number, source: string): void {
@ -45,7 +38,7 @@ export class MoneySourceTracker {
reset(): void { reset(): void {
for (const prop in this) { for (const prop in this) {
if (typeof this[prop] === "number") { if (typeof this[prop] === "number") {
this[prop] = 0; (this[prop] as number) = 0;
} }
} }
} }
@ -54,6 +47,11 @@ export class MoneySourceTracker {
toJSON(): any { toJSON(): any {
return Generic_toJSON("MoneySourceTracker", this); return Generic_toJSON("MoneySourceTracker", this);
} }
// Initiatizes a MoneySourceTracker object from a JSON save state.
static fromJSON(value: any): MoneySourceTracker {
return Generic_fromJSON(MoneySourceTracker, value.data);
}
} }
Reviver.constructors.MoneySourceTracker = MoneySourceTracker; Reviver.constructors.MoneySourceTracker = MoneySourceTracker;

@ -519,6 +519,11 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function() {
await testNonzeroDynamicRamCost(f); await testNonzeroDynamicRamCost(f);
}); });
it("getRunningScript()", async function() {
const f = ["getRunningScript"];
await testNonzeroDynamicRamCost(f);
});
it("getTimeSinceLastAug()", async function() { it("getTimeSinceLastAug()", async function() {
const f = ["getTimeSinceLastAug"]; const f = ["getTimeSinceLastAug"];
await testNonzeroDynamicRamCost(f); await testNonzeroDynamicRamCost(f);

@ -410,6 +410,11 @@ describe("Netscript Static RAM Calculation/Generation Tests", function() {
await expectNonZeroRamCost(f); await expectNonZeroRamCost(f);
}); });
it("getRunningScript()", async function() {
const f = ["getRunningScript"];
await expectNonZeroRamCost(f);
});
it("getTimeSinceLastAug()", async function() { it("getTimeSinceLastAug()", async function() {
const f = ["getTimeSinceLastAug"]; const f = ["getTimeSinceLastAug"];
await expectNonZeroRamCost(f); await expectNonZeroRamCost(f);

@ -1,8 +1,9 @@
{ {
"compilerOptions": { "compilerOptions": {
"baseUrl" : ".", "baseUrl" : ".",
"esModuleInterop": true,
"jsx": "react", "jsx": "react",
"lib" : ["es2016", "dom", "es2017.object"], "lib" : ["es2016", "dom", "es2017.object", "es2019"],
"module": "commonjs", "module": "commonjs",
"target": "es6", "target": "es6",
"sourceMap": true, "sourceMap": true,

@ -1,78 +0,0 @@
{
"defaultSeverity": "error",
"extends": [
"tslint:all"
],
"jsRules": {},
"linterOptions": {
"exclude": [
"node_modules/"
]
},
"rules": {
"completed-docs": [
true,
{
"classes": true,
"enums": true,
"enum-members": true,
"functions": {
"visibilities": [
"exported"
]
},
"interfaces": true,
"methods": {
"privacies": [
"public"
]
},
"namespaces": true,
"properties": true,
"types": true,
"variables": {
"visibilities": [
"exported"
]
}
}
],
"linebreak-style": false,
"member-access": [
true,
"no-public"
],
"no-any": false,
"no-inferrable-types": [
false,
"ignore-params",
"ignore-properties"
],
"no-magic-numbers": [true, -1, 0, 1, 2, 10, 100],
"no-null-keyword": false,
"no-unsafe-any": false,
"object-literal-key-quotes": [
true,
"as-needed"
],
"only-arrow-functions": [
true,
"allow-declarations",
"allow-named-functions"
],
"triple-equals": [true, "allow-null-check", "allow-undefined-check"],
"typedef": [
true,
"call-signatures",
"arrow-call-signatures",
"parameter",
"arrow-parameter",
"property-declaration",
"variable-declaration",
"member-variable-declaration",
"object-destructuring",
"array-destructuring"
]
},
"rulesDirectory": []
}