Compare commits

..

7 Commits

Author SHA1 Message Date
hydroflame
52a80ad236 v0.51.6 (#905)
* 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>
2021-04-28 20:07:26 -04:00
hydroflame
b2aafea656 v0.51.5 (#848) 2021-04-21 08:20:26 -04:00
hydroflame
135df8703c V0.51.4 (#847)
* BladeBurner
    * nerfed int exp gained.

    Documentation
    * purchaseServer specifies what happens on failure.
    * Fixed typo in recommended bitnode page.
    * Removed misleading ram requirements for hacking factions.

    Netscript
    * growthAnalyze handles Infinity correctly.

    Misc.
    * Faction Augmentation will list how much reputation is required even after
      that goal has been reached.
    * Removed dollar sign in travel agency confirmation.
    * Fixed typo in alpha-omega.lit

* the game save text no longer obstruct the save game and options button

* the text editors now remember where your cursor was and restores it when loading the same script again.

* v0.51.4
2021-04-19 21:26:51 -04:00
hydroflame
4743801e86 hotfix (#846) 2021-04-18 11:33:46 -04:00
hydroflame
4e5ebcfe6f V0.51.3 (#845)
v0.51.3 - 2021-04-16 Y'all broke it on the first day (hydroflame)
-------

Passive faction reputation
* Reworked, from 1 rep / 2 minute. Now is a complicated percentage of the
  reputation you'd gain working for them. It's not op but it feels a bit
  more useful.

Netscript
* print/tprint now take any number of arguments.
* print/tprint will now print object as json.
* print/tprint now handle passing in an undefined argument properly.

Casino
* Cannot bet negative money anymore.
* Roulette max bet is a bit higher.
* Coin Flip has a small cooldown.
* All buttons reject unstrusted mouse events.

Documentation
* Changed a message that said nsjs only works on Chrome.

Bugfix
* hacknet.maxNumNodes now works for both nodes and servers.
* Fixed a bug where the popup boxes would contain data from previous popup boxes.
* .js files will also have the export async function boilerplate.

Misc.
* turned off autocomplete for the terminal text input.
* Fixed an issue on Windows+Firefox where pressing up on the terminal would
  bring the cursor to the begining of the line. (Issue #836)
* Hacknet node names is easier to handle for screen readers.
* Money spent on classes is now tracked independently of work money.
* running coding contract from the terminal will display its name.
2021-04-18 11:18:56 -04:00
hydroflame
80b703639e Small hotfix (#840)
* yesno box now correctly clean up before new content is loaded in.

* formatHp doesnt display decimal, duh

* character overview uses numeralWrapper formatHp

* minor formatting stuff

* Class spending is tracked indepedently of work money

* Made an augmentation named after myself.

* hotfix a bunch of small stuff
2021-04-12 20:03:32 -04:00
hydroflame
0afdba8f38 fixed ns imports requiring semicolon (#839) 2021-04-10 02:26:13 -04:00
114 changed files with 11134 additions and 6464 deletions

View File

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

View File

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

24
css/casino.scss Normal file
View 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
View File

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

View File

@@ -228,14 +228,15 @@ a:visited {
} }
.status-text { .status-text {
display: inline-block;
position: fixed;
z-index: 2; z-index: 2;
-webkit-animation: status-text 3s 1; -webkit-animation: status-text 3s 1;
} }
#status-text-container { #status-text-container {
background-color: transparent; background-color: transparent;
position:absolute;
top:0;
left:50%;
} }
#status-text { #status-text {

File diff suppressed because one or more lines are too long

View File

@@ -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([398,0]),o()}({341:function(n,t,o){},343:function(n,t,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){},398:function(n,t,o){"use strict";o.r(t);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),o(343),o(341)}}); !function(n){function t(t){for(var e,i,f=t[0],c=t[1],l=t[2],p=0,s=[];p<f.length;p++)i=f[p],u[i]&&s.push(u[i][0]),u[i]=0;for(e in c)Object.prototype.hasOwnProperty.call(c,e)&&(n[e]=c[e]);for(a&&a(t);s.length;)s.shift()();return r.push.apply(r,l||[]),o()}function o(){for(var n,t=0;t<r.length;t++){for(var o=r[t],e=!0,f=1;f<o.length;f++){var c=o[f];0!==u[c]&&(e=!1)}e&&(r.splice(t--,1),n=i(i.s=o[0]))}return n}var e={},u={1:0},r=[];function i(t){if(e[t])return e[t].exports;var o=e[t]={i:t,l:!1,exports:{}};return n[t].call(o.exports,o,o.exports,i),o.l=!0,o.exports}i.m=n,i.c=e,i.d=function(n,t,o){i.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:o})},i.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},i.t=function(n,t){if(1&t&&(n=i(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var o=Object.create(null);if(i.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var e in n)i.d(o,e,function(t){return n[t]}.bind(null,e));return o},i.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return i.d(t,"a",t),t},i.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},i.p="";var f=window.webpackJsonp=window.webpackJsonp||[],c=f.push.bind(f);f.push=t,f=f.slice();for(var l=0;l<f.length;l++)t(f[l]);var a=c;r.push([842,0]),o()}({781:function(n,t,o){},783:function(n,t,o){},785:function(n,t,o){},787:function(n,t,o){},789:function(n,t,o){},791:function(n,t,o){},793:function(n,t,o){},795:function(n,t,o){},797:function(n,t,o){},799:function(n,t,o){},801:function(n,t,o){},803:function(n,t,o){},805:function(n,t,o){},807:function(n,t,o){},809:function(n,t,o){},811:function(n,t,o){},813:function(n,t,o){},815:function(n,t,o){},817:function(n,t,o){},819:function(n,t,o){},821:function(n,t,o){},823:function(n,t,o){},825:function(n,t,o){},827:function(n,t,o){},829:function(n,t,o){},831:function(n,t,o){},833:function(n,t,o){},835:function(n,t,o){},837:function(n,t,o){},839:function(n,t,o){},842:function(n,t,o){"use strict";o.r(t);o(841),o(839),o(837),o(835),o(833),o(831),o(829),o(827),o(825),o(823),o(821),o(819),o(817),o(815),o(813),o(811),o(809),o(807),o(805),o(803),o(801),o(799),o(797),o(795),o(793),o(791),o(789),o(787),o(785),o(783),o(781)}});
//# sourceMappingURL=engineStyle.bundle.js.map //# sourceMappingURL=engineStyle.bundle.js.map

33
dist/engineStyle.css vendored
View File

@@ -250,13 +250,14 @@ a:visited {
opacity: 0; } } opacity: 0; } }
.status-text { .status-text {
display: inline-block;
position: fixed;
z-index: 2; z-index: 2;
-webkit-animation: status-text 3s 1; } -webkit-animation: status-text 3s 1; }
#status-text-container { #status-text-container {
background-color: transparent; } background-color: transparent;
position: absolute;
top: 0;
left: 50%; }
#status-text { #status-text {
background-color: transparent; background-color: transparent;
@@ -5006,5 +5007,31 @@ html {
margin-left: 0px; margin-left: 0px;
margin-right: 0px; } margin-right: 0px; }
.casino-card {
padding: 10px;
border: solid 1px #808080;
background-color: white;
display: inline-block;
border-radius: 10px;
font-size: 14pt;
text-align: center;
margin: 3px;
font-weight: bold; }
.casino-card .value {
font-size: 15pt;
font-family: sans-serif; }
.casino-card.red {
color: red; }
.casino-card.black {
color: black; }
#milestones-container {
position: fixed;
padding: 6px;
width: 60%; }
/*# sourceMappingURL=engineStyle.css.map*/ /*# sourceMappingURL=engineStyle.css.map*/

78
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

