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

View File

@@ -7,4 +7,17 @@ Update the following
- `doc/source/conf.py` `version` and `release`
- `doc/source/changelog.rst`
- 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 {
display: inline-block;
position: fixed;
z-index: 2;
-webkit-animation: status-text 3s 1;
}
#status-text-container {
background-color: transparent;
position:absolute;
top:0;
left:50%;
}
#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

33
dist/engineStyle.css vendored
View File

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

78
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

View File

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

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
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
^^^

View File

@@ -3,6 +3,152 @@
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)
----------------------------------------------

View File

@@ -66,7 +66,7 @@ documentation_title = '{0} Documentation'.format(project)
# The short X.Y version.
version = '0.51'
# 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
# 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.
This is a random value between 51 and 60. Although you receive the message
from CSEC once you hit 50 hacking, you cannot actually pass their test
until your hacking is high enough to hack their server.
until your hacking is high enough to install a backdoor on their server.
After you are connected to the :code:`CSEC` server, you can hack it. Note that this
server requires one open port in order to gain root access. We can open the SSH port
@@ -642,10 +642,10 @@ using the :code:`BruteSSH.exe` program we created earlier. In |Terminal|::
$ run BruteSSH.exe
$ run NUKE.exe
$ hack
$ backdoor
Keep hacking the server until you are successful. After you successfully hack it, you should
receive a faction invitation from |CyberSec| shortly afterwards. Accept it. If you accidentally
After you successfully install the backdoor, you should receive a faction
invitation from |CyberSec| shortly afterwards. Accept it. If you accidentally
reject the invitation, that's okay. Just go to the :code:`Factions` tab
(|Keyboard shortcut| Alt + f) and you should see an option that lets you
accept the invitation.

View File

@@ -278,6 +278,7 @@ Description
hashes, which can be spent on a variety of different upgrades.
In this BitNode:
* Your stats are significantly decreased
* You cannnot purchase additional servers
* Hacking is significantly less profitable
@@ -292,7 +293,7 @@ Source-File
* Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode
(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT
when installing Augmentation
when installing Augmentations.)
Difficulty
Hard
@@ -312,6 +313,7 @@ Description
2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks synchronously
In this BitNode:
* Your stats are significantly decreased
* 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

View File

@@ -27,7 +27,7 @@ getServer() Netscript Function
sshPortOpen
baseDifficulty
hackDifficulty
manuallyHacked
backdoorInstalled
minDifficulty
moneyAvailable
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
==============================
.. js:function:: isRunning(filename, hostname, [args...])
.. js:function:: isRunning(filename[, hostname=current hostname[, args...]])
:RAM cost: 0.1 GB
: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
:returns: ``true`` if that script with those args is running on that server.
@@ -38,3 +38,17 @@ isRunning() Netscript Function
.. code-block:: javascript
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
===========================
.. js:function:: print(x)
.. js:function:: print(args...)
: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:
.. 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 number ram: Amount of RAM of the purchased server. Must be a power of
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.

View File

