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

@ -8,3 +8,16 @@ Update the following
- `doc/source/changelog.rst`
- post to discord
- 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

26
dist/engineStyle.css vendored

@ -5007,5 +5007,31 @@ html {
margin-left: 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*/

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 |
| Factions | | | you from joining: |
+ +----------------+-----------------------------------------+-------------------------------+
| | CyberSec | * Hack CSEC Manually | |
| | CyberSec | * Install a backdoor on the CSEC server | |
+ +----------------+-----------------------------------------+-------------------------------+
| | Tian Di Hui | * $1m | |
| | | * Hacking Level 50 | |
@ -74,11 +74,17 @@ List of Factions and their Requirements
| | | | * New Tokyo |
| | | | * 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 | |
| | | the Corporation | |
@ -109,7 +115,8 @@ List of Factions and their Requirements
+ +----------------+-----------------------------------------+-------------------------------+
| | Fulcrum Secret | * Have 250k reputation with | |
| | Technologies | the Corporation | |
| | | * Hack fulcrumassets manually | |
| | | * Install a backdoor on the | |
| | | fulcrumassets server | |
+---------------------+----------------+-----------------------------------------+-------------------------------+
| Criminal | Slum Snakes | * All Combat Stats of 30 | |
| 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
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
^^^

@ -3,10 +3,57 @@
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)
----------------------------------------
Netscript
**Netscript**
* '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.
@ -16,11 +63,11 @@ Netscript
* 'isRunning' hostname defaults to current server.
* 'isRunning' now works with pid as first argument.
Gang
**Gang**
* Nerfed ascension mechanic once again :(
Misc.
**Misc.**
* Souce-File typo fix
* Fix 'while you were away' screen.
@ -29,28 +76,28 @@ Misc.
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.
* Manual hacking gyms and university gives you a 10% discount.
* Manual hacking a corporation server decreases the penalty for leaving work
early.
BladeBurner
**BladeBurner**
* nerfed int exp gained.
Documentation
**Documentation**
* purchaseServer specifies what happens on failure.
* Fixed typo in recommended bitnode page.
* Removed misleading ram requirements for hacking factions.
Netscript
**Netscript**
* growthAnalyze handles Infinity correctly.
Misc.
**Misc.**
* Faction Augmentation will list how much reputation is required even after
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.
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
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
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 NUKE.exe
$ hack
$ backdoor
Keep hacking the server until you are successful. After you successfully hack it, you should
receive a faction invitation from |CyberSec| shortly afterwards. Accept it. If you accidentally
After you successfully install the backdoor, you should receive a faction
invitation from |CyberSec| shortly afterwards. Accept it. If you accidentally
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
accept the invitation.

@ -293,7 +293,7 @@ Source-File
* 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
when installing Augmentation
when installing Augmentations.)
Difficulty
Hard

@ -27,7 +27,7 @@ getServer() Netscript Function
sshPortOpen
baseDifficulty
hackDifficulty
manuallyHacked
backdoorInstalled
minDifficulty
moneyAvailable
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");
.. js:function:: isRunning(scriptPid[, hostname=current hostname])
.. js:function:: isRunning(scriptPid)
:RAM cost: 0.1 GB
:param number scriptPid: PID of the script to check.
@ -52,5 +52,3 @@ isRunning() Netscript Function
.. code-block:: javascript
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"]
tail("foo.script", "foodnstuff", 1, "test");
.. js:function:: tail(scriptPid[, hostname=current hostname])
.. js:function:: tail(scriptPid)
:RAM cost: 0 GB
: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
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>
getScriptName() <basicfunctions/getScriptName>
getScriptRam() <basicfunctions/getScriptRam>
getRunningScript() <basicfunctions/getRunningScript>
getHackTime() <basicfunctions/getHackTime>
getGrowTime() <basicfunctions/getGrowTime>
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.
This function will perform a manual hack on the server you are currently connected to.
This is typically required to join factions.
Examples:
.. code-block:: javascript
connect("CSEC");
connect("foodnstuff");
manualHack();
.. warning::

@ -1,9 +1,13 @@
softReset() Netscript Function
===================================
.. js:function:: softReset()
.. js:function:: softReset([callbackScript])
: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.