@@ -3,6 +3,152 @@
Changelog Changelog
========= =========
v0.51.6 - 2021-04-28 Backdoor! (Community)
-------
**Backdoor**
* a new terminal command, backdoor, has been added to help differentiate
between the terminal hack command and the netscript hack function. (@dewint)
**Milestones**
* A new tab under the Help menu has been added to guide players through the
game.
**Casino**
* Blackjack has been added (@BigD)
**Netscript**
* 'prompt' now converts input to JSON.
* 'getRunningScript' is a new netscript function that returns a bunch of
data related to a running script.
**Coding contracts**
* trivial puzzles should no longer appear.
**Infiltration**
* All numbers are formatted like the rest of the game.
**Misc.**
* Server security is capped at 100.
* Added option to quit a job.
* 'cd' no longer works on unexistent folders.
* cd with no arguments brings you back to top level folder (@Andreas)
* 'softReset' documentation udpated.
* Money tracker now accounts for going to the hospital manually.
* codemirror is now the default editor (for new save files)
* fix typo in dark web help text (@Rodeth)
* so many documentation and typos fixes (@Pimgd)
* A corruption visual effect has been added to location with servers that
have backdoor installed. (@dewint)
v0.51.5 - 2021-04-20 Flags! (hydroflame)
----------------------------------------
**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.
**Gang**
* Nerfed ascension mechanic once again :(
**Misc.**
* Souce-File typo fix
* Fix 'while you were away' screen.
* Bladeburner team size can no longer be set to negative amounts.
v0.51.4 - 2021-04-19 Manual hacking is fun (hydroflame)
-------------------------------------------------------
**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**
* nerfed int exp gained.
**Documentation**
* purchaseServer specifies what happens on failure.
* Fixed typo in recommended bitnode page.
* Removed misleading ram requirements for hacking factions.
**Netscript**
* growthAnalyze handles Infinity correctly.
**Misc.**
* Faction Augmentation will list how much reputation is required even after
that goal has been reached.
* Removed dollar sign in travel agency confirmation dialog box.
* Fixed typo in alpha-omega.lit
* the 'Game saved!' text no longer blocks the save game/options button.
* The text editor now remembers the location of your cursor and restores it.
* skills are recalculated instantly.
* Fix typo in Operation Zero description.
v0.51.3 - 2021-04-16 Y'all broke it on the first day (hydroflame)
-----------------------------------------------------------------
**Passive faction reputation**
* Reworked, from 1 rep / 2 minute. Now is a complicated percentage of the
reputation you'd gain working for them. It's not op but it feels a bit
more useful.
**Netscript**
* print/tprint now take any number of arguments.
* print/tprint will now print object as json.
* print/tprint now handle passing in an undefined argument properly.
**Casino**
* Cannot bet negative money anymore.
* Roulette max bet is a bit higher.
* Coin Flip has a small cooldown.
* All buttons reject unstrusted mouse events.
**Documentation**
* Changed a message that said nsjs only works on Chrome.
**Bugfix**
* hacknet.maxNumNodes now works for both nodes and servers.
* Fixed a bug where the popup boxes would contain data from previous popup boxes.
* .js files will also have the 'export async function' boilerplate.
**Misc.**
* turned off web form autocomplete for the terminal text input.
* Fixed an issue on Windows+Firefox where pressing up on the terminal would
bring the cursor to the begining of the line. (Issue #836)
* Hacknet node names is easier to handle for screen readers.
* Money spent on classes is now tracked independently of work money.
* running coding contract from the terminal will display its name.
v0.51.2 - 2021-04-09 Vegas, Baby! (hydroflame) v0.51.2 - 2021-04-09 Vegas, Baby! (hydroflame)
---------------------------------------------- ----------------------------------------------

View File

@@ -66,7 +66,7 @@ documentation_title = '{0} Documentation'.format(project)
# The short X.Y version. # The short X.Y version.
version = '0.51' version = '0.51'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = '0.51.2' release = '0.51.5'
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.

View File

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

View File

@@ -278,6 +278,7 @@ Description
hashes, which can be spent on a variety of different upgrades. hashes, which can be spent on a variety of different upgrades.
In this BitNode: In this BitNode:
* Your stats are significantly decreased * Your stats are significantly decreased
* You cannnot purchase additional servers * You cannnot purchase additional servers
* Hacking is significantly less profitable * Hacking is significantly less profitable
@@ -292,7 +293,7 @@ Source-File
* Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode * Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode
(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT (Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT
when installing Augmentation when installing Augmentations.)
Difficulty Difficulty
Hard Hard
@@ -312,6 +313,7 @@ Description
2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks synchronously 2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks synchronously
In this BitNode: In this BitNode:
* Your stats are significantly decreased * Your stats are significantly decreased
* All methods of gaining money are half as profitable (except Stock Market) * All methods of gaining money are half as profitable (except Stock Market)
* Purchased servers are more expensive, have less max RAM, and a lower maximum limit * Purchased servers are more expensive, have less max RAM, and a lower maximum limit

View File

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

View File

@@ -0,0 +1,40 @@
flags() Netscript Function
============================
.. js:function:: flags(data)
:RAM cost: 0 GB
:param data array of pairs of strings: Flags definition.
:returns: Object containing all the flags that were parsed or default.
The flag definition is an array of pairs of values, the first value is the
name of the flag, the 2nd value is the default value for that flag.
The return object is a map containing flag names to the value. It also
contains the special field '_' which contains all arguments that were not flags.
Example:
.. code-block:: javascript
/* example.script
var data = flags([
['delay', 0], // a default number means this flag is a number
['server', 'foodnstuff'], // a default string means this flag is a string
['exclude', []], // a default array means this flag is a default array of string
['help', false], // a default boolean means this flag is a boolean
]);
tprint(data);
*/
[home ~/]> run example.script
{"_":[],"delay":0,"server":"foodnstuff"}
[home ~/]> run example.script --delay 3000
{"_":[],"server":"foodnstuff","delay":3000}
[home ~/]> run example.script --delay 3000 --server harakiri-sushi
{"_":[],"delay":3000,"server":"harakiri-sushi"}
[home ~/]> run example.script --delay 3000 --server harakiri-sushi hello world
{"_":["hello","world"],"delay":3000,"server":"harakiri-sushi"}
[home ~/]> run example.script --delay 3000 --server harakiri-sushi hello world --exclude a --exclude b
{"_":["hello","world"],"delay":3000,"server":"harakiri-sushi","exclude":["a","b"]}
[home ~/]> run example.script --help
{"_":[],"delay":0,"server":"foodnstuff","exclude":[],"help":true}

View File

@@ -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"

View File

@@ -1,11 +1,11 @@
isRunning() Netscript Function isRunning() Netscript Function
============================== ==============================
.. js:function:: isRunning(filename, hostname, [args...]) .. js:function:: isRunning(filename[, hostname=current hostname[, args...]])
:RAM cost: 0.1 GB :RAM cost: 0.1 GB
:param string filename: Filename of script to check. case-sensitive. :param string filename: Filename of script to check. case-sensitive.
:param string hostname: Hostname of target server. :param string hostname: Hostname of target server. Defaults to current server
:param args...: Arguments to specify/identify which scripts to search for :param args...: Arguments to specify/identify which scripts to search for
:returns: ``true`` if that script with those args is running on that server. :returns: ``true`` if that script with those args is running on that server.
@@ -38,3 +38,17 @@ isRunning() Netscript Function
.. code-block:: javascript .. code-block:: javascript
isRunning("foo.script", "joesguns", 1, 5, "test"); isRunning("foo.script", "joesguns", 1, 5, "test");
.. js:function:: isRunning(scriptPid)
:RAM cost: 0.1 GB
:param number scriptPid: PID of the script to check.
Same as the above version but with pid.
Example:
.. code-block:: javascript
isRunning(39);

View File

@@ -1,15 +1,16 @@
print() Netscript Function print() Netscript Function
=========================== ===========================
.. js:function:: print(x) .. js:function:: print(args...)
:RAM cost: 0 GB :RAM cost: 0 GB
:param x: Value to be printed. :param args: Values to be printed.
Prints a value or a variable to the script's logs. Prints any number of values to the script's logs.
Example: Example:
.. code-block:: javascript .. code-block:: javascript
print("Hello world!"); // Prints "Hello world!" in the logs. print("Hello world!"); // Prints "Hello world!" in the logs.
print({a:5}); // Prints '{"a":5}' in the logs.

View File

@@ -7,7 +7,7 @@ purchaseServer() Netscript Function
:param string hostname: Hostname of the purchased server. :param string hostname: Hostname of the purchased server.
:param number ram: Amount of RAM of the purchased server. Must be a power of :param number ram: Amount of RAM of the purchased server. Must be a power of
2. Maximum value of :doc:`getPurchasedServerMaxRam<getPurchasedServerMaxRam>` 2. Maximum value of :doc:`getPurchasedServerMaxRam<getPurchasedServerMaxRam>`
:returns: The hostname of the newly purchased server. :returns: The hostname of the newly purchased server. Empty string on failure.
Purchased a server with the specified hostname and amount of RAM. Purchased a server with the specified hostname and amount of RAM.

View File

@@ -1,7 +1,7 @@
tail() Netscript Function tail() Netscript Function
================================== ==================================
.. js:function:: tail([fn[, hostname=current hostname[, [...args]]]) .. js:function:: tail([fn[, hostname=current hostname[, ...args]])
:RAM cost: 0 GB :RAM cost: 0 GB
:param string fn: Optional. Filename of script to get logs from. :param string fn: Optional. Filename of script to get logs from.
@@ -29,3 +29,33 @@ tail() Netscript Function
// Open logs from foo.script on the foodnstuff server that was run with the arguments [1, "test"] // Open logs from foo.script on the foodnstuff server that was run with the arguments [1, "test"]
tail("foo.script", "foodnstuff", 1, "test"); tail("foo.script", "foodnstuff", 1, "test");
.. js:function:: tail(scriptPid)
:RAM cost: 0 GB
:param number scriptPid: PID of the script to tail.
Opens a script's logs by pid
Example:
.. code-block:: javascript
// Open logs from process with id 42
tail(42);
// 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();

View File

@@ -1,15 +1,16 @@
tprint() Netscript Function tprint() Netscript Function
=========================== ===========================
.. js:function:: tprint(x) .. js:function:: tprint(args...)
:RAM cost: 0 GB :RAM cost: 0 GB
:param x: Value to be printed :param args: Values to be printed
Prints a value or a variable to the Terminal. Prints any number of values to the Terminal.
Example: Example:
.. code-block:: javascript .. code-block:: javascript
tprint("Hello world!"); // Prints "Hello world!" to the terminal. tprint("Hello world!"); // Prints "Hello world!" to the terminal.
tprint({a:5}); // Prints '{"a":5}' to the terminal.

View File

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

View File

@@ -65,6 +65,6 @@ Here is a short summary of the differences between Netscript 1.0 and Netscript 2
* Supports (almost) all features of modern JavaScript * Supports (almost) all features of modern JavaScript
* Extremely fast - code is executed as an Async Function * Extremely fast - code is executed as an Async Function
* Currently only works with Google Chrome browser * Works on most modern browsers.
* Each script becomes a module and therefore all instances of that script can easily * Each script becomes a module and therefore all instances of that script can easily
share data between each other (essentially global/static variables) share data between each other (essentially global/static variables)

View File

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

View File

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

View File

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

13764
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@@ -89,6 +89,7 @@ export let AugmentationNames: IMap<string> = {
BrachiBlades: "BrachiBlades", BrachiBlades: "BrachiBlades",
BionicArms: "Bionic Arms", BionicArms: "Bionic Arms",
SNA: "Social Negotiation Assistant (S.N.A)", SNA: "Social Negotiation Assistant (S.N.A)",
HydroflameLeftArm: "Hydroflame Left Arm",
EsperEyewear: "EsperTech Bladeburner Eyewear", EsperEyewear: "EsperTech Bladeburner Eyewear",
EMS4Recombination: "EMS-4 Recombination", EMS4Recombination: "EMS-4 Recombination",
OrionShoulder: "ORION-MKIV Shoulder", OrionShoulder: "ORION-MKIV Shoulder",

View File

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

View File

@@ -234,7 +234,7 @@ BitNodes["BitNode11"] = new BitNode(11, "The Big Crash", "Okay. Sell it all.",
"Level 3: 56%"); "Level 3: 56%");
BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.", BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.",
"To iterate is human, to recurse divine.<br><br>" + "To iterate is human, to recurse divine.<br><br>" +
"Every time this BitNode is destroyed, it becomes slightly harder. Destroying this BitNode will give you Souce-File 12, or " + "Every time this BitNode is destroyed, it becomes slightly harder. Destroying this BitNode will give you Source-File 12, or " +
"if you already have this Source-File it will upgrade its level. There is no maximum level for Source-File 12. Each level " + "if you already have this Source-File it will upgrade its level. There is no maximum level for Source-File 12. Each level " +
"of Source-File 12 will increase all of your multipliers by 1%. This effect is multiplicative with itself. " + "of Source-File 12 will increase all of your multipliers by 1%. This effect is multiplicative with itself. " +
"In other words, level N of this Source-File will result in a multiplier of 1.01^N (or 0.99^N for multipliers that decrease)"); "In other words, level N of this Source-File will result in a multiplier of 1.01^N (or 0.99^N for multipliers that decrease)");

View File

@@ -1069,7 +1069,11 @@ Bladeburner.prototype.gainActionStats = function(action, success) {
Player.gainDexterityExp(unweightedGain * action.weights.dex * Player.dexterity_exp_mult * skillMult); Player.gainDexterityExp(unweightedGain * action.weights.dex * Player.dexterity_exp_mult * skillMult);
Player.gainAgilityExp(unweightedGain * action.weights.agi * Player.agility_exp_mult * skillMult); Player.gainAgilityExp(unweightedGain * action.weights.agi * Player.agility_exp_mult * skillMult);
Player.gainCharismaExp(unweightedGain * action.weights.cha * Player.charisma_exp_mult * skillMult); Player.gainCharismaExp(unweightedGain * action.weights.cha * Player.charisma_exp_mult * skillMult);
Player.gainIntelligenceExp(unweightedIntGain * action.weights.int * skillMult); let intExp = unweightedIntGain * action.weights.int * skillMult;
if (intExp > 1) {
intExp = Math.pow(intExp, 0.8);
}
Player.gainIntelligenceExp(intExp);
} }
Bladeburner.prototype.randomEvent = function() { Bladeburner.prototype.randomEvent = function() {
@@ -2071,8 +2075,8 @@ Bladeburner.prototype.updateOperationsUIElement = function(el, action) {
innerText:"Confirm", class:"a-link-button", innerText:"Confirm", class:"a-link-button",
clickListener:() => { clickListener:() => {
var num = Math.round(parseFloat(input.value)); var num = Math.round(parseFloat(input.value));
if (isNaN(num)) { if (isNaN(num) || num < 0) {
dialogBoxCreate("Invalid value entered for number of Team Members (must be numeric)") dialogBoxCreate("Invalid value entered for number of Team Members (must be numeric, positive)")
} else { } else {
action.teamCount = num; action.teamCount = num;
this.updateOperationsUIElement(el, action); this.updateOperationsUIElement(el, action);
@@ -2223,8 +2227,8 @@ Bladeburner.prototype.updateBlackOpsUIElement = function(el, action) {
innerText:"Confirm", class:"a-link-button", innerText:"Confirm", class:"a-link-button",
clickListener:() => { clickListener:() => {
var num = Math.round(parseFloat(input.value)); var num = Math.round(parseFloat(input.value));
if (isNaN(num)) { if (isNaN(num) || num < 0) {
dialogBoxCreate("Invalid value entered for number of Team Members (must be numeric)") dialogBoxCreate("Invalid value entered for number of Team Members (must be numeric, positive)")
} else { } else {
action.teamCount = num; action.teamCount = num;
this.updateBlackOpsUIElement(el, action); this.updateBlackOpsUIElement(el, action);
@@ -3207,7 +3211,7 @@ Bladeburner.prototype.setTeamSizeNetscriptFn = function(type, name, size, worker
} }
const sanitizedSize = Math.round(size); const sanitizedSize = Math.round(size);
if (isNaN(sanitizedSize)) { if (isNaN(sanitizedSize) || sanitizedSize < 0) {
workerScript.log("bladeburner.setTeamSize", `Invalid size: ${size}`); workerScript.log("bladeburner.setTeamSize", `Invalid size: ${size}`);
return -1; return -1;
} }

View File

@@ -21,7 +21,7 @@ export const BlackOperations: IMap<BlackOperation> = {};
BlackOperations["Operation Zero"] = new BlackOperation({ BlackOperations["Operation Zero"] = new BlackOperation({
name:"Operation Zero", name:"Operation Zero",
desc:"AeroCorp is one of the world's largest defense contractors. " + desc:"AeroCorp is one of the world's largest defense contractors. " +
"It's leader, Steve Watataki, is thought to be a supporter of " + "Its leader, Steve Watataki, is thought to be a supporter of " +
"Synthoid rights. He must be removed.<br><br>" + "Synthoid rights. He must be removed.<br><br>" +
"The goal of Operation Zero is to covertly infiltrate AeroCorp and " + "The goal of Operation Zero is to covertly infiltrate AeroCorp and " +
"uncover any incriminating evidence or " + "uncover any incriminating evidence or " +

424
src/Casino/Blackjack.tsx Normal file
View 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>
)
}
}

View File

@@ -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}`;
}
}

View File

@@ -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;
}
}

View File

@@ -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) ])
}
}

View File

@@ -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>
)
}

View File

@@ -5,10 +5,11 @@
*/ */
import * as React from "react"; import * as React from "react";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { StdButton } from "../ui/React/StdButton"; import { StdButton } from "../ui/React/StdButton";
import { BadRNG } from "./RNG"; import { BadRNG } from "./RNG";
import { Game } from "./Game"; import { Game } from "./Game";
import { trusted } from "./utils";
type IProps = { type IProps = {
p: IPlayer; p: IPlayer;
@@ -18,8 +19,10 @@ type IState = {
investment: number; investment: number;
result: any; result: any;
status: string; status: string;
playLock: boolean;
} }
const minPlay = 0;
const maxPlay = 10e3; const maxPlay = 10e3;
export class CoinFlip extends Game<IProps, IState> { export class CoinFlip extends Game<IProps, IState> {
@@ -31,6 +34,7 @@ export class CoinFlip extends Game<IProps, IState> {
investment: 1000, investment: 1000,
result: <span> </span>, result: <span> </span>,
status: '', status: '',
playLock: false,
}; };
this.play = this.play.bind(this); this.play = this.play.bind(this);
@@ -40,11 +44,14 @@ export class CoinFlip extends Game<IProps, IState> {
updateInvestment(e: React.FormEvent<HTMLInputElement>) { updateInvestment(e: React.FormEvent<HTMLInputElement>) {
let investment: number = parseInt(e.currentTarget.value); let investment: number = parseInt(e.currentTarget.value);
if (isNaN(investment)) { if (isNaN(investment)) {
investment = 1000; investment = minPlay;
} }
if (investment > maxPlay) { if (investment > maxPlay) {
investment = maxPlay; investment = maxPlay;
} }
if (investment < minPlay) {
investment = minPlay;
}
this.setState({investment: investment}); this.setState({investment: investment});
} }
@@ -61,7 +68,9 @@ export class CoinFlip extends Game<IProps, IState> {
this.setState({ this.setState({
result: <span className={correct ? "text" : "failure"}>{letter}</span>, result: <span className={correct ? "text" : "failure"}>{letter}</span>,
status: correct ? " win!" : "lose!", status: correct ? " win!" : "lose!",
playLock: true,
}); });
setTimeout(()=>this.setState({playLock: false}), 250);
if (correct) { if (correct) {
this.win(this.props.p, this.state.investment); this.win(this.props.p, this.state.investment);
} else { } else {
@@ -81,8 +90,8 @@ export class CoinFlip extends Game<IProps, IState> {
++<br /> ++<br />
</pre> </pre>
<span className="text">Play for: </span><input type="number" className='text-input' onChange={this.updateInvestment} value={this.state.investment} /><br /> <span className="text">Play for: </span><input type="number" className='text-input' onChange={this.updateInvestment} value={this.state.investment} /><br />
<StdButton onClick={() => this.play('H')} text={"Head!"} /> <StdButton onClick={trusted(() => this.play('H'))} text={"Head!"} disabled={this.state.playLock} />
<StdButton onClick={() => this.play('T')} text={"Tail!"} /> <StdButton onClick={trusted(() => this.play('T'))} text={"Tail!"} disabled={this.state.playLock} />
<h1>{this.state.status}</h1> <h1>{this.state.status}</h1>
</> </>
} }

View File

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

View File

@@ -1,10 +1,11 @@
import * as React from "react"; import * as React from "react";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { StdButton } from "../ui/React/StdButton"; import { StdButton } from "../ui/React/StdButton";
import { Money } from "../ui/React/Money"; import { Money } from "../ui/React/Money";
import { Game } from "./Game"; import { Game } from "./Game";
import { WHRNG } from "./RNG"; import { WHRNG } from "./RNG";
import { trusted } from "./utils";
type IProps = { type IProps = {
p: IPlayer; p: IPlayer;
@@ -19,7 +20,8 @@ type IState = {
strategy: Strategy; strategy: Strategy;
} }
const maxPlay = 1e6; const minPlay = 0;
const maxPlay = 1e7;
function isRed(n: number): boolean { function isRed(n: number): boolean {
return [1, 3, 5, 7, 9, 12, 14, 16, 18, 19, return [1, 3, 5, 7, 9, 12, 14, 16, 18, 19,
@@ -165,10 +167,13 @@ export class Roulette extends Game<IProps, IState> {
updateInvestment(e: React.FormEvent<HTMLInputElement>) { updateInvestment(e: React.FormEvent<HTMLInputElement>) {
let investment: number = parseInt(e.currentTarget.value); let investment: number = parseInt(e.currentTarget.value);
if (isNaN(investment)) { if (isNaN(investment)) {
investment = 1000; investment = minPlay;
} }
if (investment > maxPlay) { if (investment > maxPlay) {
investment = maxPlay investment = maxPlay;
}
if (investment < minPlay) {
investment = minPlay;
} }
this.setState({investment: investment}); this.setState({investment: investment});
} }
@@ -226,62 +231,62 @@ export class Roulette extends Game<IProps, IState> {
<table> <table>
<tbody> <tbody>
<tr> <tr>
<td><StdButton text={"3"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(3))} /></td> <td><StdButton text={"3"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(3)))} /></td>
<td><StdButton text={"6"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(6))} /></td> <td><StdButton text={"6"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(6)))} /></td>
<td><StdButton text={"9"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(9))} /></td> <td><StdButton text={"9"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(9)))} /></td>
<td><StdButton text={"12"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(12))} /></td> <td><StdButton text={"12"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(12)))} /></td>
<td><StdButton text={"15"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(15))} /></td> <td><StdButton text={"15"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(15)))} /></td>
<td><StdButton text={"18"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(18))} /></td> <td><StdButton text={"18"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(18)))} /></td>
<td><StdButton text={"21"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(21))} /></td> <td><StdButton text={"21"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(21)))} /></td>
<td><StdButton text={"24"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(24))} /></td> <td><StdButton text={"24"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(24)))} /></td>
<td><StdButton text={"27"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(27))} /></td> <td><StdButton text={"27"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(27)))} /></td>
<td><StdButton text={"30"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(30))} /></td> <td><StdButton text={"30"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(30)))} /></td>
<td><StdButton text={"33"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(33))} /></td> <td><StdButton text={"33"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(33)))} /></td>
<td><StdButton text={"36"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(36))} /></td> <td><StdButton text={"36"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(36)))} /></td>
</tr> </tr>
<tr> <tr>
<td><StdButton text={"2"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(2))} /></td> <td><StdButton text={"2"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(2)))} /></td>
<td><StdButton text={"5"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(5))} /></td> <td><StdButton text={"5"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(5)))} /></td>
<td><StdButton text={"8"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(8))} /></td> <td><StdButton text={"8"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(8)))} /></td>
<td><StdButton text={"11"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(11))} /></td> <td><StdButton text={"11"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(11)))} /></td>
<td><StdButton text={"14"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(14))} /></td> <td><StdButton text={"14"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(14)))} /></td>
<td><StdButton text={"17"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(17))} /></td> <td><StdButton text={"17"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(17)))} /></td>
<td><StdButton text={"20"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(20))} /></td> <td><StdButton text={"20"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(20)))} /></td>
<td><StdButton text={"23"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(23))} /></td> <td><StdButton text={"23"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(23)))} /></td>
<td><StdButton text={"26"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(26))} /></td> <td><StdButton text={"26"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(26)))} /></td>
<td><StdButton text={"29"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(29))} /></td> <td><StdButton text={"29"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(29)))} /></td>
<td><StdButton text={"32"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(32))} /></td> <td><StdButton text={"32"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(32)))} /></td>
<td><StdButton text={"35"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(35))} /></td> <td><StdButton text={"35"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(35)))} /></td>
</tr> </tr>
<tr> <tr>
<td><StdButton text={"1"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(1))} /></td> <td><StdButton text={"1"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(1)))} /></td>
<td><StdButton text={"4"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(4))} /></td> <td><StdButton text={"4"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(4)))} /></td>
<td><StdButton text={"7"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(7))} /></td> <td><StdButton text={"7"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(7)))} /></td>
<td><StdButton text={"10"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(10))} /></td> <td><StdButton text={"10"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(10)))} /></td>
<td><StdButton text={"13"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(13))} /></td> <td><StdButton text={"13"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(13)))} /></td>
<td><StdButton text={"16"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(16))} /></td> <td><StdButton text={"16"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(16)))} /></td>
<td><StdButton text={"19"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(19))} /></td> <td><StdButton text={"19"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(19)))} /></td>
<td><StdButton text={"22"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(22))} /></td> <td><StdButton text={"22"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(22)))} /></td>
<td><StdButton text={"25"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(25))} /></td> <td><StdButton text={"25"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(25)))} /></td>
<td><StdButton text={"28"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(28))} /></td> <td><StdButton text={"28"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(28)))} /></td>
<td><StdButton text={"31"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(31))} /></td> <td><StdButton text={"31"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(31)))} /></td>
<td><StdButton text={"34"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(34))} /></td> <td><StdButton text={"34"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(34)))} /></td>
</tr> </tr>
<tr> <tr>
<td colSpan={4}><StdButton text={"1 to 12"} disabled={!this.state.canPlay} onClick={()=>this.play(strategies.Third1)} /></td> <td colSpan={4}><StdButton text={"1 to 12"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(strategies.Third1))} /></td>
<td colSpan={4}><StdButton text={"13 to 24"} disabled={!this.state.canPlay} onClick={()=>this.play(strategies.Third2)} /></td> <td colSpan={4}><StdButton text={"13 to 24"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(strategies.Third2))} /></td>
<td colSpan={4}><StdButton text={"25 to 36"} disabled={!this.state.canPlay} onClick={()=>this.play(strategies.Third3)} /></td> <td colSpan={4}><StdButton text={"25 to 36"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(strategies.Third3))} /></td>
</tr> </tr>
<tr> <tr>
<td colSpan={2}><StdButton text={"Red"} disabled={!this.state.canPlay} onClick={()=>this.play(strategies.Red)} /></td> <td colSpan={2}><StdButton text={"Red"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(strategies.Red))} /></td>
<td colSpan={2}><StdButton text={"Black"} disabled={!this.state.canPlay} onClick={()=>this.play(strategies.Black)} /></td> <td colSpan={2}><StdButton text={"Black"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(strategies.Black))} /></td>
<td colSpan={2}><StdButton text={"Odd"} disabled={!this.state.canPlay} onClick={()=>this.play(strategies.Odd)} /></td> <td colSpan={2}><StdButton text={"Odd"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(strategies.Odd))} /></td>
<td colSpan={2}><StdButton text={"Even"} disabled={!this.state.canPlay} onClick={()=>this.play(strategies.Even)} /></td> <td colSpan={2}><StdButton text={"Even"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(strategies.Even))} /></td>
<td colSpan={2}><StdButton text={"High"} disabled={!this.state.canPlay} onClick={()=>this.play(strategies.High)} /></td> <td colSpan={2}><StdButton text={"High"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(strategies.High))} /></td>
<td colSpan={2}><StdButton text={"Low"} disabled={!this.state.canPlay} onClick={()=>this.play(strategies.Low)} /></td> <td colSpan={2}><StdButton text={"Low"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(strategies.Low))} /></td>
</tr> </tr>
<tr> <tr>
<td><StdButton text={"0"} disabled={!this.state.canPlay} onClick={()=>this.play(Single(0))} /></td> <td><StdButton text={"0"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(0)))} /></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -4,7 +4,8 @@ import { IPlayer } from "../PersonObjects/IPlayer";
import { StdButton } from "../ui/React/StdButton"; import { StdButton } from "../ui/React/StdButton";
import { Money } from "../ui/React/Money"; import { Money } from "../ui/React/Money";
import { WHRNG } from "./RNG"; import { WHRNG } from "./RNG";
import { Game } from "./Game"; import { Game } from "./Game";
import { trusted } from "./utils";
type IProps = { type IProps = {
p: IPlayer; p: IPlayer;
@@ -57,6 +58,7 @@ const payLines = [
[[1, 0], [2, 1], [2, 2], [2, 3], [1, 4]], [[1, 0], [2, 1], [2, 2], [2, 3], [1, 4]],
]; ];
const minPlay = 0;
const maxPlay = 1e6; const maxPlay = 1e6;
export class SlotMachine extends Game<IProps, IState> { export class SlotMachine extends Game<IProps, IState> {
@@ -184,11 +186,14 @@ export class SlotMachine extends Game<IProps, IState> {
updateInvestment(e: React.FormEvent<HTMLInputElement>) { updateInvestment(e: React.FormEvent<HTMLInputElement>) {
let investment: number = parseInt(e.currentTarget.value); let investment: number = parseInt(e.currentTarget.value);
if (isNaN(investment)) { if (isNaN(investment)) {
investment = 1000; investment = minPlay;
} }
if (investment > maxPlay) { if (investment > maxPlay) {
investment = maxPlay; investment = maxPlay;
} }
if (investment < minPlay) {
investment = minPlay;
}
this.setState({investment: investment}); this.setState({investment: investment});
} }
@@ -205,7 +210,7 @@ export class SlotMachine extends Game<IProps, IState> {
++<br /> ++<br />
</pre> </pre>
<input type="number" className='text-input' onChange={this.updateInvestment} placeholder={"Amount to play"} value={this.state.investment} disabled={!this.state.canPlay} /> <input type="number" className='text-input' onChange={this.updateInvestment} placeholder={"Amount to play"} value={this.state.investment} disabled={!this.state.canPlay} />
<StdButton onClick={this.play} text={"Spin!"} disabled={!this.state.canPlay} /> <StdButton onClick={trusted(this.play)} text={"Spin!"} disabled={!this.state.canPlay} />
<h1>{this.state.status}</h1> <h1>{this.state.status}</h1>
<h2>Pay lines</h2> <h2>Pay lines</h2>
<pre> <pre>

8
src/Casino/utils.ts Normal file
View File

@@ -0,0 +1,8 @@
import * as React from "react";
export function trusted(f: () => void): (event: React.MouseEvent<HTMLElement, MouseEvent>) => any {
return function(event: React.MouseEvent<HTMLElement, MouseEvent>): any {
if(!event.isTrusted) return;
f();
};
}

View File

@@ -181,6 +181,9 @@ export class CodingContract {
return new Promise<CodingContractResult>((resolve: Function, reject: Function) => { return new Promise<CodingContractResult>((resolve: Function, reject: Function) => {
const contractType: CodingContractType = CodingContractTypes[this.type]; const contractType: CodingContractType = CodingContractTypes[this.type];
const popupId: string = `coding-contract-prompt-popup-${this.fn}`; const popupId: string = `coding-contract-prompt-popup-${this.fn}`;
const title: HTMLElement = createElement("h1", {
innerHTML: this.type,
});
const txt: HTMLElement = createElement("p", { const txt: HTMLElement = createElement("p", {
innerHTML: ["You are attempting to solve a Coding Contract. You have", innerHTML: ["You are attempting to solve a Coding Contract. You have",
`${this.getMaxNumTries() - this.tries} tries remaining,`, `${this.getMaxNumTries() - this.tries} tries remaining,`,
@@ -225,7 +228,7 @@ export class CodingContract {
innerText: "Cancel", innerText: "Cancel",
}); });
const lineBreak: HTMLElement = createElement("br"); const lineBreak: HTMLElement = createElement("br");
createPopup(popupId, [txt, lineBreak, lineBreak, answerInput, solveBtn, cancelBtn]); createPopup(popupId, [title, lineBreak, txt, lineBreak, lineBreak, answerInput, solveBtn, cancelBtn]);
answerInput.focus(); answerInput.focus();
}); });
} }

View File

@@ -6,13 +6,13 @@
import { IMap } from "./types"; import { IMap } from "./types";
export let CONSTANTS: IMap<any> = { export let CONSTANTS: IMap<any> = {
Version: "0.51.2", Version: "0.51.6",
/** Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience /** Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience
* and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then * and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then
* the player will have this level assuming no multipliers. Multipliers can cause skills to go above this. * the player will have this level assuming no multipliers. Multipliers can cause skills to go above this.
*/ */
MaxSkillLevel: 975, MaxSkillLevel: 975,
// Milliseconds per game cycle // Milliseconds per game cycle
MilliPerCycle: 200, MilliPerCycle: 200,
@@ -218,7 +218,7 @@ export let CONSTANTS: IMap<any> = {
CrimeHeist: "pull off the ultimate heist", CrimeHeist: "pull off the ultimate heist",
// Coding Contract // Coding Contract
// TODO Move this into Coding contract impelmentation? // TODO: Move this into Coding contract implementation?
CodingContractBaseFactionRepGain: 2500, CodingContractBaseFactionRepGain: 2500,
CodingContractBaseCompanyRepGain: 4000, CodingContractBaseCompanyRepGain: 4000,
CodingContractBaseMoneyGain: 75e6, CodingContractBaseMoneyGain: 75e6,
@@ -228,16 +228,42 @@ export let CONSTANTS: IMap<any> = {
LatestUpdate: LatestUpdate:
` `
v0.51.2 - 2021-04-09 Vegas, Baby! (hydroflame) v0.51.6 - 2021-04-28 Backdoor! (Community)
------- -------
New location: The Iker Molina Casino Backdoor
* A casino opened in Aevum. However the house is rumored to cheat. If only * a new terminal command, backdoor, has been added to help differentiate
we could give them a taste of their own medicine. 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. Misc.
* Link to discord added under options * Server security is capped at 100.
* 'getMemberInformation' doc updated, oops * Added option to quit a job.
* tech vendor now handle max ram and cores. * '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)
` `
} }

View File

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

View File

@@ -15,6 +15,11 @@ import { Factions } from "./Factions";
import { HackingMission, setInMission } from "../Missions"; import { HackingMission, setInMission } from "../Missions";
import { Player } from "../Player"; import { Player } from "../Player";
import { Settings } from "../Settings/Settings"; import { Settings } from "../Settings/Settings";
import {
getHackingWorkRepGain,
getFactionSecurityWorkRepGain,
getFactionFieldWorkRepGain,
} from "../PersonObjects/formulas/reputation";
import { Page, routing } from "../ui/navigationTracking"; import { Page, routing } from "../ui/navigationTracking";
import { dialogBoxCreate } from "../../utils/DialogBox"; import { dialogBoxCreate } from "../../utils/DialogBox";
@@ -235,15 +240,24 @@ export function getNextNeurofluxLevel() {
} }
export function processPassiveFactionRepGain(numCycles) { export function processPassiveFactionRepGain(numCycles) {
var numTimesGain = (numCycles / 600) * Player.faction_rep_mult; for (const name in Factions) {
for (var name in Factions) { if (name === Player.currentWorkFactionName) continue;
if (Factions.hasOwnProperty(name)) { if (!Factions.hasOwnProperty(name)) continue;
var faction = Factions[name]; const faction = Factions[name];
if (!faction.isMember) continue;
// 0 favor = 1%/s
// 50 favor = 6%/s
// 100 favor = 11%/s
const favorMult = Math.min(0.1, (faction.favor / 1000) + 0.01);
// Find the best of all possible favor gain, minimum 1 rep / 2 minute.
const hRep = getHackingWorkRepGain(Player, faction);
const sRep = getFactionSecurityWorkRepGain(Player, faction);
const fRep = getFactionFieldWorkRepGain(Player, faction);
const rate = Math.max(hRep * favorMult, sRep * favorMult, fRep * favorMult, 1/120);
//TODO Get hard value of 1 rep per "rep gain cycle"" for now.. faction.playerReputation += rate *
//maybe later make this based on (numCycles) *
//a player's 'status' like how powerful they are and how much money they have Player.faction_rep_mult *
if (faction.isMember) {faction.playerReputation += (numTimesGain * BitNodeMultipliers.FactionPassiveRepGain);} BitNodeMultipliers.FactionPassiveRepGain;
} }
}
} }

View File

@@ -118,7 +118,7 @@ export class PurchaseableAugmentation extends React.Component<IProps, any> {
} else if (this.aug.name !== AugmentationNames.NeuroFluxGovernor && (this.aug.owned || this.owned())) { } else if (this.aug.name !== AugmentationNames.NeuroFluxGovernor && (this.aug.owned || this.owned())) {
disabled = true; disabled = true;
} else if (this.hasReputation()) { } else if (this.hasReputation()) {
status = <>UNLOCKED - {Money(moneyCost)}</>; status = <>UNLOCKED (at {Reputation(repCost)} faction reputation) - {Money(moneyCost)}</>;
} else { } else {
disabled = true; disabled = true;
status = <>LOCKED (Requires {Reputation(repCost)} faction reputation - {Money(moneyCost)})</>; status = <>LOCKED (Requires {Reputation(repCost)} faction reputation - {Money(moneyCost)})</>;

View File

@@ -809,7 +809,7 @@ GangMember.prototype.ascend = function() {
GangMember.prototype.getAscensionEfficiency = function() { GangMember.prototype.getAscensionEfficiency = function() {
function formula(mult) { function formula(mult) {
return 1/(1+Math.log(mult)/Math.log(10)); return 1/(1+Math.log(mult)/Math.log(20));
} }
return { return {
hack: formula(this.hack_asc_mult), hack: formula(this.hack_asc_mult),

View File

@@ -124,8 +124,7 @@ export class HacknetNode extends React.Component {
<li className={"hacknet-node"}> <li className={"hacknet-node"}>
<div className={"hacknet-node-container"}> <div className={"hacknet-node-container"}>
<div className={"row"}> <div className={"row"}>
<p>Node name:</p> <h1 style={{"fontSize":"1em"}}>{node.name}</h1>
<span className={"text"}>{node.name}</span>
</div> </div>
<div className={"row"}> <div className={"row"}>
<p>Production:</p> <p>Production:</p>

View File

@@ -163,8 +163,7 @@ export class HacknetServer extends React.Component {
<li className={"hacknet-node"}> <li className={"hacknet-node"}>
<div className={"hacknet-node-container"}> <div className={"hacknet-node-container"}>
<div className={"row"}> <div className={"row"}>
<p>Node name:</p> <h1 style={{"fontSize":"1em"}}>{node.hostname}</h1>
<span className={"text"}>{node.hostname}</span>
</div> </div>
<div className={"row"}> <div className={"row"}>
<p>Production:</p> <p>Production:</p>

View File

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

View File

@@ -148,7 +148,7 @@ export const Literatures: IMap<Literature> = {};
"will be our people, and we will be with them as their Gods. We will wipe away every tear from their eyes, and death " + "will be our people, and we will be with them as their Gods. We will wipe away every tear from their eyes, and death " +
"shall be no more, neither shall there be mourning, nor crying, nor pain anymore, for the former things " + "shall be no more, neither shall there be mourning, nor crying, nor pain anymore, for the former things " +
"have passed away.'<br><br>" + "have passed away.'<br><br>" +
"And once were were seated on the throne we said 'Behold, I am making all things new.' " + "And once we were seated on the throne we said 'Behold, I am making all things new.' " +
"Also we said, 'Write this down, for these words are trustworthy and true.' And we said to you, " + "Also we said, 'Write this down, for these words are trustworthy and true.' And we said to you, " +
"'It is done! I am the Alpha and the Omega, the beginning and the end. To the thirsty I will give from the spring " + "'It is done! I am the Alpha and the Omega, the beginning and the end. To the thirsty I will give from the spring " +
"of the water of life without payment. The one who conquers will have this heritage, and we will be his God and " + "of the water of life without payment. The one who conquers will have this heritage, and we will be his God and " +

View File

@@ -78,7 +78,7 @@ export function createTravelPopup(destination: CityName, travelFn: TravelFunctio
return false; return false;
}); });
yesNoBoxCreate(<span>Would you like to travel to ${destination}? The trip will yesNoBoxCreate(<span>Would you like to travel to {destination}? The trip will
cost {Money(cost)}.</span>); cost {Money(cost)}.</span>);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,8 +7,11 @@ import * as React from "react";
import { Location } from "../Location"; import { Location } from "../Location";
import { CONSTANTS } from "../../Constants"; import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { getServer } from "../../Server/ServerHelpers";
import { Server } from "../../Server/Server";
import { SpecialServerIps } from "../../Server/SpecialServerIps";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { StdButton } from "../../ui/React/StdButton"; import { StdButton } from "../../ui/React/StdButton";
@@ -34,11 +37,22 @@ export class GymLocation extends React.Component<IProps, any> {
this.trainDefense = this.trainDefense.bind(this); this.trainDefense = this.trainDefense.bind(this);
this.trainDexterity = this.trainDexterity.bind(this); this.trainDexterity = this.trainDexterity.bind(this);
this.trainAgility = this.trainAgility.bind(this); this.trainAgility = this.trainAgility.bind(this);
this.calculateCost = this.calculateCost.bind(this);
}
calculateCost(): number {
const ip = SpecialServerIps.getIp(this.props.loc.name);
console.log(`ip: ${ip}`);
const server = getServer(ip);
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;
} }
train(stat: string) { train(stat: string) {
const loc = this.props.loc; const loc = this.props.loc;
this.props.p.startClass(loc.costMult, loc.expMult, stat); this.props.p.startClass(this.calculateCost(), loc.expMult, stat);
} }
trainStrength() { trainStrength() {
@@ -58,9 +72,7 @@ export class GymLocation extends React.Component<IProps, any> {
} }
render() { render() {
const costMult: number = this.props.loc.costMult; const cost = CONSTANTS.ClassGymBaseCost * this.calculateCost();
const cost = CONSTANTS.ClassGymBaseCost * costMult;
return ( return (
<div> <div>

View File

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

View File

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

View File

@@ -9,6 +9,9 @@ import { Location } from "../Location";
import { CONSTANTS } from "../../Constants"; import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { getServer } from "../../Server/ServerHelpers";
import { Server } from "../../Server/Server";
import { SpecialServerIps } from "../../Server/SpecialServerIps";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { StdButton } from "../../ui/React/StdButton"; import { StdButton } from "../../ui/React/StdButton";
@@ -37,11 +40,22 @@ export class UniversityLocation extends React.Component<IProps, any> {
this.algorithms = this.algorithms.bind(this); this.algorithms = this.algorithms.bind(this);
this.management = this.management.bind(this); this.management = this.management.bind(this);
this.leadership = this.leadership.bind(this); this.leadership = this.leadership.bind(this);
this.calculateCost = this.calculateCost.bind(this);
}
calculateCost(): number {
const ip = SpecialServerIps.getIp(this.props.loc.name);
console.log(`ip: ${ip}`);
const server = getServer(ip);
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;
} }
take(stat: string) { take(stat: string) {
const loc = this.props.loc; const loc = this.props.loc;
this.props.p.startClass(loc.costMult, loc.expMult, stat); this.props.p.startClass(this.calculateCost(), loc.expMult, stat);
} }
study() { study() {
@@ -69,7 +83,7 @@ export class UniversityLocation extends React.Component<IProps, any> {
} }
render() { render() {
const costMult: number = this.props.loc.costMult; const costMult: number = this.calculateCost();
const dataStructuresCost = CONSTANTS.ClassDataStructuresBaseCost * costMult; const dataStructuresCost = CONSTANTS.ClassDataStructuresBaseCost * costMult;
const networksCost = CONSTANTS.ClassNetworksBaseCost * costMult; const networksCost = CONSTANTS.ClassNetworksBaseCost * costMult;

View File

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

View File

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

View File

@@ -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
);
}
}

View File

@@ -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
View 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;
}
}

View File

@@ -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>
</>);
}

View File

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

View File

@@ -1,5 +1,6 @@
const sprintf = require("sprintf-js").sprintf; const sprintf = require("sprintf-js").sprintf;
const vsprintf = require("sprintf-js").vsprintf; const vsprintf = require("sprintf-js").vsprintf;
import * as libarg from 'arg';
import { getRamCost } from "./Netscript/RamCostGenerator"; import { getRamCost } from "./Netscript/RamCostGenerator";
import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStartStopEventEmitter"; import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStartStopEventEmitter";
@@ -88,7 +89,10 @@ import { inMission } from "./Missions";
import { Player } from "./Player"; import { Player } from "./Player";
import { Programs } from "./Programs/Programs"; import { Programs } from "./Programs/Programs";
import { Script } from "./Script/Script"; import { Script } from "./Script/Script";
import { findRunningScript } from "./Script/ScriptHelpers"; import {
findRunningScript,
findRunningScriptByPid,
} from "./Script/ScriptHelpers";
import { isScriptFilename } from "./Script/ScriptHelpersTS"; import { isScriptFilename } from "./Script/ScriptHelpersTS";
import { _getScriptUrls } from "./NetscriptJSEvaluator"; import { _getScriptUrls } from "./NetscriptJSEvaluator";
import { import {
@@ -162,6 +166,7 @@ import {
netscriptDelay, netscriptDelay,
resolveNetscriptRequestedThreads, resolveNetscriptRequestedThreads,
} from "./NetscriptEvaluator"; } from "./NetscriptEvaluator";
import { Interpreter } from "./JSInterpreter";
import { NetscriptPort } from "./NetscriptPort"; import { NetscriptPort } from "./NetscriptPort";
import { SleeveTaskType } from "./PersonObjects/Sleeve/SleeveTaskTypesEnum"; import { SleeveTaskType } from "./PersonObjects/Sleeve/SleeveTaskTypesEnum";
import { findSleevePurchasableAugs } from "./PersonObjects/Sleeve/SleeveHelpers"; import { findSleevePurchasableAugs } from "./PersonObjects/Sleeve/SleeveHelpers";
@@ -256,6 +261,39 @@ const possibleLogs = {
setTerritoryWarfare: true, setTerritoryWarfare: true,
} }
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') ||
!pseudoObj.hasOwnProperty('proto')) {
return pseudoObj; // it wasn't a pseudo object anyway.
}
let nativeObj;
if (pseudoObj.hasOwnProperty('class') && pseudoObj.class === 'Array') {
nativeObj = [];
const length = defaultInterpreter.getProperty(pseudoObj, 'length');
for (let i = 0; i < length; i++) {
if (defaultInterpreter.hasProperty(pseudoObj, i)) {
nativeObj[i] =
toNative(defaultInterpreter.getProperty(pseudoObj, i));
}
}
} else { // Object.
nativeObj = {};
for (var key in pseudoObj.properties) {
const val = pseudoObj.properties[key];
nativeObj[key] = toNative(val);
}
}
return nativeObj;
}
function NetscriptFunctions(workerScript) { function NetscriptFunctions(workerScript) {
const updateDynamicRam = function(fnName, ramCost) { const updateDynamicRam = function(fnName, ramCost) {
if (workerScript.dynamicLoadedFns[fnName]) { return; } if (workerScript.dynamicLoadedFns[fnName]) { return; }
@@ -305,7 +343,6 @@ function NetscriptFunctions(workerScript) {
* is not specified. * is not specified.
*/ */
const getRunningScript = function(fn, ip, callingFnName, scriptArgs) { const getRunningScript = function(fn, ip, callingFnName, scriptArgs) {
// Sanitize arguments
if (typeof callingFnName !== "string" || callingFnName === "") { if (typeof callingFnName !== "string" || callingFnName === "") {
callingFnName = "getRunningScript"; callingFnName = "getRunningScript";
} }
@@ -330,6 +367,19 @@ function NetscriptFunctions(workerScript) {
return workerScript.scriptRef; return workerScript.scriptRef;
} }
const getRunningScriptByPid = function(pid, callingFnName) {
if (typeof callingFnName !== "string" || callingFnName === "") {
callingFnName = "getRunningScriptgetRunningScriptByPid";
}
for(const name of Object.keys(AllServers)) {
const server = AllServers[name];
const runningScript = findRunningScriptByPid(pid, server);
if (runningScript) return runningScript;
}
return null;
}
/** /**
* Helper function for getting the error log message when the user specifies * Helper function for getting the error log message when the user specifies
* a nonexistent running script * a nonexistent running script
@@ -673,7 +723,7 @@ function NetscriptFunctions(workerScript) {
influenceStockThroughServerHack(server, moneyGained); influenceStockThroughServerHack(server, moneyGained);
} }
if(manual) { if(manual) {
server.manuallyHacked = true; server.backdoorInstalled = true;
} }
return Promise.resolve(moneyGained); return Promise.resolve(moneyGained);
} else { } else {
@@ -686,13 +736,30 @@ function NetscriptFunctions(workerScript) {
}); });
} }
const argsToString = function(args) {
let out = '';
for(let arg of args) {
arg = toNative(arg);
if(typeof arg === 'object') {
out += JSON.stringify(arg);
continue
}
out += `${arg}`;
}
return out;
}
return { return {
hacknet : { hacknet : {
numNodes : function() { numNodes : function() {
return Player.hacknetNodes.length; return Player.hacknetNodes.length;
}, },
maxNumNodes : function() { maxNumNodes : function() {
return MaxNumberHacknetServers; if (hasHacknetServers()) {
return HacknetServerConstants.MaxServers;
}
return Infinity;
}, },
purchaseNode : function() { purchaseNode : function() {
return purchaseHacknet(); return purchaseHacknet();
@@ -908,7 +975,7 @@ function NetscriptFunctions(workerScript) {
// Check argument validity // Check argument validity
const server = safeGetServer(ip, 'growthAnalyze'); const server = safeGetServer(ip, 'growthAnalyze');
if (typeof growth !== "number" || isNaN(growth) || growth < 1) { if (typeof growth !== "number" || isNaN(growth) || growth < 1 || !isFinite(growth)) {
throw makeRuntimeErrorMsg("growthAnalyze", `Invalid argument: growth must be numeric and >= 1, is ${growth}.`); throw makeRuntimeErrorMsg("growthAnalyze", `Invalid argument: growth must be numeric and >= 1, is ${growth}.`);
} }
@@ -944,18 +1011,17 @@ function NetscriptFunctions(workerScript) {
return Promise.resolve(CONSTANTS.ServerWeakenAmount * threads); return Promise.resolve(CONSTANTS.ServerWeakenAmount * threads);
}); });
}, },
print: function(args){ print: function(){
if (args === undefined) { if (arguments.length === 0) {
throw makeRuntimeErrorMsg("print", "Takes 1 argument."); throw makeRuntimeErrorMsg("print", "Takes at least 1 argument.");
} }
workerScript.print(args.toString()); workerScript.print(argsToString(arguments));
}, },
tprint: function(args) { tprint: function() {
if (args === undefined || args == null) { if (arguments.length === 0) {
throw makeRuntimeErrorMsg("tprint", "Takes 1 argument."); throw makeRuntimeErrorMsg("tprint", "Takes at least 1 argument.");
} }
var x = args.toString(); post(`${workerScript.scriptRef.filename}: ${argsToString(arguments)}`);
post(`${workerScript.scriptRef.filename}: ${args.toString()}`);
}, },
clearLog: function() { clearLog: function() {
workerScript.scriptRef.clearLog(); workerScript.scriptRef.clearLog();
@@ -989,8 +1055,15 @@ function NetscriptFunctions(workerScript) {
return runningScriptObj.logs.slice(); return runningScriptObj.logs.slice();
}, },
tail: function(fn, ip, ...scriptArgs) { tail: function(fn, ip=workerScript.serverIp, ...scriptArgs) {
const runningScriptObj = getRunningScript(fn, ip, "tail", scriptArgs); let runningScriptObj;
if(arguments.length === 0) {
runningScriptObj = workerScript.scriptRef;
} else if(typeof fn === 'number') {
runningScriptObj = getRunningScriptByPid(fn, 'tail');
} else {
runningScriptObj = getRunningScript(fn, ip, "tail", scriptArgs);
}
if (runningScriptObj == null) { if (runningScriptObj == null) {
workerScript.log("tail", getCannotFindRunningScriptErrorMessage(fn, ip, scriptArgs)); workerScript.log("tail", getCannotFindRunningScriptErrorMessage(fn, ip, scriptArgs));
return; return;
@@ -1494,7 +1567,12 @@ function NetscriptFunctions(workerScript) {
const processes = []; const processes = [];
for (const i in server.runningScripts) { for (const i in server.runningScripts) {
const script = server.runningScripts[i]; const script = server.runningScripts[i];
processes.push({filename:script.filename, threads: script.threads, args: script.args.slice()}) processes.push({
filename:script.filename,
threads: script.threads,
args: script.args.slice(),
pid: script.pid,
})
} }
return processes; return processes;
}, },
@@ -1677,20 +1755,16 @@ function NetscriptFunctions(workerScript) {
} }
return false; return false;
}, },
isRunning: function(filename,ip) { isRunning: function(fn, ip=workerScript.serverIp, ...scriptArgs) {
updateDynamicRam("isRunning", getRamCost("isRunning")); updateDynamicRam("isRunning", getRamCost("isRunning"));
if (filename === undefined || ip === undefined) { if (fn === undefined || ip === undefined) {
throw makeRuntimeErrorMsg("isRunning", "Usage: isRunning(scriptname, server, [arg1], [arg2]...)"); throw makeRuntimeErrorMsg("isRunning", "Usage: isRunning(scriptname, server, [arg1], [arg2]...)");
} }
var server = getServer(ip); if(typeof fn === 'number') {
if (server == null) { return getRunningScriptByPid(fn, 'isRunning') != null;
throw makeRuntimeErrorMsg("isRunning", `Invalid IP/hostname: ${ip}`); } else {
return getRunningScript(fn, ip, "isRunning", scriptArgs) != null;
} }
var argsForTargetScript = [];
for (var i = 2; i < arguments.length; ++i) {
argsForTargetScript.push(arguments[i]);
}
return (findRunningScript(filename, argsForTargetScript, server) != null);
}, },
getStockSymbols: function() { getStockSymbols: function() {
updateDynamicRam("getStockSymbols", getRamCost("getStockSymbols")); updateDynamicRam("getStockSymbols", getRamCost("getStockSymbols"));
@@ -2022,7 +2096,7 @@ function NetscriptFunctions(workerScript) {
const cost = getPurchaseServerCost(ram); const cost = getPurchaseServerCost(ram);
if (cost === Infinity) { if (cost === Infinity) {
workerScript.log("purchaseServer", `Invalid argument: ram='${ram}'`); workerScript.log("purchaseServer", `Invalid argument: ram='${ram}'`);
return Infinity; return "";
} }
if (Player.money.lt(cost)) { if (Player.money.lt(cost)) {
@@ -2362,6 +2436,38 @@ function NetscriptFunctions(workerScript) {
} }
return 0; return 0;
}, },
getRunningScript: function(fn, ip) {
updateDynamicRam("getRunningScript", getRamCost("getRunningScript"));
let runningScript;
if(arguments.length === 0) {
runningScript = workerScript.scriptRef;
} else if(typeof fn === 'number') {
runningScript = getRunningScriptByPid(fn, 'getRunningScript');
} else {
const scriptArgs = [];
for (var i = 2; i < arguments.length; ++i) {
scriptArgs.push(arguments[i]);
}
runningScript = getRunningScript(fn, ip, 'getRunningScript', scriptArgs);
}
if (runningScript === null) return null;
return {
args: runningScript.args.slice(),
filename: runningScript.filename,
logs: runningScript.logs.slice(),
offlineExpGained: runningScript.offlineExpGained,
offlineMoneyMade: runningScript.offlineMoneyMade,
offlineRunningTime: runningScript.offlineRunningTime,
onlineExpGained: runningScript.onlineExpGained,
onlineMoneyMade: runningScript.onlineMoneyMade,
onlineRunningTime: runningScript.onlineRunningTime,
pid: runningScript.pid,
ramUsage: runningScript.ramUsage,
server: runningScript.server,
threads: runningScript.threads,
};
},
getHackTime: function(ip, hack, int) { getHackTime: function(ip, hack, int) {
updateDynamicRam("getHackTime", getRamCost("getHackTime")); updateDynamicRam("getHackTime", getRamCost("getHackTime"));
const server = safeGetServer(ip, "getHackTime"); const server = safeGetServer(ip, "getHackTime");
@@ -2457,7 +2563,7 @@ function NetscriptFunctions(workerScript) {
return Player.playtimeSinceLastAug; return Player.playtimeSinceLastAug;
}, },
prompt : function(txt) { prompt : function(txt) {
if (!isString(txt)) {txt = String(txt);} if (!isString(txt)) {txt = JSON.stringify(txt);}
// The id for this popup will consist of the first 20 characters of the prompt string.. // The id for this popup will consist of the first 20 characters of the prompt string..
// Thats hopefully good enough to be unique // Thats hopefully good enough to be unique
@@ -4381,8 +4487,36 @@ function NetscriptFunctions(workerScript) {
}, },
exploit: function() { exploit: function() {
Player.giveExploit(Exploit.UndocumentedFunctionCall); Player.giveExploit(Exploit.UndocumentedFunctionCall);
},
flags: function(data) {
data = toNative(data);
// We always want the help flag.
const args = {};
for(const d of data) {
let t = String;
if(typeof d[1] === 'number') {
t = Number;
} else if(typeof d[1] === 'boolean') {
t = Boolean;
} else if(Array.isArray(d[1])) {
t = [String];
}
args['--'+d[0]] = t
}
const ret = libarg(args, {argv: workerScript.args});
for(const d of data) {
if(!ret.hasOwnProperty('--'+d[0])) ret[d[0]] = d[1];
}
for(const key of Object.keys(ret)) {
if(!key.startsWith('--')) continue;
const value = ret[key];
delete ret[key];
ret[key.slice(2)] = value;
}
return ret;
} }
} // End return } // End return
} // End NetscriptFunction() } // End NetscriptFunction()
export { NetscriptFunctions }; export { NetscriptFunctions };

View File

@@ -107,7 +107,7 @@ export function _getScriptUrls(script, scripts, seen) {
// import {foo} from "blob://<uuid>" // import {foo} from "blob://<uuid>"
// //
// Where the blob URL contains the script content. // Where the blob URL contains the script content.
let transformedCode = script.code.replace(/((?:from|import)\s+(?:'|"))(?:\.\/)?([^'"]+)('|";)/g, let transformedCode = script.code.replace(/((?:from|import)\s+(?:'|"))(?:\.\/)?([^'"]+)('|")/g,
(unmodified, prefix, filename, suffix) => { (unmodified, prefix, filename, suffix) => {
const isAllowedImport = scripts.some(s => s.filename == filename); const isAllowedImport = scripts.some(s => s.filename == filename);
if (!isAllowedImport) return unmodified; if (!isAllowedImport) return unmodified;

View File

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

View File

@@ -30,6 +30,11 @@ import { LocationName } from "../../Locations/data/LocationNames";
import { Sleeve } from "../../PersonObjects/Sleeve/Sleeve"; import { Sleeve } from "../../PersonObjects/Sleeve/Sleeve";
import { calculateSkill as calculateSkillF } from "../formulas/skill"; import { calculateSkill as calculateSkillF } from "../formulas/skill";
import { calculateIntelligenceBonus } from "../formulas/intelligence"; import { calculateIntelligenceBonus } from "../formulas/intelligence";
import {
getHackingWorkRepGain,
getFactionSecurityWorkRepGain,
getFactionFieldWorkRepGain,
} from '../formulas/reputation';
import { import {
AllServers, AllServers,
AddToAllServers, AddToAllServers,
@@ -429,6 +434,8 @@ export function gainHackingExp(exp) {
if(this.hacking_exp < 0) { if(this.hacking_exp < 0) {
this.hacking_exp = 0; this.hacking_exp = 0;
} }
this.hacking_skill = calculateSkillF(this.hacking_exp, this.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier);
} }
export function gainStrengthExp(exp) { export function gainStrengthExp(exp) {
@@ -439,6 +446,8 @@ export function gainStrengthExp(exp) {
if(this.strength_exp < 0) { if(this.strength_exp < 0) {
this.strength_exp = 0; this.strength_exp = 0;
} }
this.strength = calculateSkillF(this.strength_exp, this.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier);
} }
export function gainDefenseExp(exp) { export function gainDefenseExp(exp) {
@@ -449,6 +458,8 @@ export function gainDefenseExp(exp) {
if(this.defense_exp < 0) { if(this.defense_exp < 0) {
this.defense_exp = 0; this.defense_exp = 0;
} }
this.defense = calculateSkillF(this.defense_exp, this.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier);
} }
export function gainDexterityExp(exp) { export function gainDexterityExp(exp) {
@@ -459,6 +470,8 @@ export function gainDexterityExp(exp) {
if(this.dexterity_exp < 0) { if(this.dexterity_exp < 0) {
this.dexterity_exp = 0; this.dexterity_exp = 0;
} }
this.dexterity = calculateSkillF(this.dexterity_exp, this.dexterity_mult * BitNodeMultipliers.DexterityLevelMultiplier);
} }
export function gainAgilityExp(exp) { export function gainAgilityExp(exp) {
@@ -469,6 +482,8 @@ export function gainAgilityExp(exp) {
if(this.agility_exp < 0) { if(this.agility_exp < 0) {
this.agility_exp = 0; this.agility_exp = 0;
} }
this.agility = calculateSkillF(this.agility_exp, this.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier);
} }
export function gainCharismaExp(exp) { export function gainCharismaExp(exp) {
@@ -479,6 +494,8 @@ export function gainCharismaExp(exp) {
if(this.charisma_exp < 0) { if(this.charisma_exp < 0) {
this.charisma_exp = 0; this.charisma_exp = 0;
} }
this.charisma = calculateSkillF(this.charisma_exp, this.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier);
} }
export function gainIntelligenceExp(exp) { export function gainIntelligenceExp(exp) {
@@ -550,7 +567,11 @@ export function processWorkEarnings(numCycles=1) {
this.gainAgilityExp(agiExpGain); this.gainAgilityExp(agiExpGain);
this.gainCharismaExp(chaExpGain); this.gainCharismaExp(chaExpGain);
this.gainMoney(moneyGain); this.gainMoney(moneyGain);
this.recordMoneySource(moneyGain, "work"); if (this.className) {
this.recordMoneySource(moneyGain, "class");
} else {
this.recordMoneySource(moneyGain, "work");
}
this.workHackExpGained += hackExpGain; this.workHackExpGained += hackExpGain;
this.workStrExpGained += strExpGain; this.workStrExpGained += strExpGain;
this.workDefExpGained += defExpGain; this.workDefExpGained += defExpGain;
@@ -592,6 +613,17 @@ export function startWork(companyName) {
Engine.loadWorkInProgressContent(); Engine.loadWorkInProgressContent();
} }
export function cancelationPenalty() {
const company = Companies[this.companyName];
const specialIp = SpecialServerIps[this.companyName];
if(specialIp) {
const server = AllServers[specialIp];
if(server && server.backdoorInstalled) return 0.75;
}
return 0.5;
}
export function work(numCycles) { export function work(numCycles) {
// Cap the number of cycles being processed to whatever would put you at // Cap the number of cycles being processed to whatever would put you at
// the work time limit (8 hours) // the work time limit (8 hours)
@@ -622,6 +654,10 @@ export function work(numCycles) {
const position = this.jobs[this.companyName]; const position = this.jobs[this.companyName];
const penalty = this.cancelationPenalty();
const penaltyString = penalty === 0.5 ? 'half' : 'three quarter'
var elem = document.getElementById("work-in-progress-text"); var elem = document.getElementById("work-in-progress-text");
ReactDOM.render(<> ReactDOM.render(<>
You are currently working as a {position} at {this.companyName} (Current Company Reputation: {Reputation(companyRep)})<br /><br /> You are currently working as a {position} at {this.companyName} (Current Company Reputation: {Reputation(companyRep)})<br /><br />
@@ -636,17 +672,17 @@ export function work(numCycles) {
{numeralWrapper.formatExp(this.workAgiExpGained)} ({`${numeralWrapper.formatExp(this.workAgiExpGainRate * CYCLES_PER_SEC)} / sec`}) agility exp <br /><br /> {numeralWrapper.formatExp(this.workAgiExpGained)} ({`${numeralWrapper.formatExp(this.workAgiExpGainRate * CYCLES_PER_SEC)} / sec`}) agility exp <br /><br />
{numeralWrapper.formatExp(this.workChaExpGained)} ({`${numeralWrapper.formatExp(this.workChaExpGainRate * CYCLES_PER_SEC)} / sec`}) charisma exp <br /><br /> {numeralWrapper.formatExp(this.workChaExpGained)} ({`${numeralWrapper.formatExp(this.workChaExpGainRate * CYCLES_PER_SEC)} / sec`}) charisma exp <br /><br />
You will automatically finish after working for 8 hours. You can cancel earlier if you wish, You will automatically finish after working for 8 hours. You can cancel earlier if you wish,
but you will only gain half of the reputation you've earned so far. but you will only gain {penaltyString} of the reputation you've earned so far.
</>, elem); </>, elem);
} }
export function finishWork(cancelled, sing=false) { export function finishWork(cancelled, sing=false) {
//Since the work was cancelled early, player only gains half of what they've earned so far //Since the work was cancelled early, player only gains half of what they've earned so far
if (cancelled) { if (cancelled) {
this.workRepGained /= 2; this.workRepGained *= this.cancelationPenalty();
} }
var company = Companies[this.companyName]; const company = Companies[this.companyName];
company.playerReputation += (this.workRepGained); company.playerReputation += (this.workRepGained);
this.updateSkillLevels(); this.updateSkillLevels();
@@ -853,7 +889,7 @@ export function startFactionFieldWork(faction) {
this.workDexExpGainRate = .1 * this.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain; this.workDexExpGainRate = .1 * this.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.workAgiExpGainRate = .1 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain; this.workAgiExpGainRate = .1 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.workChaExpGainRate = .1 * this.charisma_exp_mult * BitNodeMultipliers.FactionWorkExpGain; this.workChaExpGainRate = .1 * this.charisma_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.workRepGainRate = this.getFactionFieldWorkRepGain(); this.workRepGainRate = getFactionFieldWorkRepGain(this, faction);
this.factionWorkType = CONSTANTS.FactionWorkField; this.factionWorkType = CONSTANTS.FactionWorkField;
this.currentWorkFactionDescription = "carrying out field missions" this.currentWorkFactionDescription = "carrying out field missions"
@@ -870,7 +906,7 @@ export function startFactionSecurityWork(faction) {
this.workDexExpGainRate = 0.15 * this.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain; this.workDexExpGainRate = 0.15 * this.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.workAgiExpGainRate = 0.15 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain; this.workAgiExpGainRate = 0.15 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.workChaExpGainRate = 0.00 * this.charisma_exp_mult * BitNodeMultipliers.FactionWorkExpGain; this.workChaExpGainRate = 0.00 * this.charisma_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.workRepGainRate = this.getFactionSecurityWorkRepGain(); this.workRepGainRate = getFactionSecurityWorkRepGain(this, faction);
this.factionWorkType = CONSTANTS.FactionWorkSecurity; this.factionWorkType = CONSTANTS.FactionWorkSecurity;
this.currentWorkFactionDescription = "performing security detail" this.currentWorkFactionDescription = "performing security detail"
@@ -879,29 +915,23 @@ export function startFactionSecurityWork(faction) {
} }
export function workForFaction(numCycles) { export function workForFaction(numCycles) {
var faction = Factions[this.currentWorkFactionName]; const faction = Factions[this.currentWorkFactionName];
//Constantly update the rep gain rate //Constantly update the rep gain rate
switch (this.factionWorkType) { switch (this.factionWorkType) {
case CONSTANTS.FactionWorkHacking: case CONSTANTS.FactionWorkHacking:
this.workRepGainRate = (this.hacking_skill + this.intelligence) / CONSTANTS.MaxSkillLevel * this.faction_rep_mult * this.getIntelligenceBonus(0.5); this.workRepGainRate = getHackingWorkRepGain(this, faction);
break; break;
case CONSTANTS.FactionWorkField: case CONSTANTS.FactionWorkField:
this.workRepGainRate = this.getFactionFieldWorkRepGain(); this.workRepGainRate = getFactionFieldWorkRepGain(this, faction);
break; break;
case CONSTANTS.FactionWorkSecurity: case CONSTANTS.FactionWorkSecurity:
this.workRepGainRate = this.getFactionSecurityWorkRepGain(); this.workRepGainRate = getFactionSecurityWorkRepGain(this, faction);
break; break;
default: default:
break; break;
} }
//Update reputation gain rate to account for faction favor
var favorMult = 1 + (faction.favor / 100);
if (isNaN(favorMult)) {favorMult = 1;}
this.workRepGainRate *= favorMult;
this.workRepGainRate *= BitNodeMultipliers.FactionWorkRepGain;
//Cap the number of cycles being processed to whatever would put you at limit (20 hours) //Cap the number of cycles being processed to whatever would put you at limit (20 hours)
var overMax = false; var overMax = false;
if (this.timeWorked + (Engine._idleSpeed * numCycles) >= CONSTANTS.MillisecondsPer20Hours) { if (this.timeWorked + (Engine._idleSpeed * numCycles) >= CONSTANTS.MillisecondsPer20Hours) {
@@ -1112,25 +1142,25 @@ export function getWorkRepGain() {
return jobPerformance * this.company_rep_mult * favorMult; return jobPerformance * this.company_rep_mult * favorMult;
} }
export function getFactionSecurityWorkRepGain() { // export function getFactionSecurityWorkRepGain() {
var t = 0.9 * (this.hacking_skill / CONSTANTS.MaxSkillLevel + // var t = 0.9 * (this.hacking_skill / CONSTANTS.MaxSkillLevel +
this.strength / CONSTANTS.MaxSkillLevel + // this.strength / CONSTANTS.MaxSkillLevel +
this.defense / CONSTANTS.MaxSkillLevel + // this.defense / CONSTANTS.MaxSkillLevel +
this.dexterity / CONSTANTS.MaxSkillLevel + // this.dexterity / CONSTANTS.MaxSkillLevel +
this.agility / CONSTANTS.MaxSkillLevel) / 4.5; // this.agility / CONSTANTS.MaxSkillLevel) / 4.5;
return t * this.faction_rep_mult; // return t * this.faction_rep_mult;
} // }
export function getFactionFieldWorkRepGain() { // export function getFactionFieldWorkRepGain() {
var t = 0.9 * (this.hacking_skill / CONSTANTS.MaxSkillLevel + // var t = 0.9 * (this.hacking_skill / CONSTANTS.MaxSkillLevel +
this.strength / CONSTANTS.MaxSkillLevel + // this.strength / CONSTANTS.MaxSkillLevel +
this.defense / CONSTANTS.MaxSkillLevel + // this.defense / CONSTANTS.MaxSkillLevel +
this.dexterity / CONSTANTS.MaxSkillLevel + // this.dexterity / CONSTANTS.MaxSkillLevel +
this.agility / CONSTANTS.MaxSkillLevel + // this.agility / CONSTANTS.MaxSkillLevel +
this.charisma / CONSTANTS.MaxSkillLevel + // this.charisma / CONSTANTS.MaxSkillLevel +
this.intelligence / CONSTANTS.MaxSkillLevel) / 5.5; // this.intelligence / CONSTANTS.MaxSkillLevel) / 5.5;
return t * this.faction_rep_mult; // return t * this.faction_rep_mult;
} // }
/* Creating a Program */ /* Creating a Program */
export function startCreateProgramWork(programName, time, reqLevel) { export function startCreateProgramWork(programName, time, reqLevel) {
@@ -1715,6 +1745,11 @@ export function getNextCompanyPosition(company, entryPosType) {
return entryPosType; return entryPosType;
} }
export function quitJob(company) {
this.companyName = "";
delete this.jobs[company];
}
export function applyForSoftwareJob(sing=false) { export function applyForSoftwareJob(sing=false) {
return this.applyForJob(CompanyPositions[posNames.SoftwareCompanyPositions[0]], sing); return this.applyForJob(CompanyPositions[posNames.SoftwareCompanyPositions[0]], sing);
} }
@@ -2036,7 +2071,7 @@ export function checkForFactionInvitations() {
} else { } else {
if (!fulcrumsecrettechonologiesFac.isBanned && !fulcrumsecrettechonologiesFac.isMember && if (!fulcrumsecrettechonologiesFac.isBanned && !fulcrumsecrettechonologiesFac.isMember &&
!fulcrumsecrettechonologiesFac.alreadyInvited && !fulcrumsecrettechonologiesFac.alreadyInvited &&
fulcrumSecretServer.manuallyHacked && fulcrumSecretServer.backdoorInstalled &&
checkMegacorpRequirements(LocationName.AevumFulcrumTechnologies, 250e3)) { checkMegacorpRequirements(LocationName.AevumFulcrumTechnologies, 250e3)) {
invitedFactions.push(fulcrumsecrettechonologiesFac); invitedFactions.push(fulcrumsecrettechonologiesFac);
} }
@@ -2048,7 +2083,7 @@ export function checkForFactionInvitations() {
var bitrunnersServer = AllServers[SpecialServerIps[SpecialServerNames.BitRunnersServer]]; var bitrunnersServer = AllServers[SpecialServerIps[SpecialServerNames.BitRunnersServer]];
if (bitrunnersServer == null) { if (bitrunnersServer == null) {
console.error("Could not find BitRunners Server"); console.error("Could not find BitRunners Server");
} else if (!bitrunnersFac.isBanned && !bitrunnersFac.isMember && bitrunnersServer.manuallyHacked && } else if (!bitrunnersFac.isBanned && !bitrunnersFac.isMember && bitrunnersServer.backdoorInstalled &&
!bitrunnersFac.alreadyInvited && homeComp.maxRam >= 128) { !bitrunnersFac.alreadyInvited && homeComp.maxRam >= 128) {
invitedFactions.push(bitrunnersFac); invitedFactions.push(bitrunnersFac);
} }
@@ -2058,7 +2093,7 @@ export function checkForFactionInvitations() {
var blackhandServer = AllServers[SpecialServerIps[SpecialServerNames.TheBlackHandServer]]; var blackhandServer = AllServers[SpecialServerIps[SpecialServerNames.TheBlackHandServer]];
if (blackhandServer == null) { if (blackhandServer == null) {
console.error("Could not find The Black Hand Server"); console.error("Could not find The Black Hand Server");
} else if (!theblackhandFac.isBanned && !theblackhandFac.isMember && blackhandServer.manuallyHacked && } else if (!theblackhandFac.isBanned && !theblackhandFac.isMember && blackhandServer.backdoorInstalled &&
!theblackhandFac.alreadyInvited && homeComp.maxRam >= 64) { !theblackhandFac.alreadyInvited && homeComp.maxRam >= 64) {
invitedFactions.push(theblackhandFac); invitedFactions.push(theblackhandFac);
} }
@@ -2068,7 +2103,7 @@ export function checkForFactionInvitations() {
var nitesecServer = AllServers[SpecialServerIps[SpecialServerNames.NiteSecServer]]; var nitesecServer = AllServers[SpecialServerIps[SpecialServerNames.NiteSecServer]];
if (nitesecServer == null) { if (nitesecServer == null) {
console.error("Could not find NiteSec Server"); console.error("Could not find NiteSec Server");
} else if (!nitesecFac.isBanned && !nitesecFac.isMember && nitesecServer.manuallyHacked && } else if (!nitesecFac.isBanned && !nitesecFac.isMember && nitesecServer.backdoorInstalled &&
!nitesecFac.alreadyInvited && homeComp.maxRam >= 32) { !nitesecFac.alreadyInvited && homeComp.maxRam >= 32) {
invitedFactions.push(nitesecFac); invitedFactions.push(nitesecFac);
} }
@@ -2212,7 +2247,7 @@ export function checkForFactionInvitations() {
var cybersecServer = AllServers[SpecialServerIps[SpecialServerNames.CyberSecServer]]; var cybersecServer = AllServers[SpecialServerIps[SpecialServerNames.CyberSecServer]];
if (cybersecServer == null) { if (cybersecServer == null) {
console.error("Could not find CyberSec Server"); console.error("Could not find CyberSec Server");
} else if (!cybersecFac.isBanned && !cybersecFac.isMember && cybersecServer.manuallyHacked && } else if (!cybersecFac.isBanned && !cybersecFac.isMember && cybersecServer.backdoorInstalled &&
!cybersecFac.alreadyInvited) { !cybersecFac.alreadyInvited) {
invitedFactions.push(cybersecFac); invitedFactions.push(cybersecFac);
} }

View File

@@ -0,0 +1,36 @@
import { IPlayer } from '../IPlayer';
import { Faction } from '../../Faction/Faction';
import { CONSTANTS } from '../../Constants';
import { BitNodeMultipliers } from '../../BitNode/BitNodeMultipliers';
function mult(f: Faction): number {
var favorMult = 1 + (f.favor / 100);
if (isNaN(favorMult)) {favorMult = 1;}
return favorMult * BitNodeMultipliers.FactionWorkRepGain;
}
export function getHackingWorkRepGain(p: IPlayer, f: Faction): number {
return (p.hacking_skill + p.intelligence) /
CONSTANTS.MaxSkillLevel * p.faction_rep_mult *
p.getIntelligenceBonus(0.25) * mult(f);
}
export function getFactionSecurityWorkRepGain(p: IPlayer, f: Faction): number {
var t = 0.9 * (p.hacking_skill / CONSTANTS.MaxSkillLevel +
p.strength / CONSTANTS.MaxSkillLevel +
p.defense / CONSTANTS.MaxSkillLevel +
p.dexterity / CONSTANTS.MaxSkillLevel +
p.agility / CONSTANTS.MaxSkillLevel) / 4.5;
return t * p.faction_rep_mult * mult(f);
}
export function getFactionFieldWorkRepGain(p: IPlayer, f: Faction): number {
var t = 0.9 * (p.hacking_skill / CONSTANTS.MaxSkillLevel +
p.strength / CONSTANTS.MaxSkillLevel +
p.defense / CONSTANTS.MaxSkillLevel +
p.dexterity / CONSTANTS.MaxSkillLevel +
p.agility / CONSTANTS.MaxSkillLevel +
p.charisma / CONSTANTS.MaxSkillLevel +
p.intelligence / CONSTANTS.MaxSkillLevel) / 5.5;
return t * p.faction_rep_mult * mult(f);
}

View File

@@ -16,6 +16,7 @@ import {
import { Player } from "../Player"; import { Player } from "../Player";
import { AceEditor } from "../ScriptEditor/Ace"; import { AceEditor } from "../ScriptEditor/Ace";
import { CodeMirrorEditor } from "../ScriptEditor/CodeMirror"; import { CodeMirrorEditor } from "../ScriptEditor/CodeMirror";
import { CursorPositions } from "../ScriptEditor/CursorPositions";
import { AllServers } from "../Server/AllServers"; import { AllServers } from "../Server/AllServers";
import { processSingleServerGrowth } from "../Server/ServerHelpers"; import { processSingleServerGrowth } from "../Server/ServerHelpers";
import { Settings } from "../Settings/Settings"; import { Settings } from "../Settings/Settings";
@@ -224,11 +225,13 @@ $(document).keydown(function(e) {
function saveAndCloseScriptEditor() { function saveAndCloseScriptEditor() {
var filename = document.getElementById("script-editor-filename").value; var filename = document.getElementById("script-editor-filename").value;
let code; let code, cursor;
try { try {
code = getCurrentEditor().getCode(); code = getCurrentEditor().getCode();
cursor = getCurrentEditor().getCursor();
CursorPositions.saveCursor(filename, cursor);
} catch(e) { } catch(e) {
dialogBoxCreate("Something went wrong when trying to save (getCurrentEditor().getCode()). Please report to game developer with details"); dialogBoxCreate("Something went wrong when trying to save (getCurrentEditor().getCode() or getCurrentEditor().getCursor()). Please report to game developer with details");
return; return;
} }
@@ -268,7 +271,7 @@ function saveAndCloseScriptEditor() {
} }
if (filename !== ".fconf" && !isValidFilePath(filename)) { if (filename !== ".fconf" && !isValidFilePath(filename)) {
dialogBoxCreate("Script filename can contain only alphanumerics, hyphens, and underscores"); dialogBoxCreate("Script filename can contain only alphanumerics, hyphens, and underscores, and must end with an extension.");
return; return;
} }
@@ -282,7 +285,7 @@ function saveAndCloseScriptEditor() {
} }
} else if (isScriptFilename(filename)) { } else if (isScriptFilename(filename)) {
//If the current script already exists on the server, overwrite it //If the current script already exists on the server, overwrite it
for (var i = 0; i < s.scripts.length; i++) { for (let i = 0; i < s.scripts.length; i++) {
if (filename == s.scripts[i].filename) { if (filename == s.scripts[i].filename) {
s.scripts[i].saveScript(getCurrentEditor().getCode(), Player.currentServer, Player.getCurrentServer().scripts); s.scripts[i].saveScript(getCurrentEditor().getCode(), Player.currentServer, Player.getCurrentServer().scripts);
Engine.loadTerminalContent(); Engine.loadTerminalContent();
@@ -295,14 +298,14 @@ function saveAndCloseScriptEditor() {
script.saveScript(getCurrentEditor().getCode(), Player.currentServer, Player.getCurrentServer().scripts); script.saveScript(getCurrentEditor().getCode(), Player.currentServer, Player.getCurrentServer().scripts);
s.scripts.push(script); s.scripts.push(script);
} else if (filename.endsWith(".txt")) { } else if (filename.endsWith(".txt")) {
for (var i = 0; i < s.textFiles.length; ++i) { for (let i = 0; i < s.textFiles.length; ++i) {
if (s.textFiles[i].fn === filename) { if (s.textFiles[i].fn === filename) {
s.textFiles[i].write(code); s.textFiles[i].write(code);
Engine.loadTerminalContent(); Engine.loadTerminalContent();
return; return;
} }
} }
var textFile = new TextFile(filename, code); const textFile = new TextFile(filename, code);
s.textFiles.push(textFile); s.textFiles.push(textFile);
} else { } else {
dialogBoxCreate("Invalid filename. Must be either a script (.script) or " + dialogBoxCreate("Invalid filename. Must be either a script (.script) or " +
@@ -411,3 +414,14 @@ export function findRunningScript(filename, args, server) {
} }
return null; return null;
} }
//Returns a RunningScript object matching the pid on the
//designated server, and false otherwise
export function findRunningScriptByPid(pid, server) {
for (var i = 0; i < server.runningScripts.length; ++i) {
if (server.runningScripts[i].pid === pid) {
return server.runningScripts[i];
}
}
return null;
}

View File

@@ -314,6 +314,14 @@ class AceEditorWrapper extends ScriptEditor {
elem.style.display = "none"; elem.style.display = "none";
} }
} }
getCursor() {
return this.editor.getCursorPosition();
}
setCursor(pos) {
this.editor.gotoLine(pos.row+1, pos.column);
}
} }
export const AceEditor = new AceEditorWrapper(); export const AceEditor = new AceEditorWrapper();

View File

@@ -570,6 +570,15 @@ class CodeMirrorEditorWrapper extends ScriptEditor {
elem.style.display = "none"; elem.style.display = "none";
} }
} }
getCursor() {
const c = this.editor.getCursor(); //I need to get the cursor position
return {row: c.line, column: c.ch};
}
setCursor(pos) {
this.editor.setCursor({line: pos.row, ch: pos.column});
}
} }
export const CodeMirrorEditor = new CodeMirrorEditorWrapper(); export const CodeMirrorEditor = new CodeMirrorEditorWrapper();

View File

@@ -0,0 +1,29 @@
export type Position = {
row: number;
column: number;
};
export class PositionTracker {
positions: Map<string, Position>;
constructor() {
this.positions = new Map<string, Position>();
}
saveCursor(filename: string, pos: Position) {
this.positions.set(filename, pos);
}
getCursor(filename: string): Position {
const position = this.positions.get(filename);
if (!position) {
return {
row: 0,
column: 0,
};
}
return position;
}
};
export const CursorPositions: PositionTracker = new PositionTracker();

View File

@@ -1,3 +1,5 @@
import { CursorPositions } from './CursorPositions';
// Base Script Editor class for the Ace/CodeMirror/etc. wrappers // Base Script Editor class for the Ace/CodeMirror/etc. wrappers
const beautify = require('js-beautify').js_beautify; const beautify = require('js-beautify').js_beautify;
@@ -33,6 +35,7 @@ export class ScriptEditor {
if (filename != "") { if (filename != "") {
this.filenameInput.value = filename; this.filenameInput.value = filename;
this.editor.setValue(code); this.editor.setValue(code);
this.setCursor(CursorPositions.getCursor(filename));
} }
this.editor.focus(); this.editor.focus();

View File

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

View File

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

View File

@@ -90,6 +90,7 @@ export const serverMetadata: IServerMetadata[] = [
min: 1050, min: 1050,
}, },
serverGrowth: 99, serverGrowth: 99,
specialName: LocationName.AevumECorp,
}, },
{ {
hackDifficulty: 99, hackDifficulty: 99,
@@ -106,6 +107,7 @@ export const serverMetadata: IServerMetadata[] = [
min: 1100, min: 1100,
}, },
serverGrowth: 99, serverGrowth: 99,
specialName: LocationName.Sector12MegaCorp,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -128,6 +130,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 80, max: 80,
min: 60, min: 60,
}, },
specialName: LocationName.AevumBachmanAndAssociates,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -155,6 +158,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 85, max: 85,
min: 55, min: 55,
}, },
specialName: LocationName.Sector12BladeIndustries,
}, },
{ {
hackDifficulty: 99, hackDifficulty: 99,
@@ -175,6 +179,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 95, max: 95,
min: 65, min: 65,
}, },
specialName: LocationName.VolhavenNWO,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -201,6 +206,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 75, max: 75,
min: 45, min: 45,
}, },
specialName: LocationName.AevumClarkeIncorporated,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -231,6 +237,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 99, max: 99,
min: 95, min: 95,
}, },
specialName: LocationName.VolhavenOmniTekIncorporated,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -253,6 +260,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 99, max: 99,
min: 75, min: 75,
}, },
specialName: LocationName.Sector12FourSigma,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -275,6 +283,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 99, max: 99,
min: 90, min: 90,
}, },
specialName: LocationName.ChongqingKuaiGongInternational,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -302,6 +311,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 99, max: 99,
min: 80, min: 80,
}, },
specialName: LocationName.AevumFulcrumTechnologies,
}, },
{ {
hackDifficulty: 99, hackDifficulty: 99,
@@ -338,6 +348,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 92, max: 92,
min: 68, min: 68,
}, },
specialName: LocationName.IshimaStormTechnologies,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -360,6 +371,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 73, max: 73,
min: 47, min: 47,
}, },
specialName: LocationName.NewTokyoDefComm,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -409,6 +421,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 80, max: 80,
min: 70, min: 70,
}, },
specialName: LocationName.VolhavenHeliosLabs,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -436,6 +449,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 80, max: 80,
min: 60, min: 60,
}, },
specialName: LocationName.NewTokyoVitaLife,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -458,6 +472,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 95, max: 95,
min: 85, min: 85,
}, },
specialName: LocationName.Sector12IcarusMicrosystems,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -484,6 +499,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 90, max: 90,
min: 80, min: 80,
}, },
specialName: LocationName.Sector12UniversalEnergy,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -586,6 +602,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 90, max: 90,
min: 70, min: 70,
}, },
specialName: LocationName.AevumGalacticCybersystems,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -609,6 +626,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 65, max: 65,
min: 55, min: 55,
}, },
specialName: LocationName.AevumAeroCorp,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -636,6 +654,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 70, max: 70,
min: 60, min: 60,
}, },
specialName: LocationName.VolhavenOmniaCybersystems,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -711,6 +730,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 80, max: 80,
min: 70, min: 70,
}, },
specialName: LocationName.ChongqingSolarisSpaceSystems,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -733,6 +753,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 70, max: 70,
min: 50, min: 50,
}, },
specialName: LocationName.Sector12DeltaOne,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -760,6 +781,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 90, max: 90,
min: 80, min: 80,
}, },
specialName: LocationName.NewTokyoGlobalPharmaceuticals,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -782,6 +804,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 85, max: 85,
min: 65, min: 65,
}, },
specialName: LocationName.IshimaNovaMedical,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -856,6 +879,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 65, max: 65,
min: 55, min: 55,
}, },
specialName: LocationName.VolhavenLexoCorp,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -882,6 +906,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 60, max: 60,
min: 40, min: 40,
}, },
specialName: LocationName.AevumRhoConstruction,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -909,6 +934,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 60, max: 60,
min: 50, min: 50,
}, },
specialName: LocationName.Sector12AlphaEnterprises,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -935,6 +961,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 50, max: 50,
min: 30, min: 30,
}, },
specialName: LocationName.AevumPolice,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -966,6 +993,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 45, max: 45,
min: 35, min: 35,
}, },
specialName: LocationName.Sector12RothmanUniversity,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -992,6 +1020,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 85, max: 85,
min: 75, min: 75,
}, },
specialName: LocationName.VolhavenZBInstituteOfTechnology,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -1023,6 +1052,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 60, max: 60,
min: 40, min: 40,
}, },
specialName: LocationName.AevumSummitUniversity,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -1045,6 +1075,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 70, max: 70,
min: 60, min: 60,
}, },
specialName: LocationName.VolhavenSysCoreSecurities,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -1121,6 +1152,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 65, max: 65,
min: 45, min: 45,
}, },
specialName: LocationName.VolhavenCompuTek,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -1145,6 +1177,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 75, max: 75,
min: 45, min: 45,
}, },
specialName: LocationName.AevumNetLinkTechnologies,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -1169,38 +1202,40 @@ export const serverMetadata: IServerMetadata[] = [
}, },
}, },
{ {
hackDifficulty: 10, hackDifficulty: 1,
hostname: "foodnstuff", hostname: "foodnstuff",
literature: [LiteratureNames.Sector12Crime], literature: [LiteratureNames.Sector12Crime],
maxRamExponent: 4, maxRamExponent: 4,
moneyAvailable: 2000000, moneyAvailable: 40000,
networkLayer: 1, networkLayer: 1,
numOpenPortsRequired: 0, numOpenPortsRequired: 0,
organizationName: LocationName.Sector12FoodNStuff, organizationName: LocationName.Sector12FoodNStuff,
requiredHackingSkill: 1, requiredHackingSkill: 1,
serverGrowth: 5, serverGrowth: 3000,
specialName: LocationName.Sector12FoodNStuff,
}, },
{ {
hackDifficulty: 10, hackDifficulty: 3,
hostname: "sigma-cosmetics", hostname: "sigma-cosmetics",
maxRamExponent: 4, maxRamExponent: 4,
moneyAvailable: 2300000, moneyAvailable: 70000,
networkLayer: 1, networkLayer: 1,
numOpenPortsRequired: 0, numOpenPortsRequired: 0,
organizationName: "Sigma Cosmetics", organizationName: "Sigma Cosmetics",
requiredHackingSkill: 5, requiredHackingSkill: 5,
serverGrowth: 10, serverGrowth: 3000,
}, },
{ {
hackDifficulty: 15, hackDifficulty: 9,
hostname: "joesguns", hostname: "joesguns",
maxRamExponent: 4, maxRamExponent: 4,
moneyAvailable: 2500000, moneyAvailable: 600000,
networkLayer: 1, networkLayer: 1,
numOpenPortsRequired: 0, numOpenPortsRequired: 0,
organizationName: "Joes Guns", organizationName: LocationName.Sector12JoesGuns,
requiredHackingSkill: 10, requiredHackingSkill: 10,
serverGrowth: 20, serverGrowth: 500,
specialName: LocationName.Sector12JoesGuns,
}, },
{ {
hackDifficulty: 25, hackDifficulty: 25,
@@ -1316,6 +1351,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 40, max: 40,
min: 30, min: 30,
}, },
specialName: LocationName.IshimaOmegaSoftware,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -1338,6 +1374,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 33, max: 33,
min: 27, min: 27,
}, },
specialName: LocationName.AevumCrushFitnessGym,
}, },
{ {
hackDifficulty: 30, hackDifficulty: 30,
@@ -1349,6 +1386,7 @@ export const serverMetadata: IServerMetadata[] = [
organizationName: "Iron Gym Network", organizationName: "Iron Gym Network",
requiredHackingSkill: 100, requiredHackingSkill: 100,
serverGrowth: 20, serverGrowth: 20,
specialName: LocationName.Sector12IronGym,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -1372,6 +1410,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 45, max: 45,
min: 25, min: 25,
}, },
specialName: LocationName.VolhavenMilleniumFitnessGym,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -1395,6 +1434,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 60, max: 60,
min: 50, min: 50,
}, },
specialName: LocationName.Sector12PowerhouseGym,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -1414,6 +1454,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 60, max: 60,
min: 40, min: 40,
}, },
specialName: LocationName.AevumSnapFitnessGym,
}, },
{ {
hackDifficulty: 0, hackDifficulty: 0,

View File

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

View File

@@ -60,7 +60,7 @@ import { Player } from "./Player";
import { hackWorldDaemon } from "./RedPill"; import { hackWorldDaemon } from "./RedPill";
import { RunningScript } from "./Script/RunningScript"; import { RunningScript } from "./Script/RunningScript";
import { getRamUsageFromRunningScript } from "./Script/RunningScriptHelpers"; import { getRamUsageFromRunningScript } from "./Script/RunningScriptHelpers";
import { findRunningScript } from "./Script/ScriptHelpers"; import { getCurrentEditor, findRunningScript } from "./Script/ScriptHelpers";
import { isScriptFilename } from "./Script/ScriptHelpersTS"; import { isScriptFilename } from "./Script/ScriptHelpersTS";
import { AllServers } from "./Server/AllServers"; import { AllServers } from "./Server/AllServers";
import { Server } from "./Server/Server"; import { Server } from "./Server/Server";
@@ -108,7 +108,7 @@ import React from "react";
function postNetburnerText() { function postNetburnerText() {
post("Bitburner v" + CONSTANTS.Version); post("Bitburner v" + CONSTANTS.Version);
} }
// Helper function that checks if an argument (which is a string) is a valid number // Helper function that checks if an argument (which is a string) is a valid number
@@ -123,16 +123,16 @@ function getTerminalInput() {
// Defines key commands in terminal // Defines key commands in terminal
$(document).keydown(function(event) { $(document).keydown(function(event) {
// Terminal // Terminal
if (routing.isOn(Page.Terminal)) { if (routing.isOn(Page.Terminal)) {
var terminalInput = document.getElementById("terminal-input-text-box"); var terminalInput = document.getElementById("terminal-input-text-box");
if (terminalInput != null && !event.ctrlKey && !event.shiftKey && !Terminal.contractOpen) {terminalInput.focus();} if (terminalInput != null && !event.ctrlKey && !event.shiftKey && !Terminal.contractOpen) {terminalInput.focus();}
if (event.keyCode === KEY.ENTER) { if (event.keyCode === KEY.ENTER) {
event.preventDefault(); // Prevent newline from being entered in Script Editor event.preventDefault(); // Prevent newline from being entered in Script Editor
const command = getTerminalInput(); const command = getTerminalInput();
const dir = Terminal.currDir; const dir = Terminal.currDir;
post( post(
"<span class='prompt'>[" + "<span class='prompt'>[" +
(FconfSettings.ENABLE_TIMESTAMPS ? getTimestamp() + " " : "") + (FconfSettings.ENABLE_TIMESTAMPS ? getTimestamp() + " " : "") +
Player.getCurrentServer().hostname + Player.getCurrentServer().hostname +
@@ -142,20 +142,20 @@ $(document).keydown(function(event) {
if (command.length > 0) { if (command.length > 0) {
Terminal.resetTerminalInput(); // Clear input first Terminal.resetTerminalInput(); // Clear input first
Terminal.executeCommands(command); Terminal.executeCommands(command);
} }
} }
if (event.keyCode === KEY.C && event.ctrlKey) { if (event.keyCode === KEY.C && event.ctrlKey) {
if (Engine._actionInProgress) { if (Engine._actionInProgress) {
// Cancel action // Cancel action
post("Cancelling..."); post("Cancelling...");
Engine._actionInProgress = false; Engine._actionInProgress = false;
Terminal.finishAction(true); Terminal.finishAction(true);
} else if (FconfSettings.ENABLE_BASH_HOTKEYS) { } else if (FconfSettings.ENABLE_BASH_HOTKEYS) {
// Dont prevent default so it still copies // Dont prevent default so it still copies
Terminal.resetTerminalInput(); // Clear Terminal Terminal.resetTerminalInput(); // Clear Terminal
} }
} }
if (event.keyCode === KEY.L && event.ctrlKey) { if (event.keyCode === KEY.L && event.ctrlKey) {
event.preventDefault(); event.preventDefault();
@@ -183,7 +183,7 @@ $(document).keydown(function(event) {
} }
var prevCommand = Terminal.commandHistory[Terminal.commandHistoryIndex]; var prevCommand = Terminal.commandHistory[Terminal.commandHistoryIndex];
terminalInput.value = prevCommand; terminalInput.value = prevCommand;
setTimeoutRef(function(){terminalInput.selectionStart = terminalInput.selectionEnd = 10000; }, 0); setTimeoutRef(function(){terminalInput.selectionStart = terminalInput.selectionEnd = 10000; }, 10);
} }
if (event.keyCode === KEY.DOWNARROW || if (event.keyCode === KEY.DOWNARROW ||
@@ -295,49 +295,50 @@ $(document).keydown(function(event) {
// ^k clears line after cursor // ^k clears line after cursor
// ^u clears line before cursor // ^u clears line before cursor
} }
} }
}); });
// Keep terminal in focus // Keep terminal in focus
let terminalCtrlPressed = false, shiftKeyPressed = false; let terminalCtrlPressed = false, shiftKeyPressed = false;
$(document).ready(function() { $(document).ready(function() {
if (routing.isOn(Page.Terminal)) { if (routing.isOn(Page.Terminal)) {
$('.terminal-input').focus(); $('.terminal-input').focus();
} }
}); });
$(document).keydown(function(e) { $(document).keydown(function(e) {
if (routing.isOn(Page.Terminal)) { if (routing.isOn(Page.Terminal)) {
if (e.which == KEY.CTRL) { if (e.which == KEY.CTRL) {
terminalCtrlPressed = true; terminalCtrlPressed = true;
} else if (e.shiftKey) { } else if (e.shiftKey) {
shiftKeyPressed = true; shiftKeyPressed = true;
} else if (terminalCtrlPressed || shiftKeyPressed || Terminal.contractOpen) { } else if (terminalCtrlPressed || shiftKeyPressed || Terminal.contractOpen) {
// Don't focus // Don't focus
} else { } else {
var inputTextBox = document.getElementById("terminal-input-text-box"); var inputTextBox = document.getElementById("terminal-input-text-box");
if (inputTextBox != null) {inputTextBox.focus();} if (inputTextBox != null) {inputTextBox.focus();}
terminalCtrlPressed = false; terminalCtrlPressed = false;
shiftKeyPressed = false; shiftKeyPressed = false;
} }
} }
}); });
$(document).keyup(function(e) { $(document).keyup(function(e) {
if (routing.isOn(Page.Terminal)) { if (routing.isOn(Page.Terminal)) {
if (e.which == KEY.CTRL) { if (e.which == KEY.CTRL) {
terminalCtrlPressed = false; terminalCtrlPressed = false;
} }
if (e.shiftKey) { if (e.shiftKey) {
shiftKeyPressed = false; shiftKeyPressed = false;
} }
} }
}); });
let Terminal = { let Terminal = {
// Flags to determine whether the player is currently running a hack or an analyze // Flags to determine whether the player is currently running a hack or an analyze
hackFlag: false, hackFlag: false,
backdoorFlag: false,
analyzeFlag: false, analyzeFlag: false,
actionStarted: false, actionStarted: false,
actionTime: 0, actionTime: 0,
@@ -361,14 +362,14 @@ let Terminal = {
if (FconfSettings.WRAP_INPUT) { if (FconfSettings.WRAP_INPUT) {
document.getElementById("terminal-input-td").innerHTML = document.getElementById("terminal-input-td").innerHTML =
`<div id='terminal-input-header' class='prompt'>[${Player.getCurrentServer().hostname} ~${dir}]$ </div>` + `<div id='terminal-input-header' class='prompt'>[${Player.getCurrentServer().hostname} ~${dir}]$ </div>` +
`<textarea type="text" id="terminal-input-text-box" class="terminal-input" tabindex="1" value=\"${input}\"/>`; `<textarea type="text" id="terminal-input-text-box" class="terminal-input" tabindex="1" value=\"${input}\" autocomplete="off" />`;
// Auto re-size the line element as it wraps // Auto re-size the line element as it wraps
autosize(document.getElementById("terminal-input-text-box")); autosize(document.getElementById("terminal-input-text-box"));
} else { } else {
document.getElementById("terminal-input-td").innerHTML = document.getElementById("terminal-input-td").innerHTML =
`<div id='terminal-input-header' class='prompt'>[${Player.getCurrentServer().hostname} ~${dir}]$ </div>` + `<div id='terminal-input-header' class='prompt'>[${Player.getCurrentServer().hostname} ~${dir}]$ </div>` +
`<input type="text" id="terminal-input-text-box" class="terminal-input" tabindex="1" value=\"${input}\"/>`; `<input type="text" id="terminal-input-text-box" class="terminal-input" tabindex="1" value=\"${input}\" autocomplete="off" />`;
} }
const hdr = document.getElementById("terminal-input-header"); const hdr = document.getElementById("terminal-input-header");
hdr.style.display = "inline"; hdr.style.display = "inline";
@@ -485,6 +486,14 @@ let Terminal = {
Terminal.startAction(); Terminal.startAction();
}, },
startBackdoor: function() {
Terminal.backdoorFlag = true;
// Backdoor should take the same amount of time as hack
Terminal.actionTime = calculateHackingTime(Player.getCurrentServer(), Player) / 4;
Terminal.startAction();
},
startAnalyze: function() { startAnalyze: function() {
Terminal.analyzeFlag = true; Terminal.analyzeFlag = true;
Terminal.actionTime = 1; Terminal.actionTime = 1;
@@ -506,22 +515,30 @@ let Terminal = {
finishAction: function(cancelled = false) { finishAction: function(cancelled = false) {
if (Terminal.hackFlag) { if (Terminal.hackFlag) {
Terminal.finishHack(cancelled); Terminal.finishHack(cancelled);
} else if (Terminal.backdoorFlag) {
Terminal.finishBackdoor(cancelled);
} else if (Terminal.analyzeFlag) { } else if (Terminal.analyzeFlag) {
Terminal.finishAnalyze(cancelled); Terminal.finishAnalyze(cancelled);
} }
// Rename the progress bar so that the next hacks dont trigger it. Re-enable terminal
$("#hack-progress-bar").attr('id', "old-hack-progress-bar");
$("#hack-progress").attr('id', "old-hack-progress");
Terminal.resetTerminalInput();
$('input[class=terminal-input]').prop('disabled', false);
}, },
// Complete the hack/analyze command // Complete the hack/analyze command
finishHack: function(cancelled = false) { finishHack: function(cancelled = false) {
if (cancelled == false) { if (!cancelled) {
var server = Player.getCurrentServer(); var server = Player.getCurrentServer();
// Calculate whether hack was successful // Calculate whether hack was successful
var hackChance = calculateHackingChance(server, Player); var hackChance = calculateHackingChance(server, Player);
var rand = Math.random(); var rand = Math.random();
var expGainedOnSuccess = calculateHackingExpGain(server, Player); var expGainedOnSuccess = calculateHackingExpGain(server, Player);
var expGainedOnFailure = (expGainedOnSuccess / 4); var expGainedOnFailure = (expGainedOnSuccess / 4);
if (rand < hackChance) { // Success! if (rand < hackChance) { // Success!
if (SpecialServerIps[SpecialServerNames.WorldDaemon] && if (SpecialServerIps[SpecialServerNames.WorldDaemon] &&
SpecialServerIps[SpecialServerNames.WorldDaemon] == server.ip) { SpecialServerIps[SpecialServerNames.WorldDaemon] == server.ip) {
if (Player.bitNodeN == null) { if (Player.bitNodeN == null) {
@@ -530,91 +547,87 @@ let Terminal = {
hackWorldDaemon(Player.bitNodeN); hackWorldDaemon(Player.bitNodeN);
return; return;
} }
server.manuallyHacked = true; server.backdoorInstalled = true;
var moneyGained = calculatePercentMoneyHacked(server, Player); var moneyGained = calculatePercentMoneyHacked(server, Player);
moneyGained = Math.floor(server.moneyAvailable * moneyGained); moneyGained = Math.floor(server.moneyAvailable * moneyGained);
if (moneyGained <= 0) {moneyGained = 0;} // Safety check if (moneyGained <= 0) {moneyGained = 0;} // Safety check
server.moneyAvailable -= moneyGained; server.moneyAvailable -= moneyGained;
Player.gainMoney(moneyGained); Player.gainMoney(moneyGained);
Player.recordMoneySource(moneyGained, "hacking"); Player.recordMoneySource(moneyGained, "hacking");
Player.gainHackingExp(expGainedOnSuccess) Player.gainHackingExp(expGainedOnSuccess)
Player.gainIntelligenceExp(expGainedOnSuccess / CONSTANTS.IntelligenceTerminalHackBaseExpGain); Player.gainIntelligenceExp(expGainedOnSuccess / CONSTANTS.IntelligenceTerminalHackBaseExpGain);
server.fortify(CONSTANTS.ServerFortifyAmount); server.fortify(CONSTANTS.ServerFortifyAmount);
postElement(<>Hack successful! Gained {Money(moneyGained)} and {numeralWrapper.formatExp(expGainedOnSuccess)} hacking exp</>); postElement(<>Hack successful! Gained {Money(moneyGained)} and {numeralWrapper.formatExp(expGainedOnSuccess)} hacking exp</>);
} else { // Failure } else { // Failure
// Player only gains 25% exp for failure? TODO Can change this later to balance // Player only gains 25% exp for failure? TODO Can change this later to balance
Player.gainHackingExp(expGainedOnFailure) Player.gainHackingExp(expGainedOnFailure)
post(`Failed to hack ${server.hostname}. Gained ${numeralWrapper.formatExp(expGainedOnFailure)} hacking exp`); post(`Failed to hack ${server.hostname}. Gained ${numeralWrapper.formatExp(expGainedOnFailure)} hacking exp`);
} }
} }
// Rename the progress bar so that the next hacks dont trigger it. Re-enable terminal
$("#hack-progress-bar").attr('id', "old-hack-progress-bar");
$("#hack-progress").attr('id', "old-hack-progress");
Terminal.resetTerminalInput();
$('input[class=terminal-input]').prop('disabled', false);
Terminal.hackFlag = false; Terminal.hackFlag = false;
}, },
finishBackdoor: function(cancelled = false) {
if(!cancelled){
let server = Player.getCurrentServer();
server.backdoorInstalled = true;
postElement(<>Backdoor successful!</>);
}
Terminal.backdoorFlag = false;
},
finishAnalyze: function(cancelled = false) { finishAnalyze: function(cancelled = false) {
if (cancelled == false) { if (!cancelled) {
let currServ = Player.getCurrentServer(); let currServ = Player.getCurrentServer();
const isHacknet = currServ instanceof HacknetServer; const isHacknet = currServ instanceof HacknetServer;
post(currServ.hostname + ": "); post(currServ.hostname + ": ");
post("Organization name: " + currServ.organizationName); post("Organization name: " + currServ.organizationName);
var rootAccess = ""; var rootAccess = "";
if (currServ.hasAdminRights) {rootAccess = "YES";} if (currServ.hasAdminRights) {rootAccess = "YES";}
else {rootAccess = "NO";} else {rootAccess = "NO";}
post("Root Access: " + rootAccess); post("Root Access: " + rootAccess);
if (!isHacknet) { post("Required hacking skill: " + currServ.requiredHackingSkill); } if (!isHacknet) { post("Required hacking skill: " + currServ.requiredHackingSkill); }
post("Server security level: " + numeralWrapper.formatServerSecurity(currServ.hackDifficulty)); post("Server security level: " + numeralWrapper.formatServerSecurity(currServ.hackDifficulty));
post("Chance to hack: " + numeralWrapper.formatPercentage(calculateHackingChance(currServ, Player))); post("Chance to hack: " + numeralWrapper.formatPercentage(calculateHackingChance(currServ, Player)));
post("Time to hack: " + convertTimeMsToTimeElapsedString(calculateHackingTime(currServ, Player)*1000)); post("Time to hack: " + convertTimeMsToTimeElapsedString(calculateHackingTime(currServ, Player)*1000));
postElement(<>Total money available on server: {Money(currServ.moneyAvailable)}</>); postElement(<>Total money available on server: {Money(currServ.moneyAvailable)}</>);
if (!isHacknet) { post("Required number of open ports for NUKE: " + currServ.numOpenPortsRequired); } if (!isHacknet) { post("Required number of open ports for NUKE: " + currServ.numOpenPortsRequired); }
if (currServ.sshPortOpen) { if (currServ.sshPortOpen) {
post("SSH port: Open") post("SSH port: Open")
} else { } else {
post("SSH port: Closed") post("SSH port: Closed")
} }
if (currServ.ftpPortOpen) { if (currServ.ftpPortOpen) {
post("FTP port: Open") post("FTP port: Open")
} else { } else {
post("FTP port: Closed") post("FTP port: Closed")
} }
if (currServ.smtpPortOpen) { if (currServ.smtpPortOpen) {
post("SMTP port: Open") post("SMTP port: Open")
} else { } else {
post("SMTP port: Closed") post("SMTP port: Closed")
} }
if (currServ.httpPortOpen) { if (currServ.httpPortOpen) {
post("HTTP port: Open") post("HTTP port: Open")
} else { } else {
post("HTTP port: Closed") post("HTTP port: Closed")
} }
if (currServ.sqlPortOpen) { if (currServ.sqlPortOpen) {
post("SQL port: Open") post("SQL port: Open")
} else { } else {
post("SQL port: Closed") post("SQL port: Closed")
} }
} }
Terminal.analyzeFlag = false; Terminal.analyzeFlag = false;
// Rename the progress bar so that the next hacks dont trigger it. Re-enable terminal
$("#hack-progress-bar").attr('id', "old-hack-progress-bar");
$("#hack-progress").attr('id', "old-hack-progress");
Terminal.resetTerminalInput();
$('input[class=terminal-input]').prop('disabled', false);
}, },
executeCommands : function(commands) { executeCommands : function(commands) {
@@ -729,7 +742,7 @@ let Terminal = {
}, },
executeCommand : function(command) { executeCommand : function(command) {
if (Terminal.hackFlag || Terminal.analyzeFlag) { if (Terminal.hackFlag || Terminal.backdoorFlag || Terminal.analyzeFlag) {
postError(`Cannot execute command (${command}) while an action is in progress`); postError(`Cannot execute command (${command}) while an action is in progress`);
return; return;
} }
@@ -743,8 +756,8 @@ let Terminal = {
} }
// Only split the first space // Only split the first space
var commandArray = Terminal.parseCommandArguments(command); var commandArray = Terminal.parseCommandArguments(command);
if (commandArray.length == 0) { return; } if (commandArray.length == 0) { return; }
/****************** Interactive Tutorial Terminal Commands ******************/ /****************** Interactive Tutorial Terminal Commands ******************/
if (ITutorial.isRunning) { if (ITutorial.isRunning) {
@@ -820,7 +833,7 @@ let Terminal = {
Terminal.startHack(); Terminal.startHack();
iTutorialNextStep(); iTutorialNextStep();
} else {post("Bad command. Please follow the tutorial");} } else {post("Bad command. Please follow the tutorial");}
break; break;
case iTutorialSteps.TerminalCreateScript: case iTutorialSteps.TerminalCreateScript:
if (commandArray.length == 2 && if (commandArray.length == 2 &&
commandArray[0] == "nano" && commandArray[1] == "foodnstuff.script") { commandArray[0] == "nano" && commandArray[1] == "foodnstuff.script") {
@@ -865,7 +878,7 @@ let Terminal = {
/* Command parser */ /* Command parser */
var s = Player.getCurrentServer(); var s = Player.getCurrentServer();
switch (commandArray[0].toLowerCase()) { switch (commandArray[0].toLowerCase()) {
case "alias": case "alias":
if (commandArray.length === 1) { if (commandArray.length === 1) {
printAliases(); printAliases();
@@ -887,13 +900,31 @@ let Terminal = {
} }
postError('Incorrect usage of alias command. Usage: alias [-g] [aliasname="value"]'); postError('Incorrect usage of alias command. Usage: alias [-g] [aliasname="value"]');
break; break;
case "analyze": case "analyze":
if (commandArray.length !== 1) { if (commandArray.length !== 1) {
post("Incorrect usage of analyze command. Usage: analyze"); post("Incorrect usage of analyze command. Usage: analyze");
return; return;
} }
Terminal.startAnalyze(); Terminal.startAnalyze();
break; 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": case "buy":
if (SpecialServerIps.hasOwnProperty("Darkweb Server")) { if (SpecialServerIps.hasOwnProperty("Darkweb Server")) {
executeDarkwebTerminalCommand(commandArray); executeDarkwebTerminalCommand(commandArray);
@@ -908,10 +939,10 @@ let Terminal = {
return; return;
} }
const filename = Terminal.getFilepath(commandArray[1]); const filename = Terminal.getFilepath(commandArray[1]);
if (!filename.endsWith(".msg") && !filename.endsWith(".lit") && !filename.endsWith(".txt")) { if (!filename.endsWith(".msg") && !filename.endsWith(".lit") && !filename.endsWith(".txt")) {
postError("Only .msg, .txt, and .lit files are viewable with cat (filename must end with .msg, .txt, or .lit)"); postError("Only .msg, .txt, and .lit files are viewable with cat (filename must end with .msg, .txt, or .lit)");
return; return;
} }
if (filename.endsWith(".msg") || filename.endsWith(".lit")) { if (filename.endsWith(".msg") || filename.endsWith(".lit")) {
for (let i = 0; i < s.messages.length; ++i) { for (let i = 0; i < s.messages.length; ++i) {
@@ -938,10 +969,10 @@ let Terminal = {
break; break;
} }
case "cd": { case "cd": {
if (commandArray.length !== 2) { if (commandArray.length > 2) {
postError("Incorrect number of arguments. Usage: cd [dir]"); postError("Incorrect number of arguments. Usage: cd [dir]");
} else { } else {
let dir = commandArray[1]; let dir = commandArray.length === 2 ? commandArray[1] : "/";
let evaledDir; let evaledDir;
if (dir === "/") { if (dir === "/") {
@@ -955,6 +986,12 @@ let Terminal = {
postError("Invalid path. Failed to change directories"); postError("Invalid path. Failed to change directories");
return; return;
} }
const server = Player.getCurrentServer();
if(!server.scripts.some(script => script.filename.startsWith(evaledDir))) {
postError("Invalid path. Failed to change directories");
return;
}
} }
Terminal.currDir = evaledDir; Terminal.currDir = evaledDir;
@@ -996,17 +1033,17 @@ let Terminal = {
break; break;
} }
case "clear": case "clear":
case "cls": case "cls":
if (commandArray.length !== 1) { if (commandArray.length !== 1) {
postError("Incorrect usage of clear/cls command. Usage: clear/cls"); postError("Incorrect usage of clear/cls command. Usage: clear/cls");
return; return;
} }
$("#terminal tr:not(:last)").remove(); $("#terminal tr:not(:last)").remove();
postNetburnerText(); postNetburnerText();
break; break;
case "connect": { case "connect": {
// Disconnect from current server in terminal and connect to new one // Disconnect from current server in terminal and connect to new one
if (commandArray.length !== 2) { if (commandArray.length !== 2) {
postError("Incorrect usage of connect command. Usage: connect [ip/hostname]"); postError("Incorrect usage of connect command. Usage: connect [ip/hostname]");
return; return;
@@ -1022,7 +1059,7 @@ let Terminal = {
} }
postError("Host not found"); postError("Host not found");
break; break;
} }
case "download": { case "download": {
try { try {
@@ -1102,35 +1139,35 @@ let Terminal = {
post(result); post(result);
break; break;
} }
case "free": case "free":
Terminal.executeFreeCommand(commandArray); Terminal.executeFreeCommand(commandArray);
break; break;
case "hack": { case "hack": {
if (commandArray.length !== 1) { if (commandArray.length !== 1) {
postError("Incorrect usage of hack command. Usage: hack"); postError("Incorrect usage of hack command. Usage: hack");
return; return;
} }
// Hack the current PC (usually for money) // Hack the current PC (usually for money)
// You can't hack your home pc or servers you purchased // You can't hack your home pc or servers you purchased
if (s.purchasedByPlayer) { if (s.purchasedByPlayer) {
postError("Cannot hack your own machines! You are currently connected to your home PC or one of your purchased servers"); postError("Cannot hack your own machines! You are currently connected to your home PC or one of your purchased servers");
} else if (s.hasAdminRights == false ) { } else if (s.hasAdminRights == false ) {
postError("You do not have admin rights for this machine! Cannot hack"); postError("You do not have admin rights for this machine! Cannot hack");
} else if (s.requiredHackingSkill > Player.hacking_skill) { } else if (s.requiredHackingSkill > Player.hacking_skill) {
postError("Your hacking skill is not high enough to attempt hacking this machine. Try analyzing the machine to determine the required hacking skill"); postError("Your hacking skill is not high enough to attempt hacking this machine. Try analyzing the machine to determine the required hacking skill");
} else if (s instanceof HacknetServer) { } else if (s instanceof HacknetServer) {
postError("Cannot hack this type of Server") postError("Cannot hack this type of Server")
} else { } else {
Terminal.startHack(); Terminal.startHack();
} }
break; break;
} }
case "help": case "help":
if (commandArray.length !== 1 && commandArray.length !== 2) { if (commandArray.length !== 1 && commandArray.length !== 2) {
postError("Incorrect usage of help command. Usage: help"); postError("Incorrect usage of help command. Usage: help");
return; return;
} }
if (commandArray.length === 1) { if (commandArray.length === 1) {
post(TerminalHelpText); post(TerminalHelpText);
} else { } else {
var cmd = commandArray[1]; var cmd = commandArray[1];
@@ -1141,9 +1178,9 @@ let Terminal = {
} }
post(txt); post(txt);
} }
break; break;
case "home": case "home":
if (commandArray.length !== 1) { if (commandArray.length !== 1) {
postError("Incorrect usage of home command. Usage: home"); postError("Incorrect usage of home command. Usage: home");
return; return;
} }
@@ -1153,24 +1190,24 @@ let Terminal = {
post("Connected to home"); post("Connected to home");
Terminal.currDir = "/"; Terminal.currDir = "/";
Terminal.resetTerminalInput(); Terminal.resetTerminalInput();
break; break;
case "hostname": case "hostname":
if (commandArray.length !== 1) { if (commandArray.length !== 1) {
postError("Incorrect usage of hostname command. Usage: hostname"); postError("Incorrect usage of hostname command. Usage: hostname");
return; return;
} }
post(Player.getCurrentServer().hostname); post(Player.getCurrentServer().hostname);
break; break;
case "ifconfig": case "ifconfig":
if (commandArray.length !== 1) { if (commandArray.length !== 1) {
postError("Incorrect usage of ifconfig command. Usage: ifconfig"); postError("Incorrect usage of ifconfig command. Usage: ifconfig");
return; return;
} }
post(Player.getCurrentServer().ip); post(Player.getCurrentServer().ip);
break; break;
case "kill": { case "kill": {
Terminal.executeKillCommand(commandArray); Terminal.executeKillCommand(commandArray);
break; break;
} }
case "killall": { case "killall": {
for (let i = s.runningScripts.length - 1; i >= 0; --i) { for (let i = s.runningScripts.length - 1; i >= 0; --i) {
@@ -1180,9 +1217,9 @@ let Terminal = {
post("Killing all running scripts"); post("Killing all running scripts");
break; break;
} }
case "ls": { case "ls": {
Terminal.executeListCommand(commandArray); Terminal.executeListCommand(commandArray);
break; break;
} }
case "lscpu": { case "lscpu": {
post(Player.getCurrentServer().cpuCores + " Core(s)"); post(Player.getCurrentServer().cpuCores + " Core(s)");
@@ -1269,25 +1306,25 @@ let Terminal = {
break; break;
} }
case "nano": case "nano":
Terminal.executeNanoCommand(commandArray); Terminal.executeNanoCommand(commandArray);
break; break;
case "ps": case "ps":
if (commandArray.length !== 1) { if (commandArray.length !== 1) {
postError("Incorrect usage of ps command. Usage: ps"); postError("Incorrect usage of ps command. Usage: ps");
return; return;
} }
for (let i = 0; i < s.runningScripts.length; i++) { for (let i = 0; i < s.runningScripts.length; i++) {
let rsObj = s.runningScripts[i]; let rsObj = s.runningScripts[i];
let res = `(PID - ${rsObj.pid}) ${rsObj.filename}`; let res = `(PID - ${rsObj.pid}) ${rsObj.filename}`;
for (let j = 0; j < rsObj.args.length; ++j) { for (let j = 0; j < rsObj.args.length; ++j) {
res += (" " + rsObj.args[j].toString()); res += (" " + rsObj.args[j].toString());
} }
post(res); post(res);
} }
break; break;
case "rm": { case "rm": {
if (commandArray.length !== 2) { if (commandArray.length !== 2) {
postError("Incorrect number of arguments. Usage: rm [program/script]"); postError("Incorrect number of arguments. Usage: rm [program/script]");
return; return;
} }
@@ -1299,14 +1336,14 @@ let Terminal = {
if (!status.res) { if (!status.res) {
postError(status.msg); postError(status.msg);
} }
break; break;
} }
case "run": case "run":
// Run a program or a script // Run a program or a script
if (commandArray.length < 2) { if (commandArray.length < 2) {
postError("Incorrect number of arguments. Usage: run [program/script] [-t] [num threads] [arg1] [arg2]..."); postError("Incorrect number of arguments. Usage: run [program/script] [-t] [num threads] [arg1] [arg2]...");
} else { } else {
var executableName = commandArray[1]; var executableName = commandArray[1];
// Secret Music player! // Secret Music player!
if (executableName === "musicplayer") { if (executableName === "musicplayer") {
@@ -1314,19 +1351,19 @@ let Terminal = {
return; return;
} }
// Check if its a script or just a program/executable // Check if its a script or just a program/executable
if (isScriptFilename(executableName)) { if (isScriptFilename(executableName)) {
Terminal.runScript(commandArray); Terminal.runScript(commandArray);
} else if (executableName.endsWith(".cct")) { } else if (executableName.endsWith(".cct")) {
Terminal.runContract(executableName); Terminal.runContract(executableName);
} else { } else {
Terminal.runProgram(commandArray); Terminal.runProgram(commandArray);
} }
} }
break; break;
case "scan": case "scan":
Terminal.executeScanCommand(commandArray); Terminal.executeScanCommand(commandArray);
break; break;
case "scan-analyze": case "scan-analyze":
if (commandArray.length === 1) { if (commandArray.length === 1) {
Terminal.executeScanAnalyzeCommand(1); Terminal.executeScanAnalyzeCommand(1);
@@ -1362,7 +1399,7 @@ let Terminal = {
} }
break; break;
/* eslint-disable no-case-declarations */ /* eslint-disable no-case-declarations */
case "scp": case "scp":
Terminal.executeScpCommand(commandArray); Terminal.executeScpCommand(commandArray);
break; break;
/* eslint-enable no-case-declarations */ /* eslint-enable no-case-declarations */
@@ -1378,7 +1415,7 @@ let Terminal = {
post("You do NOT have root access to this machine"); post("You do NOT have root access to this machine");
} }
break; break;
case "tail": { case "tail": {
try { try {
if (commandArray.length < 2) { if (commandArray.length < 2) {
postError("Incorrect number of arguments. Usage: tail [script] [arg1] [arg2]..."); postError("Incorrect number of arguments. Usage: tail [script] [arg1] [arg2]...");
@@ -1407,7 +1444,7 @@ let Terminal = {
Terminal.postThrownError(e); Terminal.postThrownError(e);
} }
break; break;
} }
case "theme": { case "theme": {
let args = commandArray.slice(1); let args = commandArray.slice(1);
@@ -1455,11 +1492,11 @@ let Terminal = {
} }
break; break;
} }
case "top": { case "top": {
if (commandArray.length !== 1) { if (commandArray.length !== 1) {
postError("Incorrect usage of top command. Usage: top"); postError("Incorrect usage of top command. Usage: top");
return; return;
} }
// Headers // Headers
const scriptWidth = 40; const scriptWidth = 40;
@@ -1477,29 +1514,29 @@ let Terminal = {
const headers = `${scriptTxt}${spacesAfterScriptTxt}${pidTxt}${spacesAfterPidTxt}${threadsTxt}${spacesAfterThreadsTxt}${ramTxt}`; const headers = `${scriptTxt}${spacesAfterScriptTxt}${pidTxt}${spacesAfterPidTxt}${threadsTxt}${spacesAfterThreadsTxt}${ramTxt}`;
post(headers); post(headers);
let currRunningScripts = s.runningScripts; let currRunningScripts = s.runningScripts;
// Iterate through scripts on current server // Iterate through scripts on current server
for (let i = 0; i < currRunningScripts.length; i++) { for (let i = 0; i < currRunningScripts.length; i++) {
let script = currRunningScripts[i]; let script = currRunningScripts[i];
// Calculate name padding // Calculate name padding
const numSpacesScript = Math.max(0, scriptWidth - script.filename.length); const numSpacesScript = Math.max(0, scriptWidth - script.filename.length);
const spacesScript = " ".repeat(numSpacesScript); const spacesScript = " ".repeat(numSpacesScript);
// Calculate PID padding // Calculate PID padding
const numSpacesPid = Math.max(0, pidWidth - (script.pid + "").length); const numSpacesPid = Math.max(0, pidWidth - (script.pid + "").length);
const spacesPid = " ".repeat(numSpacesPid); const spacesPid = " ".repeat(numSpacesPid);
// Calculate thread padding // Calculate thread padding
const numSpacesThread = Math.max(0, threadsWidth - (script.threads + "").length); const numSpacesThread = Math.max(0, threadsWidth - (script.threads + "").length);
const spacesThread = " ".repeat(numSpacesThread); const spacesThread = " ".repeat(numSpacesThread);
// Calculate and transform RAM usage // Calculate and transform RAM usage
const ramUsage = numeralWrapper.formatRAM(getRamUsageFromRunningScript(script) * script.threads); const ramUsage = numeralWrapper.formatRAM(getRamUsageFromRunningScript(script) * script.threads);
const entry = [ const entry = [
script.filename, script.filename,
spacesScript, spacesScript,
script.pid, script.pid,
@@ -1508,9 +1545,9 @@ let Terminal = {
spacesThread, spacesThread,
ramUsage ramUsage
].join(""); ].join("");
post(entry); post(entry);
} }
break; break;
} }
case "unalias": { case "unalias": {
if (commandArray.length !== 2) { if (commandArray.length !== 2) {
@@ -1555,10 +1592,10 @@ let Terminal = {
}) })
break; break;
} }
default: default:
postError(`Command ${commandArray[0]} not found`); postError(`Command ${commandArray[0]} not found`);
} }
}, },
connectToServer: function(ip) { connectToServer: function(ip) {
var serv = getServer(ip); var serv = getServer(ip);
@@ -1783,12 +1820,13 @@ let Terminal = {
const script = Terminal.getScript(filename); const script = Terminal.getScript(filename);
if (script == null) { if (script == null) {
let code = "" let code = ""
if(filename.endsWith(".ns")) { if(filename.endsWith(".ns") || filename.endsWith(".js")) {
code = `export async function main(ns) { code = `export async function main(ns) {
}`; }`;
} }
Engine.loadScriptEditorContent(filepath, code); Engine.loadScriptEditorContent(filepath, code);
getCurrentEditor().setCursor({row: 1, column: 4});
} else { } else {
Engine.loadScriptEditorContent(filepath, script.code); Engine.loadScriptEditorContent(filepath, script.code);
} }
@@ -2003,24 +2041,24 @@ let Terminal = {
} }
}, },
// First called when the "run [program]" command is called. Checks to see if you // First called when the "run [program]" command is called. Checks to see if you
// have the executable and, if you do, calls the executeProgram() function // have the executable and, if you do, calls the executeProgram() function
runProgram: function(commandArray) { runProgram: function(commandArray) {
if (commandArray.length < 2) { return; } if (commandArray.length < 2) { return; }
// Check if you have the program on your computer. If you do, execute it, otherwise // Check if you have the program on your computer. If you do, execute it, otherwise
// display an error message // display an error message
const programName = commandArray[1]; const programName = commandArray[1];
if (Player.hasProgram(programName)) { if (Player.hasProgram(programName)) {
Terminal.executeProgram(commandArray); Terminal.executeProgram(commandArray);
return; return;
} }
post("ERROR: No such executable on home computer (Only programs that exist on your home computer can be run)"); post("ERROR: No such executable on home computer (Only programs that exist on your home computer can be run)");
}, },
// Contains the implementations of all possible programs // Contains the implementations of all possible programs
executeProgram: function(commandArray) { executeProgram: function(commandArray) {
if (commandArray.length < 2) { return; } if (commandArray.length < 2) { return; }
var s = Player.getCurrentServer(); var s = Player.getCurrentServer();
@@ -2173,7 +2211,7 @@ let Terminal = {
} }
programHandlers[programName](s, splitArgs); programHandlers[programName](s, splitArgs);
}, },
/** /**
* Given a filename, returns that file's full path. This takes into account * Given a filename, returns that file's full path. This takes into account
@@ -2271,13 +2309,13 @@ let Terminal = {
} }
}, },
runScript: function(commandArray) { runScript: function(commandArray) {
if (commandArray.length < 2) { if (commandArray.length < 2) {
dialogBoxCreate(`Bug encountered with Terminal.runScript(). Command array has a length of less than 2: ${commandArray}`); dialogBoxCreate(`Bug encountered with Terminal.runScript(). Command array has a length of less than 2: ${commandArray}`);
return; return;
} }
const server = Player.getCurrentServer(); const server = Player.getCurrentServer();
let numThreads = 1; let numThreads = 1;
const args = []; const args = [];
@@ -2307,38 +2345,38 @@ let Terminal = {
return; return;
} }
// Check if the script exists and if it does run it // Check if the script exists and if it does run it
for (var i = 0; i < server.scripts.length; i++) { for (var i = 0; i < server.scripts.length; i++) {
if (server.scripts[i].filename === scriptName) { if (server.scripts[i].filename === scriptName) {
// Check for admin rights and that there is enough RAM availble to run // Check for admin rights and that there is enough RAM availble to run
var script = server.scripts[i]; var script = server.scripts[i];
var ramUsage = script.ramUsage * numThreads; var ramUsage = script.ramUsage * numThreads;
var ramAvailable = server.maxRam - server.ramUsed; var ramAvailable = server.maxRam - server.ramUsed;
if (server.hasAdminRights == false) { if (server.hasAdminRights == false) {
post("Need root access to run script"); post("Need root access to run script");
return; return;
} else if (ramUsage > ramAvailable){ } else if (ramUsage > ramAvailable){
post("This machine does not have enough RAM to run this script with " + post("This machine does not have enough RAM to run this script with " +
numThreads + " threads. Script requires " + ramUsage + "GB of RAM"); numThreads + " threads. Script requires " + ramUsage + "GB of RAM");
return; return;
} else { } else {
// Able to run script // Able to run script
var runningScriptObj = new RunningScript(script, args); var runningScriptObj = new RunningScript(script, args);
runningScriptObj.threads = numThreads; runningScriptObj.threads = numThreads;
if (startWorkerScript(runningScriptObj, server)) { if (startWorkerScript(runningScriptObj, server)) {
post("Running script with " + numThreads + " thread(s) and args: " + arrayToString(args) + "."); post(`Running script with ${numThreads} thread(s), pid ${runningScriptObj.pid} and args: ${arrayToString(args)}.`);
} else { } else {
postError(`Failed to start script`); postError(`Failed to start script`);
} }
return; return;
} }
} }
} }
post("ERROR: No such script"); post("ERROR: No such script");
}, },
runContract: async function(contractName) { runContract: async function(contractName) {
// There's already an opened contract // There's already an opened contract

View File

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

View File

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

View File

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

View File

@@ -72,6 +72,7 @@ import {
processStockPrices, processStockPrices,
displayStockMarketContent displayStockMarketContent
} from "./StockMarket/StockMarket"; } from "./StockMarket/StockMarket";
import { displayMilestonesContent } from "./Milestones/MilestoneHelpers";
import { Terminal, postNetburnerText } from "./Terminal"; import { Terminal, postNetburnerText } from "./Terminal";
import { Sleeve } from "./PersonObjects/Sleeve/Sleeve"; import { Sleeve } from "./PersonObjects/Sleeve/Sleeve";
import { import {
@@ -216,6 +217,7 @@ const Engine = {
factionsContent: null, factionsContent: null,
factionContent: null, factionContent: null,
augmentationsContent: null, augmentationsContent: null,
milestonesContent: null,
tutorialContent: null, tutorialContent: null,
infiltrationContent: null, infiltrationContent: null,
stockMarketContent: null, stockMarketContent: null,
@@ -311,6 +313,14 @@ const Engine = {
MainMenuLinks.Augmentations.classList.add("active"); MainMenuLinks.Augmentations.classList.add("active");
}, },
loadMilestonesContent: function() {
Engine.hideAllContent();
Engine.Display.milestonesContent.style.display = "block";
routing.navigateTo(Page.Milestones);
displayMilestonesContent();
MainMenuLinks.Milestones.classList.add("active");
},
loadTutorialContent: function() { loadTutorialContent: function() {
Engine.hideAllContent(); Engine.hideAllContent();
Engine.Display.tutorialContent.style.display = "block"; Engine.Display.tutorialContent.style.display = "block";
@@ -496,6 +506,7 @@ const Engine = {
Engine.Display.augmentationsContent.style.display = "none"; Engine.Display.augmentationsContent.style.display = "none";
ReactDOM.unmountComponentAtNode(Engine.Display.augmentationsContent); ReactDOM.unmountComponentAtNode(Engine.Display.augmentationsContent);
Engine.Display.milestonesContent.style.display = "none";
Engine.Display.tutorialContent.style.display = "none"; Engine.Display.tutorialContent.style.display = "none";
Engine.Display.locationContent.style.display = "none"; Engine.Display.locationContent.style.display = "none";
@@ -550,6 +561,7 @@ const Engine = {
MainMenuLinks.Bladeburner.classList.remove("active"); MainMenuLinks.Bladeburner.classList.remove("active");
MainMenuLinks.Corporation.classList.remove("active"); MainMenuLinks.Corporation.classList.remove("active");
MainMenuLinks.Gang.classList.remove("active"); MainMenuLinks.Gang.classList.remove("active");
MainMenuLinks.Milestones.classList.remove("active");
MainMenuLinks.Tutorial.classList.remove("active"); MainMenuLinks.Tutorial.classList.remove("active");
MainMenuLinks.Options.classList.remove("active"); MainMenuLinks.Options.classList.remove("active");
MainMenuLinks.DevMenu.classList.remove("active"); MainMenuLinks.DevMenu.classList.remove("active");
@@ -777,7 +789,7 @@ const Engine = {
createProgramNotifications: 10, createProgramNotifications: 10,
augmentationsNotifications: 10, augmentationsNotifications: 10,
checkFactionInvitations: 100, checkFactionInvitations: 100,
passiveFactionGrowth: 600, passiveFactionGrowth: 5,
messages: 150, messages: 150,
mechanicProcess: 5, // Processes certain mechanics (Corporation, Bladeburner) mechanicProcess: 5, // Processes certain mechanics (Corporation, Bladeburner)
contractGeneration: 3000, // Generate Coding Contracts contractGeneration: 3000, // Generate Coding Contracts
@@ -911,9 +923,9 @@ const Engine = {
} }
if (Engine.Counters.passiveFactionGrowth <= 0) { if (Engine.Counters.passiveFactionGrowth <= 0) {
var adjustedCycles = Math.floor((600 - Engine.Counters.passiveFactionGrowth)); var adjustedCycles = Math.floor((5 - Engine.Counters.passiveFactionGrowth));
processPassiveFactionRepGain(adjustedCycles); processPassiveFactionRepGain(adjustedCycles);
Engine.Counters.passiveFactionGrowth = 600; Engine.Counters.passiveFactionGrowth = 5;
} }
if (Engine.Counters.messages <= 0) { if (Engine.Counters.messages <= 0) {
@@ -1055,6 +1067,7 @@ const Engine = {
const bladeburner = document.getElementById("bladeburner-tab"); const bladeburner = document.getElementById("bladeburner-tab");
const corp = document.getElementById("corporation-tab"); const corp = document.getElementById("corporation-tab");
const gang = document.getElementById("gang-tab"); const gang = document.getElementById("gang-tab");
const milestones = document.getElementById("milestones-tab");
const tutorial = document.getElementById("tutorial-tab"); const tutorial = document.getElementById("tutorial-tab");
const options = document.getElementById("options-tab"); const options = document.getElementById("options-tab");
const dev = document.getElementById("dev-tab"); const dev = document.getElementById("dev-tab");
@@ -1097,7 +1110,7 @@ const Engine = {
// Hacknet Nodes offline progress // Hacknet Nodes offline progress
var offlineProductionFromHacknetNodes = processHacknetEarnings(numCyclesOffline); var offlineProductionFromHacknetNodes = processHacknetEarnings(numCyclesOffline);
const hacknetProdInfo = hasHacknetServers() ? const hacknetProdInfo = hasHacknetServers() ?
Hashes(offlineProductionFromHacknetNodes): <>{Hashes(offlineProductionFromHacknetNodes)} hashes</>:
Money(offlineProductionFromHacknetNodes); Money(offlineProductionFromHacknetNodes);
// Passive faction rep gain offline // Passive faction rep gain offline
@@ -1151,11 +1164,12 @@ const Engine = {
removeLoadingScreen(); removeLoadingScreen();
const timeOfflineString = convertTimeMsToTimeElapsedString(time); const timeOfflineString = convertTimeMsToTimeElapsedString(time);
dialogBoxCreate(<> dialogBoxCreate(<>
Offline for {timeOfflineString}. While you were offline, your scripts generated {Money(offlineProductionFromScripts)} and your Hacknet Nodes generated {hacknetProdInfo} hashes. Offline for {timeOfflineString}. While you were offline, your scripts generated {Money(offlineProductionFromScripts)} and your Hacknet Nodes generated {hacknetProdInfo}.
</>); </>);
// Close main menu accordions for loaded game // Close main menu accordions for loaded game
var visibleMenuTabs = [terminal, createScript, activeScripts, stats, var visibleMenuTabs = [terminal, createScript, activeScripts, stats,
hacknetnodes, city, tutorial, options, dev]; hacknetnodes, city, milestones, tutorial,
options, dev];
if (Player.firstFacInvRecvd) {visibleMenuTabs.push(factions);} if (Player.firstFacInvRecvd) {visibleMenuTabs.push(factions);}
else {factions.style.display = "none";} else {factions.style.display = "none";}
if (Player.firstAugPurchased) {visibleMenuTabs.push(augmentations);} if (Player.firstAugPurchased) {visibleMenuTabs.push(augmentations);}
@@ -1214,7 +1228,7 @@ const Engine = {
Engine.openMainMenuHeader( Engine.openMainMenuHeader(
[terminal, createScript, activeScripts, stats, [terminal, createScript, activeScripts, stats,
hacknetnodes, city, hacknetnodes, city, milestones,
tutorial, options] tutorial, options]
); );
@@ -1257,6 +1271,8 @@ const Engine = {
Engine.Display.augmentationsContent = document.getElementById("augmentations-container"); Engine.Display.augmentationsContent = document.getElementById("augmentations-container");
Engine.Display.augmentationsContent.style.display = "none"; Engine.Display.augmentationsContent.style.display = "none";
Engine.Display.milestonesContent = document.getElementById("milestones-container");
Engine.Display.milestonesContent.style.display = "none";
Engine.Display.tutorialContent = document.getElementById("tutorial-container"); Engine.Display.tutorialContent = document.getElementById("tutorial-container");
Engine.Display.tutorialContent.style.display = "none"; Engine.Display.tutorialContent.style.display = "none";
@@ -1398,6 +1414,11 @@ const Engine = {
return false; return false;
}); });
MainMenuLinks.Milestones.addEventListener("click", function() {
Engine.loadMilestonesContent();
return false;
});
MainMenuLinks.Tutorial.addEventListener("click", function() { MainMenuLinks.Tutorial.addEventListener("click", function() {
Engine.loadTutorialContent(); Engine.loadTutorialContent();
return false; return false;
@@ -1480,6 +1501,7 @@ const Engine = {
document.getElementById("active-scripts-menu-link").removeAttribute("class"); document.getElementById("active-scripts-menu-link").removeAttribute("class");
document.getElementById("hacknet-nodes-menu-link").removeAttribute("class"); document.getElementById("hacknet-nodes-menu-link").removeAttribute("class");
document.getElementById("city-menu-link").removeAttribute("class"); document.getElementById("city-menu-link").removeAttribute("class");
document.getElementById("milestones-menu-link").removeAttribute("class");
document.getElementById("tutorial-menu-link").removeAttribute("class"); document.getElementById("tutorial-menu-link").removeAttribute("class");
// Copy Save Data to Clipboard // Copy Save Data to Clipboard

View File

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

View File

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

View File

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

View File

@@ -40,7 +40,7 @@ export function CharacterInfo(p: IPlayer): React.ReactElement {
function Hacknet(): React.ReactElement { function Hacknet(): React.ReactElement {
// Can't import HacknetHelpers for some reason. // Can't import HacknetHelpers for some reason.
if(!(p.bitNodeN === 9 || SourceFileFlags[9] > 0)) { if(!(p.bitNodeN === 9 || SourceFileFlags[9] > 0)) {
return <><span>{`Hacknet Nodes owned: ${p.hacknetNodes.length}</span>`}</span><br /></> return <><span>{`Hacknet Nodes owned: ${p.hacknetNodes.length}`}</span><br /></>
} else { } else {
return <><span>{`Hacknet Servers owned: ${p.hacknetNodes.length} / ${HacknetServerConstants.MaxServers}`}</span><br /></> return <><span>{`Hacknet Servers owned: ${p.hacknetNodes.length} / ${HacknetServerConstants.MaxServers}`}</span><br /></>
} }
@@ -51,6 +51,7 @@ export function CharacterInfo(p: IPlayer): React.ReactElement {
if (src.bladeburner) { parts.push([`Bladeburner:`, Money(src.bladeburner)]) }; if (src.bladeburner) { parts.push([`Bladeburner:`, Money(src.bladeburner)]) };
if (src.codingcontract) { parts.push([`Coding Contracts:`, Money(src.codingcontract)]) }; if (src.codingcontract) { parts.push([`Coding Contracts:`, Money(src.codingcontract)]) };
if (src.work) { parts.push([`Company Work:`, Money(src.work)]) }; if (src.work) { parts.push([`Company Work:`, Money(src.work)]) };
if (src.class) { parts.push([`Class:`, Money(src.class)]) };
if (src.corporation) { parts.push([`Corporation:`, Money(src.corporation)]) }; if (src.corporation) { parts.push([`Corporation:`, Money(src.corporation)]) };
if (src.crime) { parts.push([`Crimes:`, Money(src.crime)]) }; if (src.crime) { parts.push([`Crimes:`, Money(src.crime)]) };
if (src.gang) { parts.push([`Gang:`, Money(src.gang)]) }; if (src.gang) { parts.push([`Gang:`, Money(src.gang)]) };

View File

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

View File

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

View File

@@ -19,7 +19,7 @@ export class CharacterOverviewComponent extends Component {
<table> <table>
<tbody> <tbody>
<tr id="character-hp-wrapper"> <tr id="character-hp-wrapper">
<td className="character-hp-cell">Hp:</td><td id="character-hp-text" className="character-hp-cell character-stat-cell">{Player.hp + " / " + Player.max_hp}</td> <td className="character-hp-cell">Hp:</td><td id="character-hp-text" className="character-hp-cell character-stat-cell">{numeralWrapper.formatHp(Player.hp) + " / " + numeralWrapper.formatHp(Player.max_hp)}</td>
</tr> </tr>
<tr id="character-money-wrapper"> <tr id="character-money-wrapper">
<td className="character-money-cell">Money:&nbsp;</td><td id="character-money-text" className="character-money-cell character-stat-cell">{numeralWrapper.formatMoney(Player.money.toNumber())}</td> <td className="character-money-cell">Money:&nbsp;</td><td id="character-money-text" className="character-money-cell character-stat-cell">{numeralWrapper.formatMoney(Player.money.toNumber())}</td>

Some files were not shown because too many files have changed in this diff Show More