@@ -1,7 +1,7 @@
tail() Netscript Function
==================================
.. js:function:: tail([fn[, hostname=current hostname[, [...args]]])
.. js:function:: tail([fn[, hostname=current hostname[, ...args]])
:RAM cost: 0 GB
: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"]
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
===========================
.. js:function:: tprint(x)
.. js:function:: tprint(args...)
: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:
.. 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>
getScriptName() <basicfunctions/getScriptName>
getScriptRam() <basicfunctions/getScriptRam>
getRunningScript() <basicfunctions/getRunningScript>
getHackTime() <basicfunctions/getHackTime>
getGrowTime() <basicfunctions/getGrowTime>
getWeakenTime() <basicfunctions/getWeakenTime>
@@ -88,3 +89,4 @@ This includes information such as function signatures, what they do, and their r
prompt() <basicfunctions/prompt>
wget() <basicfunctions/wget>
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
* 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
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.
This function will perform a manual hack on the server you are currently connected to.
This is typically required to join factions.
Examples:
.. code-block:: javascript
connect("CSEC");
connect("foodnstuff");
manualHack();
.. warning::

View File

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

View File

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

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"
},
"dependencies": {
"@material-ui/core": "^4.11.3",
"@types/numeral": "0.0.25",
"@types/react": "^16.8.6",
"@types/react-dom": "^16.8.2",
@@ -13,6 +14,7 @@
"acorn-walk": "^6.2.0",
"ajv": "^5.1.5",
"ajv-keywords": "^2.0.0",
"arg": "^5.0.0",
"async": "^2.6.1",
"autosize": "^4.0.2",
"brace": "^0.11.1",
@@ -46,7 +48,10 @@
"@babel/core": "^7.3.4",
"@babel/preset-react": "^7.0.0",
"@types/chai": "^4.1.7",
"@types/lodash": "^4.14.168",
"@types/mocha": "^5.2.7",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
"babel-loader": "^8.0.5",
"beautify-lint": "^1.0.3",
"benchmark": "^2.1.1",
@@ -54,8 +59,8 @@
"chai": "^4.2.0",
"css-loader": "^0.28.11",
"es6-promise-polyfill": "^1.1.1",
"eslint": "^4.19.1",
"eslint-plugin-node": "^6.0.1",
"eslint": "^7.24.0",
"eslint-plugin-node": "^11.1.0",
"file-loader": "^1.1.11",
"html-webpack-plugin": "^3.2.0",
"i18n-webpack-plugin": "^1.0.0",
@@ -85,8 +90,7 @@
"stylelint-declaration-use-variable": "^1.6.1",
"stylelint-order": "^0.8.1",
"ts-loader": "^4.5.0",
"tslint": "^5.10.0",
"typescript": "^2.9.2",
"typescript": "^4.2.4",
"uglify-es": "^3.3.9",
"uglifyjs-webpack-plugin": "^1.3.0",
"url-loader": "^1.0.1",
@@ -112,14 +116,13 @@
"build": "webpack --mode production",
"build:dev": "webpack --mode development",
"build:test": "webpack --config webpack.config-test.js",
"lint": "npm run lint:typescript & npm run lint:javascript & npm run lint:style",
"lint:javascript": "eslint *.js ./src/**/*.js ./tests/**/*.js ./utils/**/*.js",
"lint": "npm run lint:jsts & npm run lint:style",
"lint:jsts": "eslint '*.{js,jsx,ts,tsx}' './src/**/*.{js,jsx,ts,tsx}' './test/**/*.{js,jsx,ts,tsx}' './utils/**/*.{js,jsx,ts,tsx}'",
"lint:style": "stylelint ./css/*",
"lint:typescript": "tslint --project . --exclude **/*.d.ts --format stylish src/**/*.ts utils/**/*.ts",
"preinstall": "node ./scripts/engines-check.js",
"test": "mochapack --webpack-config webpack.config-test.js -r jsdom-global/register ./test/index.js",
"watch": "webpack --watch --mode production",
"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
export function parseAliasDeclaration(dec: string, global: boolean=false) {
export function parseAliasDeclaration(dec: string, global = false): boolean {
var re = /^([_|\w|!|%|,|@]+)="(.+)"$/;
var matches = dec.match(re);
if (matches == null || matches.length != 3) {return false;}

View File

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

View File

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

View File

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

View File

@@ -234,7 +234,7 @@ BitNodes["BitNode11"] = new BitNode(11, "The Big Crash", "Okay. Sell it all.",
"Level 3: 56%");
BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.",
"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 " +
"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)");

View File

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

View File

@@ -21,7 +21,7 @@ export const BlackOperations: IMap<BlackOperation> = {};
BlackOperations["Operation Zero"] = new BlackOperation({
name:"Operation Zero",
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>" +
"The goal of Operation Zero is to covertly infiltrate AeroCorp and " +
"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 { IPlayer } from "../PersonObjects/IPlayer";
import { StdButton } from "../ui/React/StdButton";
import { BadRNG } from "./RNG";
import { Game } from "./Game";
import { IPlayer } from "../PersonObjects/IPlayer";
import { StdButton } from "../ui/React/StdButton";
import { BadRNG } from "./RNG";
import { Game } from "./Game";
import { trusted } from "./utils";
type IProps = {
p: IPlayer;
@@ -18,8 +19,10 @@ type IState = {
investment: number;
result: any;
status: string;
playLock: boolean;
}
const minPlay = 0;
const maxPlay = 10e3;
export class CoinFlip extends Game<IProps, IState> {
@@ -31,6 +34,7 @@ export class CoinFlip extends Game<IProps, IState> {
investment: 1000,
result: <span> </span>,
status: '',
playLock: false,
};
this.play = this.play.bind(this);
@@ -40,11 +44,14 @@ export class CoinFlip extends Game<IProps, IState> {
updateInvestment(e: React.FormEvent<HTMLInputElement>) {
let investment: number = parseInt(e.currentTarget.value);
if (isNaN(investment)) {
investment = 1000;
investment = minPlay;
}
if (investment > maxPlay) {
investment = maxPlay;
}
if (investment < minPlay) {
investment = minPlay;
}
this.setState({investment: investment});
}
@@ -61,7 +68,9 @@ export class CoinFlip extends Game<IProps, IState> {
this.setState({
result: <span className={correct ? "text" : "failure"}>{letter}</span>,
status: correct ? " win!" : "lose!",
playLock: true,
});
setTimeout(()=>this.setState({playLock: false}), 250);
if (correct) {
this.win(this.props.p, this.state.investment);
} else {
@@ -81,8 +90,8 @@ export class CoinFlip extends Game<IProps, IState> {
++<br />
</pre>
<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={() => this.play('T')} text={"Tail!"} />
<StdButton onClick={trusted(() => this.play('H'))} text={"Head!"} disabled={this.state.playLock} />
<StdButton onClick={trusted(() => this.play('T'))} text={"Tail!"} disabled={this.state.playLock} />
<h1>{this.state.status}</h1>
</>
}

View File

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

View File

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

View File

@@ -4,7 +4,8 @@ import { IPlayer } from "../PersonObjects/IPlayer";
import { StdButton } from "../ui/React/StdButton";
import { Money } from "../ui/React/Money";
import { WHRNG } from "./RNG";
import { Game } from "./Game";
import { Game } from "./Game";
import { trusted } from "./utils";
type IProps = {
p: IPlayer;
@@ -57,6 +58,7 @@ const payLines = [
[[1, 0], [2, 1], [2, 2], [2, 3], [1, 4]],
];
const minPlay = 0;
const maxPlay = 1e6;
export class SlotMachine extends Game<IProps, IState> {
@@ -184,11 +186,14 @@ export class SlotMachine extends Game<IProps, IState> {
updateInvestment(e: React.FormEvent<HTMLInputElement>) {
let investment: number = parseInt(e.currentTarget.value);
if (isNaN(investment)) {
investment = 1000;
investment = minPlay;
}
if (investment > maxPlay) {
investment = maxPlay;
}
if (investment < minPlay) {
investment = minPlay;
}
this.setState({investment: investment});
}
@@ -205,7 +210,7 @@ export class SlotMachine extends Game<IProps, IState> {
++<br />
</pre>
<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>
<h2>Pay lines</h2>
<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) => {
const contractType: CodingContractType = CodingContractTypes[this.type];
const popupId: string = `coding-contract-prompt-popup-${this.fn}`;
const title: HTMLElement = createElement("h1", {
innerHTML: this.type,
});
const txt: HTMLElement = createElement("p", {
innerHTML: ["You are attempting to solve a Coding Contract. You have",
`${this.getMaxNumTries() - this.tries} tries remaining,`,
@@ -225,7 +228,7 @@ export class CodingContract {
innerText: "Cancel",
});
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();
});
}

View File

@@ -6,13 +6,13 @@
import { IMap } from "./types";
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
* 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
MilliPerCycle: 200,
@@ -218,7 +218,7 @@ export let CONSTANTS: IMap<any> = {
CrimeHeist: "pull off the ultimate heist",
// Coding Contract
// TODO Move this into Coding contract impelmentation?
// TODO: Move this into Coding contract implementation?
CodingContractBaseFactionRepGain: 2500,
CodingContractBaseCompanyRepGain: 4000,
CodingContractBaseMoneyGain: 75e6,
@@ -228,16 +228,42 @@ export let CONSTANTS: IMap<any> = {
LatestUpdate:
`
v0.51.2 - 2021-04-09 Vegas, Baby! (hydroflame)
v0.51.6 - 2021-04-28 Backdoor! (Community)
-------
New location: The Iker Molina Casino
* A casino opened in Aevum. However the house is rumored to cheat. If only
we could give them a taste of their own medicine.
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.
* Link to discord added under options
* 'getMemberInformation' doc updated, oops
* tech vendor now handle max ram and cores.
* 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)
`
}
}

View File

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

View File

@@ -15,6 +15,11 @@ import { Factions } from "./Factions";
import { HackingMission, setInMission } from "../Missions";
import { Player } from "../Player";
import { Settings } from "../Settings/Settings";
import {
getHackingWorkRepGain,
getFactionSecurityWorkRepGain,
getFactionFieldWorkRepGain,
} from "../PersonObjects/formulas/reputation";
import { Page, routing } from "../ui/navigationTracking";
import { dialogBoxCreate } from "../../utils/DialogBox";
@@ -235,15 +240,24 @@ export function getNextNeurofluxLevel() {
}
export function processPassiveFactionRepGain(numCycles) {
var numTimesGain = (numCycles / 600) * Player.faction_rep_mult;
for (var name in Factions) {
if (Factions.hasOwnProperty(name)) {
var faction = Factions[name];
for (const name in Factions) {
if (name === Player.currentWorkFactionName) continue;
if (!Factions.hasOwnProperty(name)) continue;
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..
//maybe later make this based on
//a player's 'status' like how powerful they are and how much money they have
if (faction.isMember) {faction.playerReputation += (numTimesGain * BitNodeMultipliers.FactionPassiveRepGain);}
}
}
faction.playerReputation += rate *
(numCycles) *
Player.faction_rep_mult *
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())) {
disabled = true;
} else if (this.hasReputation()) {
status = <>UNLOCKED - {Money(moneyCost)}</>;
status = <>UNLOCKED (at {Reputation(repCost)} faction reputation) - {Money(moneyCost)}</>;
} else {
disabled = true;
status = <>LOCKED (Requires {Reputation(repCost)} faction reputation - {Money(moneyCost)})</>;

View File

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

View File

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

View File

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

View File

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

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 " +
"shall be no more, neither shall there be mourning, nor crying, nor pain anymore, for the former things " +
"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, " +
"'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 " +

View File

@@ -78,7 +78,7 @@ export function createTravelPopup(destination: CityName, travelFn: TravelFunctio
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>);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,6 +9,9 @@ import { Location } from "../Location";
import { CONSTANTS } from "../../Constants";
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 { StdButton } from "../../ui/React/StdButton";
@@ -37,11 +40,22 @@ export class UniversityLocation extends React.Component<IProps, any> {
this.algorithms = this.algorithms.bind(this);
this.management = this.management.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) {
const loc = this.props.loc;
this.props.p.startClass(loc.costMult, loc.expMult, stat);
this.props.p.startClass(this.calculateCost(), loc.expMult, stat);
}
study() {
@@ -69,7 +83,7 @@ export class UniversityLocation extends React.Component<IProps, any> {
}
render() {
const costMult: number = this.props.loc.costMult;
const costMult: number = this.calculateCost();
const dataStructuresCost = CONSTANTS.ClassDataStructuresBaseCost * 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 " +
"your talents. If you join us, you can put your skills to good use and change " +
"the world for the better. If you join us, we can unlock your full potential. <br><br>" +
"But first, you must pass our test. Find and hack our server using the Terminal. <br><br>" +
"But first, you must pass our test. Find and install the backdoor on our server. <br><br>" +
"-CyberSec"));
AddToAllMessages(new Message(MessageFilenames.NiteSecTest,
"People say that the corrupted governments and corporations rule the world. " +
@@ -161,7 +161,7 @@ function initMessages() {
"like us. Because they can't hide from us. Because they can't fight shadows " +
"and ideas with bullets. <br><br>" +
"Join us, and people will fear you, too. <br><br>" +
"Find and hack our hidden server using the Terminal. Then, we will contact you again." +
"Find and install the backdoor on our server. Then, we will contact you again." +
"<br><br>-NiteSec"));
AddToAllMessages(new Message(MessageFilenames.BitRunnersTest,
"We know what you are doing. We know what drives you. We know " +

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

View File

@@ -1,5 +1,6 @@
const sprintf = require("sprintf-js").sprintf;
const vsprintf = require("sprintf-js").vsprintf;
import * as libarg from 'arg';
import { getRamCost } from "./Netscript/RamCostGenerator";
import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStartStopEventEmitter";
@@ -88,7 +89,10 @@ import { inMission } from "./Missions";
import { Player } from "./Player";
import { Programs } from "./Programs/Programs";
import { Script } from "./Script/Script";
import { findRunningScript } from "./Script/ScriptHelpers";
import {
findRunningScript,
findRunningScriptByPid,
} from "./Script/ScriptHelpers";
import { isScriptFilename } from "./Script/ScriptHelpersTS";
import { _getScriptUrls } from "./NetscriptJSEvaluator";
import {
@@ -162,6 +166,7 @@ import {
netscriptDelay,
resolveNetscriptRequestedThreads,
} from "./NetscriptEvaluator";
import { Interpreter } from "./JSInterpreter";
import { NetscriptPort } from "./NetscriptPort";
import { SleeveTaskType } from "./PersonObjects/Sleeve/SleeveTaskTypesEnum";
import { findSleevePurchasableAugs } from "./PersonObjects/Sleeve/SleeveHelpers";
@@ -256,6 +261,39 @@ const possibleLogs = {
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) {
const updateDynamicRam = function(fnName, ramCost) {
if (workerScript.dynamicLoadedFns[fnName]) { return; }
@@ -305,7 +343,6 @@ function NetscriptFunctions(workerScript) {
* is not specified.
*/
const getRunningScript = function(fn, ip, callingFnName, scriptArgs) {
// Sanitize arguments
if (typeof callingFnName !== "string" || callingFnName === "") {
callingFnName = "getRunningScript";
}
@@ -330,6 +367,19 @@ function NetscriptFunctions(workerScript) {
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
* a nonexistent running script
@@ -673,7 +723,7 @@ function NetscriptFunctions(workerScript) {
influenceStockThroughServerHack(server, moneyGained);
}
if(manual) {
server.manuallyHacked = true;
server.backdoorInstalled = true;
}
return Promise.resolve(moneyGained);
} 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 {
hacknet : {
numNodes : function() {
return Player.hacknetNodes.length;
},
maxNumNodes : function() {
return MaxNumberHacknetServers;
if (hasHacknetServers()) {
return HacknetServerConstants.MaxServers;
}
return Infinity;
},
purchaseNode : function() {
return purchaseHacknet();
@@ -908,7 +975,7 @@ function NetscriptFunctions(workerScript) {
// Check argument validity
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}.`);
}
@@ -944,18 +1011,17 @@ function NetscriptFunctions(workerScript) {
return Promise.resolve(CONSTANTS.ServerWeakenAmount * threads);
});
},
print: function(args){
if (args === undefined) {
throw makeRuntimeErrorMsg("print", "Takes 1 argument.");
print: function(){
if (arguments.length === 0) {
throw makeRuntimeErrorMsg("print", "Takes at least 1 argument.");
}
workerScript.print(args.toString());
workerScript.print(argsToString(arguments));
},
tprint: function(args) {
if (args === undefined || args == null) {
throw makeRuntimeErrorMsg("tprint", "Takes 1 argument.");
tprint: function() {
if (arguments.length === 0) {
throw makeRuntimeErrorMsg("tprint", "Takes at least 1 argument.");
}
var x = args.toString();
post(`${workerScript.scriptRef.filename}: ${args.toString()}`);
post(`${workerScript.scriptRef.filename}: ${argsToString(arguments)}`);
},
clearLog: function() {
workerScript.scriptRef.clearLog();
@@ -989,8 +1055,15 @@ function NetscriptFunctions(workerScript) {
return runningScriptObj.logs.slice();
},
tail: function(fn, ip, ...scriptArgs) {
const runningScriptObj = getRunningScript(fn, ip, "tail", scriptArgs);
tail: function(fn, ip=workerScript.serverIp, ...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) {
workerScript.log("tail", getCannotFindRunningScriptErrorMessage(fn, ip, scriptArgs));
return;
@@ -1494,7 +1567,12 @@ function NetscriptFunctions(workerScript) {
const processes = [];
for (const i in server.runningScripts) {
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;
},
@@ -1677,20 +1755,16 @@ function NetscriptFunctions(workerScript) {
}
return false;
},
isRunning: function(filename,ip) {
isRunning: function(fn, ip=workerScript.serverIp, ...scriptArgs) {
updateDynamicRam("isRunning", getRamCost("isRunning"));
if (filename === undefined || ip === undefined) {
if (fn === undefined || ip === undefined) {
throw makeRuntimeErrorMsg("isRunning", "Usage: isRunning(scriptname, server, [arg1], [arg2]...)");
}
var server = getServer(ip);
if (server == null) {
throw makeRuntimeErrorMsg("isRunning", `Invalid IP/hostname: ${ip}`);
if(typeof fn === 'number') {
return getRunningScriptByPid(fn, 'isRunning') != null;
} 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() {
updateDynamicRam("getStockSymbols", getRamCost("getStockSymbols"));
@@ -2022,7 +2096,7 @@ function NetscriptFunctions(workerScript) {
const cost = getPurchaseServerCost(ram);
if (cost === Infinity) {
workerScript.log("purchaseServer", `Invalid argument: ram='${ram}'`);
return Infinity;
return "";
}
if (Player.money.lt(cost)) {
@@ -2362,6 +2436,38 @@ function NetscriptFunctions(workerScript) {
}
return 0;
},
getRunningScript: function(fn, ip) {
updateDynamicRam("getRunningScript", getRamCost("getRunningScript"));
let runningScript;
if(arguments.length === 0) {
runningScript = workerScript.scriptRef;
} else if(typeof fn === 'number') {
runningScript = getRunningScriptByPid(fn, 'getRunningScript');
} else {
const scriptArgs = [];
for (var i = 2; i < arguments.length; ++i) {
scriptArgs.push(arguments[i]);
}
runningScript = getRunningScript(fn, ip, 'getRunningScript', scriptArgs);
}
if (runningScript === null) return null;
return {
args: runningScript.args.slice(),
filename: runningScript.filename,
logs: runningScript.logs.slice(),
offlineExpGained: runningScript.offlineExpGained,
offlineMoneyMade: runningScript.offlineMoneyMade,
offlineRunningTime: runningScript.offlineRunningTime,
onlineExpGained: runningScript.onlineExpGained,
onlineMoneyMade: runningScript.onlineMoneyMade,
onlineRunningTime: runningScript.onlineRunningTime,
pid: runningScript.pid,
ramUsage: runningScript.ramUsage,
server: runningScript.server,
threads: runningScript.threads,
};
},
getHackTime: function(ip, hack, int) {
updateDynamicRam("getHackTime", getRamCost("getHackTime"));
const server = safeGetServer(ip, "getHackTime");
@@ -2457,7 +2563,7 @@ function NetscriptFunctions(workerScript) {
return Player.playtimeSinceLastAug;
},
prompt : function(txt) {
if (!isString(txt)) {txt = String(txt);}
if (!isString(txt)) {txt = JSON.stringify(txt);}
// The id for this popup will consist of the first 20 characters of the prompt string..
// Thats hopefully good enough to be unique
@@ -4381,8 +4487,36 @@ function NetscriptFunctions(workerScript) {
},
exploit: function() {
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 NetscriptFunction()
export { NetscriptFunctions };
export { NetscriptFunctions };

View File

@@ -107,7 +107,7 @@ export function _getScriptUrls(script, scripts, seen) {
// import {foo} from "blob://<uuid>"
//
// 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) => {
const isAllowedImport = scripts.some(s => s.filename == filename);
if (!isAllowedImport) return unmodified;

View File

@@ -180,4 +180,5 @@ export interface IPlayer {
queryStatFromString(str: string): number;
getIntelligenceBonus(weight: number): 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 { calculateSkill as calculateSkillF } from "../formulas/skill";
import { calculateIntelligenceBonus } from "../formulas/intelligence";
import {
getHackingWorkRepGain,
getFactionSecurityWorkRepGain,
getFactionFieldWorkRepGain,
} from '../formulas/reputation';
import {
AllServers,
AddToAllServers,
@@ -429,6 +434,8 @@ export function gainHackingExp(exp) {
if(this.hacking_exp < 0) {
this.hacking_exp = 0;
}
this.hacking_skill = calculateSkillF(this.hacking_exp, this.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier);
}
export function gainStrengthExp(exp) {
@@ -439,6 +446,8 @@ export function gainStrengthExp(exp) {
if(this.strength_exp < 0) {
this.strength_exp = 0;
}
this.strength = calculateSkillF(this.strength_exp, this.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier);
}
export function gainDefenseExp(exp) {
@@ -449,6 +458,8 @@ export function gainDefenseExp(exp) {
if(this.defense_exp < 0) {
this.defense_exp = 0;
}
this.defense = calculateSkillF(this.defense_exp, this.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier);
}
export function gainDexterityExp(exp) {
@@ -459,6 +470,8 @@ export function gainDexterityExp(exp) {
if(this.dexterity_exp < 0) {
this.dexterity_exp = 0;
}
this.dexterity = calculateSkillF(this.dexterity_exp, this.dexterity_mult * BitNodeMultipliers.DexterityLevelMultiplier);
}
export function gainAgilityExp(exp) {
@@ -469,6 +482,8 @@ export function gainAgilityExp(exp) {
if(this.agility_exp < 0) {
this.agility_exp = 0;
}
this.agility = calculateSkillF(this.agility_exp, this.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier);
}
export function gainCharismaExp(exp) {
@@ -479,6 +494,8 @@ export function gainCharismaExp(exp) {
if(this.charisma_exp < 0) {
this.charisma_exp = 0;
}
this.charisma = calculateSkillF(this.charisma_exp, this.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier);
}
export function gainIntelligenceExp(exp) {
@@ -550,7 +567,11 @@ export function processWorkEarnings(numCycles=1) {
this.gainAgilityExp(agiExpGain);
this.gainCharismaExp(chaExpGain);
this.gainMoney(moneyGain);
this.recordMoneySource(moneyGain, "work");
if (this.className) {
this.recordMoneySource(moneyGain, "class");
} else {
this.recordMoneySource(moneyGain, "work");
}
this.workHackExpGained += hackExpGain;
this.workStrExpGained += strExpGain;
this.workDefExpGained += defExpGain;
@@ -592,6 +613,17 @@ export function startWork(companyName) {
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) {
// Cap the number of cycles being processed to whatever would put you at
// the work time limit (8 hours)
@@ -622,6 +654,10 @@ export function work(numCycles) {
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");
ReactDOM.render(<>
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.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,
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);
}
export function finishWork(cancelled, sing=false) {
//Since the work was cancelled early, player only gains half of what they've earned so far
if (cancelled) {
this.workRepGained /= 2;
this.workRepGained *= this.cancelationPenalty();
}
var company = Companies[this.companyName];
const company = Companies[this.companyName];
company.playerReputation += (this.workRepGained);
this.updateSkillLevels();
@@ -853,7 +889,7 @@ export function startFactionFieldWork(faction) {
this.workDexExpGainRate = .1 * this.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.workAgiExpGainRate = .1 * this.agility_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.currentWorkFactionDescription = "carrying out field missions"
@@ -870,7 +906,7 @@ export function startFactionSecurityWork(faction) {
this.workDexExpGainRate = 0.15 * this.dexterity_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.workRepGainRate = this.getFactionSecurityWorkRepGain();
this.workRepGainRate = getFactionSecurityWorkRepGain(this, faction);
this.factionWorkType = CONSTANTS.FactionWorkSecurity;
this.currentWorkFactionDescription = "performing security detail"
@@ -879,29 +915,23 @@ export function startFactionSecurityWork(faction) {
}
export function workForFaction(numCycles) {
var faction = Factions[this.currentWorkFactionName];
const faction = Factions[this.currentWorkFactionName];
//Constantly update the rep gain rate
switch (this.factionWorkType) {
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;
case CONSTANTS.FactionWorkField:
this.workRepGainRate = this.getFactionFieldWorkRepGain();
this.workRepGainRate = getFactionFieldWorkRepGain(this, faction);
break;
case CONSTANTS.FactionWorkSecurity:
this.workRepGainRate = this.getFactionSecurityWorkRepGain();
this.workRepGainRate = getFactionSecurityWorkRepGain(this, faction);
break;
default:
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)
var overMax = false;
if (this.timeWorked + (Engine._idleSpeed * numCycles) >= CONSTANTS.MillisecondsPer20Hours) {
@@ -1112,25 +1142,25 @@ export function getWorkRepGain() {
return jobPerformance * this.company_rep_mult * favorMult;
}
export function getFactionSecurityWorkRepGain() {
var t = 0.9 * (this.hacking_skill / CONSTANTS.MaxSkillLevel +
this.strength / CONSTANTS.MaxSkillLevel +
this.defense / CONSTANTS.MaxSkillLevel +
this.dexterity / CONSTANTS.MaxSkillLevel +
this.agility / CONSTANTS.MaxSkillLevel) / 4.5;
return t * this.faction_rep_mult;
}
// export function getFactionSecurityWorkRepGain() {
// var t = 0.9 * (this.hacking_skill / CONSTANTS.MaxSkillLevel +
// this.strength / CONSTANTS.MaxSkillLevel +
// this.defense / CONSTANTS.MaxSkillLevel +
// this.dexterity / CONSTANTS.MaxSkillLevel +
// this.agility / CONSTANTS.MaxSkillLevel) / 4.5;
// return t * this.faction_rep_mult;
// }
export function getFactionFieldWorkRepGain() {
var t = 0.9 * (this.hacking_skill / CONSTANTS.MaxSkillLevel +
this.strength / CONSTANTS.MaxSkillLevel +
this.defense / CONSTANTS.MaxSkillLevel +
this.dexterity / CONSTANTS.MaxSkillLevel +
this.agility / CONSTANTS.MaxSkillLevel +
this.charisma / CONSTANTS.MaxSkillLevel +
this.intelligence / CONSTANTS.MaxSkillLevel) / 5.5;
return t * this.faction_rep_mult;
}
// export function getFactionFieldWorkRepGain() {
// var t = 0.9 * (this.hacking_skill / CONSTANTS.MaxSkillLevel +
// this.strength / CONSTANTS.MaxSkillLevel +
// this.defense / CONSTANTS.MaxSkillLevel +
// this.dexterity / CONSTANTS.MaxSkillLevel +
// this.agility / CONSTANTS.MaxSkillLevel +
// this.charisma / CONSTANTS.MaxSkillLevel +
// this.intelligence / CONSTANTS.MaxSkillLevel) / 5.5;
// return t * this.faction_rep_mult;
// }
/* Creating a Program */
export function startCreateProgramWork(programName, time, reqLevel) {
@@ -1715,6 +1745,11 @@ export function getNextCompanyPosition(company, entryPosType) {
return entryPosType;
}
export function quitJob(company) {
this.companyName = "";
delete this.jobs[company];
}
export function applyForSoftwareJob(sing=false) {
return this.applyForJob(CompanyPositions[posNames.SoftwareCompanyPositions[0]], sing);
}
@@ -2036,7 +2071,7 @@ export function checkForFactionInvitations() {
} else {
if (!fulcrumsecrettechonologiesFac.isBanned && !fulcrumsecrettechonologiesFac.isMember &&
!fulcrumsecrettechonologiesFac.alreadyInvited &&
fulcrumSecretServer.manuallyHacked &&
fulcrumSecretServer.backdoorInstalled &&
checkMegacorpRequirements(LocationName.AevumFulcrumTechnologies, 250e3)) {
invitedFactions.push(fulcrumsecrettechonologiesFac);
}
@@ -2048,7 +2083,7 @@ export function checkForFactionInvitations() {
var bitrunnersServer = AllServers[SpecialServerIps[SpecialServerNames.BitRunnersServer]];
if (bitrunnersServer == null) {
console.error("Could not find BitRunners Server");
} else if (!bitrunnersFac.isBanned && !bitrunnersFac.isMember && bitrunnersServer.manuallyHacked &&
} else if (!bitrunnersFac.isBanned && !bitrunnersFac.isMember && bitrunnersServer.backdoorInstalled &&
!bitrunnersFac.alreadyInvited && homeComp.maxRam >= 128) {
invitedFactions.push(bitrunnersFac);
}
@@ -2058,7 +2093,7 @@ export function checkForFactionInvitations() {
var blackhandServer = AllServers[SpecialServerIps[SpecialServerNames.TheBlackHandServer]];
if (blackhandServer == null) {
console.error("Could not find The Black Hand Server");
} else if (!theblackhandFac.isBanned && !theblackhandFac.isMember && blackhandServer.manuallyHacked &&
} else if (!theblackhandFac.isBanned && !theblackhandFac.isMember && blackhandServer.backdoorInstalled &&
!theblackhandFac.alreadyInvited && homeComp.maxRam >= 64) {
invitedFactions.push(theblackhandFac);
}
@@ -2068,7 +2103,7 @@ export function checkForFactionInvitations() {
var nitesecServer = AllServers[SpecialServerIps[SpecialServerNames.NiteSecServer]];
if (nitesecServer == null) {
console.error("Could not find NiteSec Server");
} else if (!nitesecFac.isBanned && !nitesecFac.isMember && nitesecServer.manuallyHacked &&
} else if (!nitesecFac.isBanned && !nitesecFac.isMember && nitesecServer.backdoorInstalled &&
!nitesecFac.alreadyInvited && homeComp.maxRam >= 32) {
invitedFactions.push(nitesecFac);
}
@@ -2212,7 +2247,7 @@ export function checkForFactionInvitations() {
var cybersecServer = AllServers[SpecialServerIps[SpecialServerNames.CyberSecServer]];
if (cybersecServer == null) {
console.error("Could not find CyberSec Server");
} else if (!cybersecFac.isBanned && !cybersecFac.isMember && cybersecServer.manuallyHacked &&
} else if (!cybersecFac.isBanned && !cybersecFac.isMember && cybersecServer.backdoorInstalled &&
!cybersecFac.alreadyInvited) {
invitedFactions.push(cybersecFac);
}

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 { AceEditor } from "../ScriptEditor/Ace";
import { CodeMirrorEditor } from "../ScriptEditor/CodeMirror";
import { CursorPositions } from "../ScriptEditor/CursorPositions";
import { AllServers } from "../Server/AllServers";
import { processSingleServerGrowth } from "../Server/ServerHelpers";
import { Settings } from "../Settings/Settings";
@@ -224,11 +225,13 @@ $(document).keydown(function(e) {
function saveAndCloseScriptEditor() {
var filename = document.getElementById("script-editor-filename").value;
let code;
let code, cursor;
try {
code = getCurrentEditor().getCode();
cursor = getCurrentEditor().getCursor();
CursorPositions.saveCursor(filename, cursor);
} 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;
}
@@ -268,7 +271,7 @@ function saveAndCloseScriptEditor() {
}
if (filename !== ".fconf" && !isValidFilePath(filename)) {
dialogBoxCreate("Script filename can contain only alphanumerics, hyphens, and underscores");
dialogBoxCreate("Script filename can contain only alphanumerics, hyphens, and underscores, and must end with an extension.");
return;
}
@@ -282,7 +285,7 @@ function saveAndCloseScriptEditor() {
}
} else if (isScriptFilename(filename)) {
//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) {
s.scripts[i].saveScript(getCurrentEditor().getCode(), Player.currentServer, Player.getCurrentServer().scripts);
Engine.loadTerminalContent();
@@ -295,14 +298,14 @@ function saveAndCloseScriptEditor() {
script.saveScript(getCurrentEditor().getCode(), Player.currentServer, Player.getCurrentServer().scripts);
s.scripts.push(script);
} 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) {
s.textFiles[i].write(code);
Engine.loadTerminalContent();
return;
}
}
var textFile = new TextFile(filename, code);
const textFile = new TextFile(filename, code);
s.textFiles.push(textFile);
} else {
dialogBoxCreate("Invalid filename. Must be either a script (.script) or " +
@@ -411,3 +414,14 @@ export function findRunningScript(filename, args, server) {
}
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";
}
}
getCursor() {
return this.editor.getCursorPosition();
}
setCursor(pos) {
this.editor.gotoLine(pos.row+1, pos.column);
}
}
export const AceEditor = new AceEditorWrapper();

View File

@@ -570,6 +570,15 @@ class CodeMirrorEditorWrapper extends ScriptEditor {
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();

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
const beautify = require('js-beautify').js_beautify;
@@ -33,6 +35,7 @@ export class ScriptEditor {
if (filename != "") {
this.filenameInput.value = filename;
this.editor.setValue(code);
this.setCursor(CursorPositions.getCursor(filename));
}
this.editor.focus();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -19,7 +19,7 @@ export class CharacterOverviewComponent extends Component {
<table>
<tbody>
<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 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>

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