@ -100,9 +100,12 @@
<li id="help-menu-header-li">
<button id="help-menu-header" class="mainmenu-accordion-header"> Help </button>
</li>
<li id="tutorial-tab" class="mainmenu-accordion-panel">
<button id="tutorial-menu-link"> Tutorial </button>
</li>
<li id="milestones-tab" class="mainmenu-accordion-panel">
<button id="milestones-menu-link"> Milestones </button>
</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">
<button id="options-menu-link"> Options </button>
</li>
@ -226,6 +229,10 @@
<!-- Augmentations -->
<div id="augmentations-container" class="generic-menupage-container"></div>
<!-- Milestones content -->
<div id="milestones-container" class="generic-menupage-container">
</div>
<!-- Tutorial content -->
<div id="tutorial-container" class="generic-menupage-container">
<h1> Tutorial (AKA Links to Documentation) </h1>
@ -521,6 +528,16 @@
<input class="optionCheckbox" type="checkbox" name="settingsDisableASCIIArt" id="settingsDisableASCIIArt">
</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 -->
<fieldset>
<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"
},
"dependencies": {
"@material-ui/core": "^4.11.3",
"@types/numeral": "0.0.25",
"@types/react": "^16.8.6",
"@types/react-dom": "^16.8.2",
@ -47,7 +48,10 @@
"@babel/core": "^7.3.4",
"@babel/preset-react": "^7.0.0",
"@types/chai": "^4.1.7",
"@types/lodash": "^4.14.168",
"@types/mocha": "^5.2.7",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
"babel-loader": "^8.0.5",
"beautify-lint": "^1.0.3",
"benchmark": "^2.1.1",
@ -55,8 +59,8 @@
"chai": "^4.2.0",
"css-loader": "^0.28.11",
"es6-promise-polyfill": "^1.1.1",
"eslint": "^4.19.1",
"eslint-plugin-node": "^6.0.1",
"eslint": "^7.24.0",
"eslint-plugin-node": "^11.1.0",
"file-loader": "^1.1.11",
"html-webpack-plugin": "^3.2.0",
"i18n-webpack-plugin": "^1.0.0",
@ -86,8 +90,7 @@
"stylelint-declaration-use-variable": "^1.6.1",
"stylelint-order": "^0.8.1",
"ts-loader": "^4.5.0",
"tslint": "^5.10.0",
"typescript": "^2.9.2",
"typescript": "^4.2.4",
"uglify-es": "^3.3.9",
"uglifyjs-webpack-plugin": "^1.3.0",
"url-loader": "^1.0.1",
@ -113,10 +116,9 @@
"build": "webpack --mode production",
"build:dev": "webpack --mode development",
"build:test": "webpack --config webpack.config-test.js",
"lint": "npm run lint:typescript & npm run lint:javascript & npm run lint:style",
"lint:javascript": "eslint *.js ./src/**/*.js ./tests/**/*.js ./utils/**/*.js",
"lint": "npm run lint:jsts & npm run lint:style",
"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:typescript": "tslint --project . --exclude **/*.d.ts --format stylish src/**/*.ts utils/**/*.ts",
"preinstall": "node ./scripts/engines-check.js",
"test": "mochapack --webpack-config webpack.config-test.js -r jsdom-global/register ./test/index.js",
"watch": "webpack --watch --mode production",

@ -35,7 +35,7 @@ export function printAliases(): void {
}
// 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 matches = dec.match(re);
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 { isString } from "../../utils/helpers/isString";
import { removeChildrenFromElement } from "../../utils/uiHelpers/removeChildrenFromElement";
import { Money } from "../ui/React/Money";
import React from "react";
import ReactDOM from "react-dom";
@ -177,7 +178,7 @@ function initAugmentations() {
const CombatRib3 = new Augmentation({
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 " +
"drugs into the bloodstream<br><br>." +
"drugs into the bloodstream.<br><br>" +
"This augmentation increases the player's strength and defense by 18%.",
prereqs:[AugmentationNames.CombatRib2],
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, " +
"an artificial pheromone discovered by scientists. The ADR-V1 pheromone, when excreted, " +
"triggers feelings of admiration and approval in other people.<br><br>" +
"This augmentation:<br>" +
"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%.",
"This augmentation increases the amount of reputation the player gains when working for a faction or company by 10%.",
company_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, " +
"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>" +
"This augmentation:<br>" +
"Increases the amount of reputation the player gains for a faction and company by 20%.",
"This augmentation increases the amount of reputation the player gains when working for a faction or company by 20%.",
company_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 " +
"simulacrums within a large radius. These simulacrums are commonly used for " +
"espionage and surveillance work.<br><br>" +
"This augmentation:<br>" +
"Increases the amount of reputation the player gains when working for a faction or company by 15%.",
"This augmentation increases the amount of reputation the player gains when working for a faction or company by 15%.",
company_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 " +
"around the skin, making the user much harder to see from the naked eye.<br><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%.",
agility_mult: 1.05,
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, " +
"making the user more resilient as well as stealthy.<br><br>" +
"This augmentation:<br>" +
"Increases the player's agility by 10% <br>" +
"Increases the player's defense by 10% <br>" +
"Increases the player's agility by 10%.<br>" +
"Increases the player's defense by 10%.<br>" +
"Increases the amount of money the player gains from crimes by 25%.",
prereqs:[AugmentationNames.LuminCloaking1],
agility_mult: 1.1,
@ -1372,7 +1369,7 @@ function initAugmentations() {
name:AugmentationNames.Xanipher, repCost:350e3, moneyCost:850e6,
info:"A concoction of advanced nanobots that is orally ingested into the " +
"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>" +
"Increases all of the player's stats by 20%.<br>" +
"Increases the player's experience gain rate for all stats by 15%.",
@ -1554,12 +1551,12 @@ function initAugmentations() {
// Sector12
const CashRoot = new Augmentation({
name:AugmentationNames.CashRoot, repCost:5e3, moneyCost:25e6,
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 " +
"and upload the assets.<br><br>" +
"This augmentation:<br>" +
"Lets the player start with $1,000,000 after a reset.<br>" +
"Lets the player start with the BruteSSH.exe program after a reset."
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
and upload the assets.<br /><br />
This augmentation:<br />
Lets the player start with {Money(1e6)} after a reset.<br />
Lets the player start with the BruteSSH.exe program after a reset.</>
});
CashRoot.addToFactions(["Sector-12"]);
if (augmentationExists(AugmentationNames.CashRoot)) {
@ -1574,8 +1571,7 @@ function initAugmentations() {
"synthesizes glucose, amino acids, and vitamins and redistributes them " +
"across the body. The device is powered by the body's naturally wasted " +
"energy in the form of heat.<br><br>" +
"This augmentation:<br>" +
"Increases the player's experience gain rate for all combat stats by 20%.",
"This augmentation increases the player's experience gain rate for all combat stats by 20%.",
strength_exp_mult: 1.2,
defense_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, " +
"it can also be effective against non-augmented enemies due to its high temperature " +
"and concussive force.<br><br>" +
"This augmentation:<br>" +
"Increases the player's success chance in Bladeburner contracts/operations by 6%.",
"This augmentation increases the player's success chance in Bladeburner contracts/operations by 6%.",
bladeburner_success_chance_mult: 1.06,
isSpecial: true,
});
@ -1799,8 +1794,7 @@ function initAugmentations() {
"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 " +
"higher velocity than the V1 model.<br><br>" +
"This augmentation:<br>" +
"Increases the player's success chance in Bladeburner contracts/operations by 8%.",
"This augmentation increases the player's success chance in Bladeburner contracts/operations by 8%.",
prereqs:[AugmentationNames.HyperionV1],
bladeburner_success_chance_mult: 1.08,
isSpecial: true,
@ -1958,8 +1952,7 @@ function initAugmentations() {
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 " +
"threats while keeping casualties to a minimum.<br><br>" +
"This augmentation:<br>" +
"Increases the player's success chance in Bladeburner contracts/operations by 8%.",
"This augmentation increases the player's success chance in Bladeburner contracts/operations by 8%.",
prereqs:[AugmentationNames.BladeArmor],
bladeburner_success_chance_mult: 1.08,
isSpecial: true,
@ -1973,8 +1966,7 @@ function initAugmentations() {
"multiple-fiber system. The upgraded weapon uses multiple fiber laser " +
"modules that combine together to form a single, more powerful beam of up to " +
"2000MW.<br><br>" +
"This augmentation:<br>" +
"Increases the player's success chance in Bladeburner contracts/operations by 10%.",
"This augmentation increases the player's success chance in Bladeburner contracts/operations by 10%.",
prereqs:[AugmentationNames.BladeArmorUnibeam],
bladeburner_success_chance_mult: 1.1,
isSpecial: true,

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

@ -6,7 +6,7 @@
import { IMap } from "./types";
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
* 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:
`
v0.51.5 - 2021-04-20 Flags! (hydroflame)
v0.51.6 - 2021-04-28 Backdoor! (Community)
-------
Netscript
* '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.
* '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.
Backdoor
* a new terminal command, backdoor, has been added to help differentiate
between the terminal hack command and the netscript hack function. (@dewint)
Gang
* Nerfed ascension mechanic once again :(
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.
* Souce-File typo fix
* Fix 'while you were away' screen.
* Bladeburner team size can no longer be set to negative amounts.
* 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)
`
}

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

@ -7,6 +7,7 @@ import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners";
import { getRandomInt } from "../utils/helpers/getRandomInt";
import { infiltrationBoxCreate } from "../utils/InfiltrationBox";
import { formatNumber } from "../utils/StringHelperFunctions";
import { numeralWrapper } from "./ui/numeralFormat";
let InfiltrationScenarios = {
Guards: "You see an armed security guard patrolling the area.",
@ -451,9 +452,9 @@ function endInfiltrationLevel(inst) {
BitNodeMultipliers.InfiltrationMoney;
inst.secretsStolen.push(baseSecretValue);
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'>$" +
formatNumber(secretMoneyValue, 2) + "</span>), or they " +
"could be given to factions for reputation (<span class='light-yellow'>" + formatNumber(secretValue, 3) + " rep</span>)");
"These classified secrets could probably be sold for money (<span class='money-gold'>" +
numeralWrapper.formatMoney(secretMoneyValue) + "</span>), or they " +
"could be given to factions for reputation (<span class='light-yellow'>" + numeralWrapper.formatReputation(secretValue) + " rep</span>)");
}
// Increase security level based on difficulty
@ -495,16 +496,16 @@ function updateInfiltrationLevelText(inst) {
document.getElementById("infiltration-level-text").innerHTML =
"Facility name:    " + inst.companyName + "<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>" +
"Reputation:       <span class='light-yellow'>" + formatNumber(totalValue, 3) + "</span><br>" +
"Money:           <span class='money-gold'>$" + formatNumber(totalMoneyValue, 2) + "</span><br><br>" +
"Hack exp gained:  " + formatNumber(inst.calcGainedHackingExp(), 3) + "<br>" +
"Str exp gained:   " + formatNumber(inst.calcGainedStrengthExp(), 3) + "<br>" +
"Def exp gained:   " + formatNumber(inst.calcGainedDefenseExp(), 3) + "<br>" +
"Dex exp gained:   " + formatNumber(inst.calcGainedDexterityExp(), 3) + "<br>" +
"Agi exp gained:   " + formatNumber(inst.calcGainedAgilityExp(), 3) + "<br>" +
"Cha exp gained:   " + formatNumber(inst.calcGainedCharismaExp(), 3);
"Reputation:       <span class='light-yellow'>" + numeralWrapper.formatReputation(totalValue, 3) + "</span><br>" +
"Money:           <span class='money-gold'>" + numeralWrapper.formatMoney(totalMoneyValue, 2) + "</span><br><br>" +
"Hack exp gained:  " + numeralWrapper.formatExp(inst.calcGainedHackingExp(), 3) + "<br>" +
"Str exp gained:   " + numeralWrapper.formatExp(inst.calcGainedStrengthExp(), 3) + "<br>" +
"Def exp gained:   " + numeralWrapper.formatExp(inst.calcGainedDefenseExp(), 3) + "<br>" +
"Dex exp gained:   " + numeralWrapper.formatExp(inst.calcGainedDexterityExp(), 3) + "<br>" +
"Agi exp gained:   " + numeralWrapper.formatExp(inst.calcGainedAgilityExp(), 3) + "<br>" +
"Cha exp gained:   " + numeralWrapper.formatExp(inst.calcGainedCharismaExp(), 3);
/* eslint-enable no-irregular-whitespace */
}
@ -524,7 +525,7 @@ function updateInfiltrationButtons(inst, scenario) {
"<span class='tooltiptext'>" +
"Attempt to escape the facility with the classified secrets and " +
"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>";
switch(scenario) {
@ -532,55 +533,55 @@ function updateInfiltrationButtons(inst, scenario) {
document.getElementById("infiltration-pickdoor").innerHTML = "Lockpick" +
"<span class='tooltiptext'>" +
"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 " +
"security level will increase by 3%.</span>";
case InfiltrationScenarios.TechOnly:
document.getElementById("infiltration-hacksecurity").innerHTML = "Hack" +
"<span class='tooltiptext'>" +
"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, " +
"the security level will increase by 5%.</span>";
document.getElementById("infiltration-destroysecurity").innerHTML = "Destroy security" +
"<span class='tooltiptext'>" +
"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 " +
"security level will increase by 10%. </span>";
document.getElementById("infiltration-sneak").innerHTML = "Sneak" +
"<span class='tooltiptext'>" +
"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>";
break;
case InfiltrationScenarios.Bots:
document.getElementById("infiltration-kill").innerHTML = "Destroy bots" +
"<span class='tooltiptext'>" +
"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, " +
"the security level will increase by 10%. </span>";
document.getElementById("infiltration-assassinate").innerHTML = "Assassinate bots" +
"<span class='tooltiptext'>" +
"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>";
document.getElementById("infiltration-hacksecurity").innerHTML = "Hack bots" +
"<span class='tooltiptext'>" +
"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, " +
"the security level will increase by 5%. </span>";
document.getElementById("infiltration-sneak").innerHTML = "Sneak" +
"<span class='tooltiptext'>" +
"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>";
break;
@ -589,39 +590,39 @@ function updateInfiltrationButtons(inst, scenario) {
document.getElementById("infiltration-kill").innerHTML = "Kill" +
"<span class='tooltiptext'>" +
"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, " +
"the security level will decrease by 10%. </span>";
document.getElementById("infiltration-knockout").innerHTML = "Knockout" +
"<span class='tooltiptext'>" +
"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 " +
"security level will increase by 10%. </span>";
document.getElementById("infiltration-stealthknockout").innerHTML = "Stealth Knockout" +
"<span class='tooltiptext'>" +
"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>";
document.getElementById("infiltration-assassinate").innerHTML = "Assassinate" +
"<span class='tooltiptext'>" +
"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>";
document.getElementById("infiltration-sneak").innerHTML = "Sneak" +
"<span class='tooltiptext'>" +
"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>";
document.getElementById("infiltration-bribe").innerHTML = "Bribe" +
"<span class='tooltiptext'>" +
"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>";
break;
}

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

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

@ -19,9 +19,9 @@ type IProps = {
export class LocationCity extends React.Component<IProps, any> {
asciiCity() {
const thiscity = this;
const topprop = this.props
const topprop = this.props;
function LocationLetter(location: string) {
function LocationLetter(location: LocationName) {
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)}>
X

@ -26,6 +26,13 @@ import { StdButton } from "../../ui/React/StdButton";
import { Reputation } from "../../ui/React/Reputation";
import { Favor } from "../../ui/React/Favor";
import {
yesNoBoxGetYesButton,
yesNoBoxGetNoButton,
yesNoBoxClose,
yesNoBoxCreate
} from "../../../utils/YesNoBox";
type IProps = {
engine: IEngine;
locName: LocationName;
@ -73,6 +80,7 @@ export class CompanyLocation extends React.Component<IProps, IState> {
this.btnStyle = { display: "block" };
this.quit = this.quit.bind(this);
this.applyForAgentJob = this.applyForAgentJob.bind(this);
this.applyForBusinessConsultantJob = this.applyForBusinessConsultantJob.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() {
const isEmployedHere = this.jobTitle != null;
const favorGain = this.company.getFavorGain();
@ -236,10 +264,12 @@ export class CompanyLocation extends React.Component<IProps, IState> {
</p><br />
<br /><p style={blockStyleMarkup}>-------------------------</p><br />
<StdButton
id={"foo-work-button-id"}
onClick={this.work}
style={this.btnStyle}
text={"Work"}
/>&nbsp;&nbsp;&nbsp;&nbsp;
<StdButton
onClick={this.quit}
text={"Quit"}
/>
</div>
}

@ -6,24 +6,29 @@
*/
import * as React from "react";
import { CompanyLocation } from "./CompanyLocation";
import { GymLocation } from "./GymLocation";
import { HospitalLocation } from "./HospitalLocation";
import { SlumsLocation } from "./SlumsLocation";
import { SpecialLocation } from "./SpecialLocation";
import { TechVendorLocation } from "./TechVendorLocation";
import { TravelAgencyLocation } from "./TravelAgencyLocation";
import { UniversityLocation } from "./UniversityLocation";
import { CasinoLocation } from "./CasinoLocation";
import { CompanyLocation } from "./CompanyLocation";
import { GymLocation } from "./GymLocation";
import { HospitalLocation } from "./HospitalLocation";
import { SlumsLocation } from "./SlumsLocation";
import { SpecialLocation } from "./SpecialLocation";
import { TechVendorLocation } from "./TechVendorLocation";
import { TravelAgencyLocation } from "./TravelAgencyLocation";
import { UniversityLocation } from "./UniversityLocation";
import { CasinoLocation } from "./CasinoLocation";
import { Location } from "../Location";
import { LocationType } from "../LocationTypeEnum";
import { CityName } from "../data/CityNames";
import { Location } from "../Location";
import { LocationType } from "../LocationTypeEnum";
import { CityName } from "../data/CityNames";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
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 = {
engine: IEngine;
@ -146,11 +151,19 @@ export class GenericLocation extends React.Component<IProps, any> {
render() {
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 (
<div>
<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}
</div>
)

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

@ -55,6 +55,7 @@ export class HospitalLocation extends React.Component<IProps, IState> {
const cost = this.getCost();
this.props.p.loseMoney(cost);
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.setState({

@ -82,7 +82,7 @@ export class TravelAgencyLocation extends React.Component<IProps, any> {
listWorldMap() {
const travelBtns: React.ReactNode[] = [];
for (const key in CityName) {
const city = CityName[key];
const city: CityName = (CityName as any)[key];
// Skip current city
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);
console.log(`ip: ${ip}`);
const server = getServer(ip);
if(server == null || !server.hasOwnProperty('manuallyHacked')) return this.props.loc.costMult;
const discount = (server as Server).manuallyHacked? 0.9 : 1;
if(server == null || !server.hasOwnProperty('backdoorInstalled')) return this.props.loc.costMult;
const discount = (server as Server).backdoorInstalled? 0.9 : 1;
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 " +
"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>" +
"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"));
AddToAllMessages(new Message(MessageFilenames.NiteSecTest,
"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 " +
"and ideas with bullets. <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"));
AddToAllMessages(new Message(MessageFilenames.BitRunnersTest,
"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,
ScriptArbScriptRamCost: 1.0,
ScriptGetScriptRamCost: 0.1,
ScriptGetRunningScriptRamCost: 0.3,
ScriptGetHackTimeRamCost: 0.05,
ScriptGetFavorToDonate: 0.10,
ScriptCodingContractBaseRamCost: 10,
@ -165,6 +166,7 @@ export const RamCosts: IMap<any> = {
getWeakenTime: () => RamCostConstants.ScriptGetHackTimeRamCost,
getScriptIncome: () => RamCostConstants.ScriptGetScriptRamCost,
getScriptExpGain: () => RamCostConstants.ScriptGetScriptRamCost,
getRunningScript: () => RamCostConstants.ScriptGetRunningScriptRamCost,
nFormat: () => 0,
getTimeSinceLastAug: () => RamCostConstants.ScriptGetHackTimeRamCost,
prompt: () => 0,

@ -266,6 +266,7 @@ const defaultInterpreter = new Interpreter('', function(){});
// the acorn interpreter has a bug where it doesn't convert arrays correctly.
// so we have to more or less copy it here.
function toNative(pseudoObj) {
if(pseudoObj == null) return null;
if(!pseudoObj.hasOwnProperty('properties') ||
!pseudoObj.hasOwnProperty('getter') ||
!pseudoObj.hasOwnProperty('setter') ||
@ -366,13 +367,17 @@ function NetscriptFunctions(workerScript) {
return workerScript.scriptRef;
}
const getRunningScriptByPid = function(pid, ip, callingFnName) {
const getRunningScriptByPid = function(pid, callingFnName) {
if (typeof callingFnName !== "string" || callingFnName === "") {
callingFnName = "getRunningScriptgetRunningScriptByPid";
}
const server = safeGetServer(ip, callingFnName);
return findRunningScriptByPid(pid, server);
for(const name of Object.keys(AllServers)) {
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);
}
if(manual) {
server.manuallyHacked = true;
server.backdoorInstalled = true;
}
return Promise.resolve(moneyGained);
} else {
@ -1052,8 +1057,10 @@ function NetscriptFunctions(workerScript) {
},
tail: function(fn, ip=workerScript.serverIp, ...scriptArgs) {
let runningScriptObj;
if(typeof fn === 'number') {
runningScriptObj = getRunningScriptByPid(fn, ip, 'tail');
if(arguments.length === 0) {
runningScriptObj = workerScript.scriptRef;
} else if(typeof fn === 'number') {
runningScriptObj = getRunningScriptByPid(fn, 'tail');
} else {
runningScriptObj = getRunningScript(fn, ip, "tail", scriptArgs);
}
@ -1754,7 +1761,7 @@ function NetscriptFunctions(workerScript) {
throw makeRuntimeErrorMsg("isRunning", "Usage: isRunning(scriptname, server, [arg1], [arg2]...)");
}
if(typeof fn === 'number') {
return getRunningScriptByPid(fn, ip, 'isRunning') != null;
return getRunningScriptByPid(fn, 'isRunning') != null;
} else {
return getRunningScript(fn, ip, "isRunning", scriptArgs) != null;
}
@ -2429,6 +2436,38 @@ function NetscriptFunctions(workerScript) {
}
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) {
updateDynamicRam("getHackTime", getRamCost("getHackTime"));
const server = safeGetServer(ip, "getHackTime");
@ -2524,7 +2563,7 @@ function NetscriptFunctions(workerScript) {
return Player.playtimeSinceLastAug;
},
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..
// Thats hopefully good enough to be unique

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

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

@ -271,7 +271,7 @@ function saveAndCloseScriptEditor() {
}
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;
}

@ -30,6 +30,9 @@ export class Server extends BaseServer {
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
// (i.e. security level when the server was created)
baseDifficulty: number = 1;
@ -37,10 +40,6 @@ export class Server extends BaseServer {
// Server Security Level
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
minDifficulty: number = 1;
@ -103,7 +102,7 @@ export class Server extends BaseServer {
// Place some arbitrarily limit that realistically should never happen unless someone is
// 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]];
}
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",
literature: [LiteratureNames.Sector12Crime],
maxRamExponent: 4,
moneyAvailable: 2000000,
moneyAvailable: 40000,
networkLayer: 1,
numOpenPortsRequired: 0,
organizationName: LocationName.Sector12FoodNStuff,
requiredHackingSkill: 1,
serverGrowth: 5,
serverGrowth: 3000,
specialName: LocationName.Sector12FoodNStuff,
},
{
hackDifficulty: 10,
hackDifficulty: 3,
hostname: "sigma-cosmetics",
maxRamExponent: 4,
moneyAvailable: 2300000,
moneyAvailable: 70000,
networkLayer: 1,
numOpenPortsRequired: 0,
organizationName: "Sigma Cosmetics",
requiredHackingSkill: 5,
serverGrowth: 10,
serverGrowth: 3000,
},
{
hackDifficulty: 15,
hackDifficulty: 9,
hostname: "joesguns",
maxRamExponent: 4,
moneyAvailable: 2500000,
moneyAvailable: 600000,
networkLayer: 1,
numOpenPortsRequired: 0,
organizationName: LocationName.Sector12JoesGuns,
requiredHackingSkill: 10,
serverGrowth: 20,
serverGrowth: 500,
specialName: LocationName.Sector12JoesGuns,
},
{

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

@ -338,6 +338,7 @@ $(document).keyup(function(e) {
let Terminal = {
// Flags to determine whether the player is currently running a hack or an analyze
hackFlag: false,
backdoorFlag: false,
analyzeFlag: false,
actionStarted: false,
actionTime: 0,
@ -485,6 +486,14 @@ let Terminal = {
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() {
Terminal.analyzeFlag = true;
Terminal.actionTime = 1;
@ -506,14 +515,22 @@ let Terminal = {
finishAction: function(cancelled = false) {
if (Terminal.hackFlag) {
Terminal.finishHack(cancelled);
} else if (Terminal.backdoorFlag) {
Terminal.finishBackdoor(cancelled);
} else if (Terminal.analyzeFlag) {
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
finishHack: function(cancelled = false) {
if (cancelled == false) {
finishHack: function(cancelled = false) {
if (!cancelled) {
var server = Player.getCurrentServer();
// Calculate whether hack was successful
@ -530,7 +547,7 @@ let Terminal = {
hackWorldDaemon(Player.bitNodeN);
return;
}
server.manuallyHacked = true;
server.backdoorInstalled = true;
var moneyGained = calculatePercentMoneyHacked(server, Player);
moneyGained = Math.floor(server.moneyAvailable * moneyGained);
@ -548,21 +565,23 @@ let Terminal = {
} else { // Failure
// Player only gains 25% exp for failure? TODO Can change this later to balance
Player.gainHackingExp(expGainedOnFailure)
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);
post(`Failed to hack ${server.hostname}. Gained ${numeralWrapper.formatExp(expGainedOnFailure)} hacking exp`);
}
}
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) {
if (cancelled == false) {
if (!cancelled) {
let currServ = Player.getCurrentServer();
const isHacknet = currServ instanceof HacknetServer;
post(currServ.hostname + ": ");
@ -609,12 +628,6 @@ let Terminal = {
}
}
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) {
@ -728,8 +741,8 @@ let Terminal = {
return args;
},
executeCommand : function(command) {
if (Terminal.hackFlag || Terminal.analyzeFlag) {
executeCommand : function(command) {
if (Terminal.hackFlag || Terminal.backdoorFlag || Terminal.analyzeFlag) {
postError(`Cannot execute command (${command}) while an action is in progress`);
return;
}
@ -893,6 +906,24 @@ let Terminal = {
return;
}
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;
case "buy":
if (SpecialServerIps.hasOwnProperty("Darkweb Server")) {
@ -938,10 +969,10 @@ let Terminal = {
break;
}
case "cd": {
if (commandArray.length !== 2) {
if (commandArray.length > 2) {
postError("Incorrect number of arguments. Usage: cd [dir]");
} else {
let dir = commandArray[1];
let dir = commandArray.length === 2 ? commandArray[1] : "/";
let evaledDir;
if (dir === "/") {
@ -955,6 +986,12 @@ let Terminal = {
postError("Invalid path. Failed to change directories");
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;
@ -2329,7 +2366,7 @@ let Terminal = {
runningScriptObj.threads = numThreads;
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 {
postError(`Failed to start script`);
}

@ -5,6 +5,7 @@ export const TerminalHelpText: string =
"Type 'help name' to learn more about the command 'name'<br><br>" +
'alias [-g] [name="value"] Create or display Terminal aliases<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>" +
"cat [file] Display a .msg, .lit, or .txt file<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 " +
"hacking-related information such as an estimated chance to successfully hack, an estimate of how much money is " +
"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>" +
"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 " +
"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>" +
"Display message (.msg), literature (.lit), or text (.txt) files. Examples:<br><br>" +
"cat j1.msg<br>" +

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

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

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

@ -29,3 +29,5 @@ import "../css/resleeving.scss";
import "../css/treant.css";
import "../css/grid.min.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">
<button id="help-menu-header" class="mainmenu-accordion-header"> Help </button>
</li>
<li id="tutorial-tab" class="mainmenu-accordion-panel">
<button id="tutorial-menu-link"> Tutorial </button>
</li>
<li id="milestones-tab" class="mainmenu-accordion-panel">
<button id="milestones-menu-link"> Milestones </button>
</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">
<button id="options-menu-link"> Options </button>
</li>
@ -228,6 +231,10 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
<!-- Augmentations -->
<div id="augmentations-container" class="generic-menupage-container"></div>
<!-- Milestones content -->
<div id="milestones-container" class="generic-menupage-container">
</div>
<!-- Tutorial content -->
<div id="tutorial-container" class="generic-menupage-container">
<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">
</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 -->
<fieldset>
<label for="settingsLocale" class="tooltip">Locale:

@ -27,7 +27,7 @@ export function WorkerScriptAccordion(props: IProps): React.ReactElement {
const scriptRef = workerScript.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() {
killScript();

@ -24,7 +24,7 @@ function toggleHeader(open: boolean, elems: HTMLElement[], links: HTMLElement[])
elems[i].style.maxHeight = elems[i].scrollHeight + "px";
} else {
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";
} else {
links[i].style.opacity = "0";
links[i].style.maxHeight = null;
links[i].style.maxHeight = "";
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 {
const elem: HTMLElement | null = document.getElementById(id);
if (elem == null) {
@ -68,16 +68,16 @@ export function initializeMainMenuHeaders(p: IPlayer, dev: boolean=false): boole
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 links: HTMLElement[] = [MainMenuLinks.Terminal!, MainMenuLinks.ScriptEditor!, MainMenuLinks.ActiveScripts!, MainMenuLinks.CreateProgram!];
if (terminal.style.maxHeight) {
toggleHeader(false, elems, links);
createProgramNot!.style.display = "none";
createProgramNot.style.display = "none";
} else {
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";
this.classList.toggle("opened");
(this as any).classList.toggle("opened");
const elems: HTMLElement[] = [stats, factions, augmentations, hacknetnodes, 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";
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 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() {
const milestones: HTMLElement = safeGetElement("milestones-tab");
const tutorial: HTMLElement = safeGetElement("tutorial-tab");
const options: HTMLElement = safeGetElement("options-tab");
this.classList.toggle("opened");
(this as any).classList.toggle("opened");
const elems: HTMLElement[] = [tutorial, options];
const links: HTMLElement[] = [MainMenuLinks.Tutorial!, MainMenuLinks.Options!];
const elems: HTMLElement[] = [milestones, tutorial, options];
const links: HTMLElement[] = [MainMenuLinks.Milestones!, MainMenuLinks.Tutorial!, MainMenuLinks.Options!];
if (dev) {
elems.push(safeGetElement("dev-tab"));

@ -19,6 +19,7 @@ interface IMainMenuLinks {
Bladeburner: HTMLElement | null;
Corporation: HTMLElement | null;
Gang: HTMLElement | null;
Milestones: HTMLElement | null;
Tutorial: HTMLElement | null;
Options: HTMLElement | null;
DevMenu: HTMLElement | null;
@ -41,6 +42,7 @@ export const MainMenuLinks: IMainMenuLinks = {
Bladeburner: null,
Corporation: null,
Gang: null,
Milestones: null,
Tutorial: null,
Options: null,
DevMenu: null,
@ -54,7 +56,7 @@ export function initializeMainMenuLinks(): boolean {
throw new Error(`clearEventListeners() failed for element with id: ${id}`);
}
return elem!;
return elem;
}
MainMenuLinks.Terminal = safeGetLink("terminal-menu-link");
@ -73,6 +75,7 @@ export function initializeMainMenuLinks(): boolean {
MainMenuLinks.Bladeburner = safeGetLink("bladeburner-menu-link");
MainMenuLinks.Corporation = safeGetLink("corporation-menu-link");
MainMenuLinks.Gang = safeGetLink("gang-menu-link");
MainMenuLinks.Milestones = safeGetLink("milestones-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.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;
style?: object;
text: string | JSX.Element;
tooltip?: string;
tooltip?: string | JSX.Element;
}
type IInnerHTMLMarkup = {
@ -30,10 +30,16 @@ export function StdButton(props: IStdButtonProps): React.ReactElement {
}
// Tooltip will be set using inner HTML
let tooltipMarkup: IInnerHTMLMarkup | null;
let tooltip;
if (hasTooltip) {
tooltipMarkup = {
__html: props.tooltip!
if(typeof props.tooltip === 'string') {
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}
{
hasTooltip &&
<span className={"tooltiptext"} dangerouslySetInnerHTML={tooltipMarkup!}></span>
tooltip
}
</button>
)

@ -48,6 +48,11 @@ export enum Page {
*/
Augmentations = "Augmentations",
/**
* List of milestones that players should follow.
*/
Milestones = "Milestones",
/**
* 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/cs';
import 'numeral/locales/da-dk';
@ -18,7 +18,7 @@ import 'numeral/locales/ru';
class NumeralFormatter {
// Default Locale
defaultLocale: string = "en";
defaultLocale = "en";
constructor() {
this.defaultLocale = 'en';
@ -80,12 +80,12 @@ class NumeralFormatter {
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) + "%";
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");
}
@ -127,6 +127,10 @@ class NumeralFormatter {
}
return this.format(n, "0.000a");
}
formatInfiltrationSecurity(n: number): string {
return this.format(n, "0.000a");
}
}
export const numeralWrapper = new NumeralFormatter();

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

@ -5,29 +5,22 @@
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
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;
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(amt: number, source: string): void {
@ -45,7 +38,7 @@ export class MoneySourceTracker {
reset(): void {
for (const prop in this) {
if (typeof this[prop] === "number") {
this[prop] = 0;
(this[prop] as number) = 0;
}
}
}
@ -54,6 +47,11 @@ export class MoneySourceTracker {
toJSON(): any {
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;

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

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

@ -1,8 +1,9 @@
{
"compilerOptions": {
"baseUrl" : ".",
"esModuleInterop": true,
"jsx": "react",
"lib" : ["es2016", "dom", "es2017.object"],
"lib" : ["es2016", "dom", "es2017.object", "es2019"],
"module": "commonjs",
"target": "es6",
"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": []
}