diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..eba264699 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +insert_final_newline = true + +[package.json] +indent_size = 2 + +[md] +trim_trailing_whitespace = false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 508adaa39..e965b1e63 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,29 +17,29 @@ heard: scripting perhaps there is something that is conflicting with the browser's Javascript interaction. So please do not be afraid to open a [new issue](https://github.com/danielyxie/bitburner/issues/new). - -## Reporting Bugs -The recommended method for reporting a bug is by opening a -[Github Issue](https://github.com/danielyxie/bitburner/issues). -Before submitting a bug report, please check to make sure the bug has not -already been reported as an [Issue](https://github.com/danielyxie/bitburner/issues). +## Reporting Bugs +The recommended method for reporting a bug is by opening a +[Github Issue](https://github.com/danielyxie/bitburner/issues). + +Before submitting a bug report, please check to make sure the bug has not +already been reported as an [Issue](https://github.com/danielyxie/bitburner/issues). #### How to Submit a Good Bug Report * **Use a clear and descriptive title** for the issue * **State your browser, your browser's version, and your computer's OS** - * **Provide instructions on how to reproduce the bug** in as much detail + * **Provide instructions on how to reproduce the bug** in as much detail as possible. If you cannot reliably reproduce the bug, then just try your best to explain what was happening when the bug occurred * **Provide any scripts** that triggered the bug if the issue is Netscript-related - * **Open your browser's Dev Console and report any error-related output** - that may be printed there. The Dev Console can be opened on most modern + * **Open your browser's Dev Console and report any error-related output** + that may be printed there. The Dev Console can be opened on most modern browsers by pressing F12 ## As a Developer -Anyone is welcome to contribute to Bitburner code. However, please read -the [license](https://github.com/danielyxie/bitburner/blob/dev/license.txt) +Anyone is welcome to contribute to Bitburner code. However, please read +the [license](https://github.com/danielyxie/bitburner/blob/dev/license.txt) and the [readme](https://github.com/danielyxie/bitburner/blob/dev/README.md) before doing so. @@ -48,22 +48,22 @@ To contribute to Bitburner code, you will need to have called `npm` is installed as well. #### What are you Allowed to Contribute? -Not all code contributions will be accepted. The safest way to ensure -that you don't waste time working on something that gets rejected is to -run your idea(s)/plan(s) past [danielyxie](https://github.com/danielyxie) first. +Not all code contributions will be accepted. The safest way to ensure +that you don't waste time working on something that gets rejected is to +run your idea(s)/plan(s) past [danielyxie](https://github.com/danielyxie) first. You can contact him through: * Github * Discord * [Reddit](https://www.reddit.com/user/chapt3r/) -Otherwise, here are some general guidelines for determining what types of changes -are okay to contribute: +Otherwise, here are some general guidelines for determining what types of +changes are okay to contribute: ##### Contributions that Will Most Likely Be Accepted * Bug Fixes * Quality-of-Life Changes - * Adding a new, commonly-requested Netscript function + * Adding a new, commonly-requested Netscript function * Fixing or improving UI elements * Adding game settings/options * Adding a new Terminal command @@ -73,8 +73,8 @@ are okay to contribute: * Changes that directly affect the game's balance * New gameplay mechanics -#### Submitting a Pull Request -When submitting a pull request with your code contributions, please abide by +#### Submitting a Pull Request +When submitting a pull request with your code contributions, please abide by the following rules: - Work in a branch forked from `dev` to isolate the new code @@ -86,14 +86,18 @@ the following rules: _danielyxie/bitburner_ and the base is _dev_. - If your changes affect the game's UI, attach some screenshots or GIFs showing the changes to the UI - - If your changes affect Netscript, provide some - scripts that can be used to test the Netscript changes. - - Do not check in any bundled files (`dist\*.bundle.js`). These will be - updated as part of official releases. + - If your changes affect Netscript, provide some + scripts that can be used to test the Netscript changes. + - Ensure you have run `npm run lint` to make sure your changes conform to the + rules enforced across the code base. The command will fail if any of the + linters find a violation. + - Do not check in any bundled files (`dist\*.bundle.js`) or the `index.html` + in the root of the repository. These will be updated as part of official + releases. ## As a Documentor -To contribute to BitBurner documentation, you will need to have Python -installed, along with [Sphinx](http://www.sphinx-doc.org). +To contribute to and view your changes to the BitBurner documentation, you will +need to have Python installed, along with [Sphinx](http://www.sphinx-doc.org). Before submitting your code for a pull request, please try to follow these rules: @@ -103,3 +107,5 @@ rules: - Rebase your branch if necessary - When submitting the pull request, make sure that the base fork is _danielyxie/bitburner_ and the base is _dev_. + - Do not check in any generated files under `doc\`. The documentation is built + automatically by ReadTheDocs. diff --git a/css/_theme.scss b/css/_theme.scss index e9ffd2225..0b727d702 100644 --- a/css/_theme.scss +++ b/css/_theme.scss @@ -1,2 +1,17 @@ $fontFamily: 'Lucida Console', 'Lucida Sans Unicode', 'Fira Mono', 'Consolas', 'Courier New', Courier, monospace, 'Times New Roman'; $defaultFontSize: 16px; + +/* COLORS */ +$hacker-green: #adff2f; +$success-green: #3adb76; +$alert-red: #ff2929; +$money-gold: #ffd700; +$light-yellow: #faffdf; + +/* Attributes */ +$my-stat-hp-color: #dd3434; +$my-stat-money-color: $money-gold; +$my-stat-hack-color: $hacker-green; +$my-stat-physical: $light-yellow; +$my-stat-cha-color: #a671d1; +$my-stat-int-color: #6495ed; diff --git a/css/interactivetutorial.scss b/css/interactivetutorial.scss index 15b8f3cc9..143260d6d 100644 --- a/css/interactivetutorial.scss +++ b/css/interactivetutorial.scss @@ -11,11 +11,11 @@ position: absolute; /* Stay in place */ right: 0; top: 0; - height: 400px; /* Full height */ + height: 450px; padding: 10px; border: 5px solid #fff; - width: 20%; - overflow: auto; /* Enable scroll if needed */ + width: 23%; + overflow: hidden; background-color: #444; /* Fallback color */ color: #fff; @@ -29,6 +29,9 @@ margin: 4px; color: #fff; background-color: #444; + font-size: $defaultFontSize * 0.875; + max-height: 350px; + overflow-y: auto; } #interactive-tutorial-exit, @@ -38,7 +41,7 @@ @include boxShadow(1px 1px 3px #000); color: #aaa; - font-size: $defaultFontSize * 1.25; + font-size: $defaultFontSize * 1.125; font-weight: bold; background-color: #000; @@ -50,6 +53,7 @@ } } +/* #interactive-tutorial-exit { float: left; } @@ -58,6 +62,16 @@ margin-right: 20%; float: right; } +*/ +#interactive-tutorial-exit { + position: absolute; + bottom: 0; + left: 0; +} + +#interactive-tutorial-back { + float: left; +} #interactive-tutorial-next { float: right; diff --git a/css/menupages.scss b/css/menupages.scss index 8c9e475a1..e949b0fce 100644 --- a/css/menupages.scss +++ b/css/menupages.scss @@ -22,17 +22,15 @@ #script-editor-container { background-color: transparent; } + #javascript-editor { margin: 10px; - height: 80%; width: 100%; margin-left: 6px; - padding-left: 6px; padding-top: 6px; padding-bottom: 6px; - border: 2px solid var(--my-highlight-color); z-index: 1; font-family: $fontFamily; @@ -101,9 +99,7 @@ resize: none; color: #fff; margin: 4px; - padding: 2px; - border: 2px solid var(--my-highlight-color); } @@ -204,38 +200,37 @@ .active-scripts-script-header { background-color: #555; color: var(--my-font-color); - padding: 4px; - padding-left: 10px; + padding: 4px 25px 4px 10px; cursor: pointer; width: auto; text-align: left; border: none; outline: none; -} + position: relative; -.active-scripts-script-header:hover, -.active-scripts-script-header.active:hover { - background-color: #666; -} + &:after { + content: '\02795'; /* "plus" sign (+) */ + font-size: $defaultFontSize * 0.8125; + float: right; + margin-left: 5px; + color: transparent; + text-shadow: 0 0 0 var(--my-font-color); + position: absolute; + bottom: 4px; + } -.active-scripts-script-header.active { - background-color: #555; -} + &.active:after { + content: "\2796"; /* "minus" sign (-) */ + } -.active-scripts-script-header:after { - content: '\02795'; /* "plus" sign (+) */ - font-size: $defaultFontSize * 0.8125; - color: var(--my-font-color); - float: right; - margin-left: 5px; -} + &:hover, + &.active:hover { + background-color: #666; + } -.active-scripts-script-header.active:after { - content: "\2796"; /* "minus" sign (-) */ - font-size: $defaultFontSize * 0.8125; - color: var(--my-font-color); - float: right; - margin-left: 5px; + &.active { + background-color: #555; + } } .active-scripts-script-panel { @@ -244,16 +239,13 @@ width: auto; display: none; margin-bottom: 6px; -} -.active-scripts-script-panel p, -.active-scripts-script-panel h2, -.active-scripts-script-panel ul, -.active-scripts-script-panel li { - background-color: #555; - width: auto; - color: #fff; - margin-left: 5%; + p, h2, ul, li { + background-color: #555; + width: auto; + color: #fff; + margin-left: 5%; + } } .active-scripts-button { @@ -266,13 +258,13 @@ margin: 4px; padding: 4px; background-color: #000; -} -.active-scripts-button:hover, -.active-scripts-button:focus { - color: #fff; - text-decoration: none; - cursor: pointer; + &:hover, + &:focus { + color: #fff; + text-decoration: none; + cursor: pointer; + } } /* Hacknet Nodes */ @@ -291,6 +283,16 @@ float: left; overflow: hidden; white-space: nowrap; + + &.hacknet-node { + $boxShadowArgs: inset 0 0 8px rgba(0, 0, 0, 0.1), 0 0 16px rgba(0, 0, 0, 0.1); + @include boxShadow($boxShadowArgs); + + margin: 6px; + padding: 7px; + width: 35vw; + border: 2px solid var(--my-highlight-color); + } } #hacknet-nodes-list { @@ -316,34 +318,24 @@ display: inline-block; } -.hacknet-node { - $boxShadowArgs: inset 0 0 8px rgba(0, 0, 0, 0.1), 0 0 16px rgba(0, 0, 0, 0.1); - @include boxShadow($boxShadowArgs); - - margin: 6px; - padding: 6px; - width: 34vw; - border: 2px solid var(--my-highlight-color); -} - .hacknet-node-container { display: inline-table; -} -.hacknet-node-container .row { - display: table-row; - height: 30px; -} + .row { + display: table-row; + height: 30px; -.hacknet-node-container .row p { - display: table-cell; -} + p { + display: table-cell; + } + } -.hacknet-node-container .upgradable-info { - display: inline-block; - margin: 0 4px; /* Don't want the vertical margin/padding, just left & right */ - padding: 0 4px; - width: $defaultFontSize * 4; + .upgradable-info { + display: inline-block; + margin: 0 4px; /* Don't want the vertical margin/padding, just left & right */ + padding: 0 4px; + width: $defaultFontSize * 4; + } } .menu-page-text { @@ -412,8 +404,8 @@ #faction-container p, #faction-container pre { - padding: 6px; - margin: 6px; + padding: 4px 6px; + margin: 4px 6px; } #faction-container pre { @@ -426,17 +418,14 @@ } /* Faction Augmentations */ -#faction-augmentations-container{ +#faction-augmentations-container { position: fixed; padding-top: 10px; -} -#faction-augmentations-container p, -#faction-augmentations-container a, -#faction-augmentations-container ul, -#faction-augmentations-container h1{ - margin: 8px; - padding: 4px; + p, a, ul, h1 { + margin: 8px; + padding: 4px; + } } /* World */ @@ -451,11 +440,20 @@ padding-top: 10px; } -.augmentations-list button, -.augmentations-list div { - color: var(--my-font-color); - padding: 8px; - text-decoration: none; +.augmentations-list { + button, + div { + color: var(--my-font-color); + text-decoration: none; + } + + button { + padding: 2px 5px; + } + + div { + padding: 6px; + } } /* Tutorial */ @@ -510,11 +508,12 @@ padding: 6px; } -#location-container * { +#location-container > * { margin: 10px 5px 10px 5px; } -#location-job-reputation, #location-company-favor { +#location-job-reputation, +#location-company-favor { display: inline; } @@ -522,7 +521,13 @@ #infiltration-container { position: fixed; padding: 6px; + + span { + margin: 0; + padding: 0; + } } + #infiltration-left-panel, #infiltration-right-panel { display: inline-block; @@ -543,6 +548,10 @@ margin: 4px; } +#infiltration-buttons { + margin-top: 20px; +} + #infiltration-buttons .a-link-button { display: inline; width: 25%; @@ -552,6 +561,7 @@ #stock-market-container { position: fixed; padding: 6px; + p { font-size: $defaultFontSize * 0.8125; } @@ -559,9 +569,9 @@ font-size: $defaultFontSize * 0.875; } h2 { - margin-top:10px; - margin-left:10px; - display:block; + margin-top: 10px; + margin-left: 10px; + display: block; } } @@ -583,8 +593,8 @@ } #stock-market-watchlist-filter { - width:50%; - margin-left:10px; + width: 50%; + margin-left: 10px; } .stock-market-input { diff --git a/css/popupboxes.scss b/css/popupboxes.scss index 60c18d8ab..d6536f2c5 100644 --- a/css/popupboxes.scss +++ b/css/popupboxes.scss @@ -8,8 +8,8 @@ z-index: 10; /* Sit on top */ left: 0; top: 0; - align-items:center; - justify-content:center; + align-items: center; + justify-content: center; width: 100%; height: 100%; overflow: auto; @@ -21,7 +21,7 @@ padding: 12px; border: 5px solid var(--my-highlight-color); width: 70%; - max-height:80%; + max-height: 80%; /* margin: auto; height:auto; @@ -32,7 +32,7 @@ bottom:0; right:0; */ - overflow-y:auto; + overflow-y: auto; color: var(--my-font-color); } @@ -87,6 +87,11 @@ z-index: 2; background-color: var(--my-background-color); padding: 10px; + + p span { + padding: 0; + margin: 0; + } } .dialog-box-close-button { @@ -138,6 +143,11 @@ margin: 8px; } +#infiltration-box-content span { + padding: 0; + margin: 0; +} + #infiltration-faction-select { background-color: #000; } @@ -158,8 +168,8 @@ border: 5px solid var(--my-highlight-color); color: var(--my-font-color); width: 80%; - max-height:80%; - overflow-y:auto; + max-height: 80%; + overflow-y: auto; } #game-options-left-panel, diff --git a/css/styles.scss b/css/styles.scss index feebc7177..c0ee086e3 100644 --- a/css/styles.scss +++ b/css/styles.scss @@ -3,10 +3,11 @@ @import "reset"; -:root{ +:root { --my-font-color: #6f3; --my-background-color: #000; --my-highlight-color: #fff; + --my-prompt-color: #f92672; } body { @@ -83,7 +84,7 @@ tr:focus { display: block; color: #e6e6e6; background-color: #555; - padding: 16px; + padding: 12px 8px; text-decoration: none; } @@ -105,21 +106,36 @@ tr:focus { background-color: #aaa; } +#hacking-menu-header-li, +#character-menu-header-li, +#world-menu-header-li, +#help-menu-header-li { + position: relative; +} + +/* Accordion Outline */ +.mainmenu-accordion-header { + outline: 2px solid #fff; +} + /* Plus and minus signs */ .mainmenu-accordion-header:after { content: '\02795'; - font-size: $defaultFontSize * 0.8125; - color: #fff; float: right; - margin-left: 5px; -} - -.mainmenu-accordion-header.opened:after { - content: "\2796"; + font-size: $defaultFontSize * 0.8125; + position: absolute; + bottom: 25%; + right: 3px; + color: transparent; + text-shadow: 0 0 0 #fff; } .mainmenu-accordion-header.opened { background-color: #222; + + &:after { + content: "\2796"; + } } /* Slide down transition */ @@ -129,11 +145,6 @@ tr:focus { transition: max-height 0.2s ease-out; } -/* Borders */ -.mainmenu-accordion-header { - border: 2px solid #fff; -} - /* Make html links ("a" elements) nice looking buttons with this class */ a:link, a:visited { @@ -144,7 +155,7 @@ a:visited { text-decoration: none; background-color: #555; color: #fff; - padding: 5px; + padding: 3px 5px; margin: 5px; border: 1px solid #333; @@ -152,14 +163,14 @@ a:visited { -ms-user-select: none; -khtml-user-select: none; -webkit-user-select: none; -} -.a-link-button:hover { - background-color: #666; -} + &:hover { + background-color: #666; + } -.a-link-button:active { - @include boxShadow(inset 0 1px 4px rgba(0, 0, 0, 0.6)); + &:active { + @include boxShadow(inset 0 1px 4px rgba(0, 0, 0, 0.6)); + } } /* Make anchor tags ("a" elements) inactive (not clickable) */ @@ -167,20 +178,22 @@ a:visited { text-decoration: none; background-color: #333; color: #fff; - padding: 5px; + padding: 3px 5px; margin: 5px; border: 1px solid #333; cursor: default; -} -.a-link-button-inactive:hover .tooltiptext, -.a-link-button-inactive:hover .tooltiptexthigh, -.a-link-button-inactive:hover .tooltiptextleft { - visibility: visible; -} + &:hover { + .tooltiptext, + .tooltiptexthigh, + .tooltiptextleft { + visibility: visible; + } + } -.a-link-button-inactive:active { - pointer-events: none; + &:active { + pointer-events: none; + } } /* Make anchor tags ("a" elements) for activated actions */ @@ -188,20 +201,22 @@ a:visited { text-decoration: none; background-color: #0a0; color: #fff; - padding: 5px; + padding: 3px 5px; margin: 5px; border: 1px solid #0a0; cursor: default; -} -.a-link-button-bought:hover .tooltiptext, -.a-link-button-bought:hover .tooltiptexthigh, -.a-link-button-bought:hover .tooltiptextleft { - visibility: visible; -} + &:hover { + .tooltiptext, + .tooltiptexthigh, + .tooltiptextleft { + visibility: visible; + } + } -.a-link-button-bought:active { - pointer-events: none; + &:active { + pointer-events: none; + } } .dropdown { @@ -218,9 +233,9 @@ a:visited { #create-program-tab { position: relative; } + #create-program-notification { font-size: $defaultFontSize * 0.625; - position: absolute; /* Position the badge within the relatively positioned button */ top: 0; right: 0; @@ -242,22 +257,23 @@ a:visited { /* Tool tips (when hovering over an element */ .tooltip { - position: relative; display: inline-block; -} + position: relative; -.tooltip .tooltiptext { - visibility: hidden; - width: 300px; - background-color: var(--my-background-color); - border: 2px solid var(--my-highlight-color); - color: #fff; - text-align: center; - padding: 4px; - left: 101%; + .tooltiptext { + visibility: hidden; + width: 300px; + background-color: var(--my-background-color); + border: 2px solid var(--my-highlight-color); + color: #fff; + text-align: center; + padding: 4px; + left: 101%; - position: absolute; - z-index: 99; + pointer-events: none; + position: absolute; + z-index: 99; + } } /* Same thing as a normal tooltip except its a bit higher */ @@ -276,7 +292,7 @@ a:visited { z-index: 99; } -/* Similar to a normal tooltip except its positioned on the left of the elemnt +/* Similar to a normal tooltip except its positioned on the left of the element rather than the right to avoid exceeding the elements normal width */ .tooltip .tooltiptextleft { visibility: hidden; @@ -364,7 +380,7 @@ a:visited { /* Blinking Cursor */ /* ----- blinking cursor animation ----- */ -.typed-cursor{ +.typed-cursor { opacity: 1; -webkit-animation: blink 0.95s infinite; -moz-animation: blink 0.95s infinite; @@ -400,16 +416,16 @@ a:visited { } /* Status text */ -@-webkit-keyframes status-text{ - from{ +@-webkit-keyframes status-text { + from { opacity: 1; } - to{ + to { opacity: 0; } } -.status-text{ +.status-text { display: inline-block; height: 15%; position: fixed; @@ -437,47 +453,78 @@ a:visited { #character-overview-wrapper { position: relative; } + #character-overview-container { display: none; position: absolute; /* Stay in place */ right: 0; top: 0; height: auto; /* Full height */ - padding: 8px; + padding: 10px 2px; border: 2px solid var(--my-highlight-color); - width: 19%; + width: auto; + max-width: 280px; overflow: auto; /* Enable scroll if needed */ - background-color: #444; /* Fallback color */ + background-color: rgba(57, 54, 54, 0.9); /* Fallback color */ z-index: 1; } #character-overview-text { - color: #fff; - background-color: #444; + color: $my-stat-physical; + + table { + border-collapse: collapse; + margin: auto; + } + + td { + padding: 2px; + vertical-align: middle; + } } .character-stat-text { color: #fff; background-color: #444; } - .character-stat-cell { + +.character-stat-cell { text-align: right; } +#character-hack-wrapper td, +#character-agi-wrapper td { + border-bottom: 1px #aaa solid; + padding-bottom: 10px; +} + +#character-str-wrapper td, +#character-cha-wrapper td { + padding-top: 10px; +} + +#character-hp-wrapper { color: $my-stat-hp-color; } +#character-money-wrapper { color: $my-stat-money-color; } +#character-hack-wrapper { color: $my-stat-hack-color; } +#character-cha-wrapper { color: $my-stat-cha-color; } +#character-int-wrapper { color: $my-stat-int-color; } + #character-overview-save-button, #character-overview-options-button { @include borderRadius(12px); @include boxShadow(1px 1px 3px #000); - - color: #aaa; + color: #cecece; + display: inline-block; font-size: $defaultFontSize * 0.875; font-weight: bold; - height: 22px; + height: 25px; background-color: #000; + padding: 5px 8px; } .character-quick-options { - padding-top: 5px; + margin-top: 10px; + text-align: center; } #character-overview-save-button:hover, @@ -489,18 +536,15 @@ a:visited { cursor: pointer; } -#character-overview-options-button { - display: inline; -} - /* Scan analyze links from AutoLink */ .scan-analyze-link { cursor: pointer; color: #fff; text-decoration: underline; -} -.scan-analyze-link:hover { - text-decoration: none; + + &:hover { + text-decoration: none; + } } /* Accordion menus (Header with collapsible panel) */ @@ -509,37 +553,37 @@ a:visited { font-size: $defaultFontSize * 1.25; color: #fff; margin: 6px 6px 0 6px; - padding: 6px; + padding: 4px 6px; cursor: pointer; width: 80%; text-align: left; border: none; outline: none; -} + position: relative; -.accordion-header.active, -.accordion-header:hover { - background-color: #555; -} + &.active, + &:hover { + background-color: #555; + } -.accordion-header.active:hover { - background-color: #666; -} + &.active:hover { + background-color: #666; + } -.accordion-header:after { - content: '\02795'; /* "plus" sign (+) */ - font-size: $defaultFontSize * 0.8125; - color: #fff; - float: right; - margin-left: 5px; -} + &:after { + content: '\02795'; /* "plus" sign (+) */ + font-size: $defaultFontSize * 0.8125; + float: right; + color: transparent; + text-shadow: 0 0 0 #fff; + position: absolute; + bottom: 5px; + right: 6px; + } -.accordion-header.active:after { - content: "\2796"; /* "minus" sign (-) */ - font-size: $defaultFontSize * 0.8125; - color: #fff; - float: right; - margin-left: 5px; + &.active:after { + content: "\2796"; /* "minus" sign (-) */ + } } .accordion-panel { @@ -551,13 +595,10 @@ a:visited { background-color: #555; overflow-y: auto; overflow-x: none; -} -.accordion-panel div, -.accordion-panel ul, -.accordion-panel p, -.accordion-panel ul > li { - background-color: #555; + div, ul, p, ul > li { + background-color: #555; + } } /* override the global styling */ @@ -567,3 +608,34 @@ a:visited { margin: 0; padding: 0; } + +/* Helper Classes */ +.hacker-green { + color: $hacker-green; +} + +.money-gold { + color: $money-gold; +} + +.light-yellow { + color: $light-yellow; +} + +.failure { + color: $alert-red; + text-shadow: 0 0 0 $alert-red; +} + +.success { + color: $success-green; + text-shadow: 0 0 0 $success-green; +} + +.physical-yellow { + color: $my-stat-physical; +} + +.charisma-purple { + color: $my-stat-cha-color; +} diff --git a/css/terminal.scss b/css/terminal.scss index 7e7ea52bc..1bd63a829 100644 --- a/css/terminal.scss +++ b/css/terminal.scss @@ -19,6 +19,12 @@ overflow-y: scroll; background-color: var(--my-background-color); table-layout: fixed; + + .prompt { + color: var(--my-prompt-color); + margin: 0; + padding: 0; + } } #terminal-input { diff --git a/doc/source/netscript.rst b/doc/source/netscript.rst index edc3d6c4c..13272cd50 100644 --- a/doc/source/netscript.rst +++ b/doc/source/netscript.rst @@ -3,9 +3,10 @@ Netscript Documentation Netscript is the programming language used in the world of Bitburner. When you write scripts in Bitburner, they are written in the Netscript language. -Netscript is simply a tiny subset of Javascript. This means that Netscript's -syntax is almost idental to Javascript's, but it does not implement many of the -features that Javascript has. +Netscript is simply a subset of `JavaScript `_,. +This means that Netscript's syntax is +identical to that of JavaScript, but it does not implement some of the features +that JavaScript has. If you have any requests or suggestions to improve the Netscript language, feel free to reach out to the developer! diff --git a/doc/source/netscript1.rst b/doc/source/netscript1.rst index d6adf3b39..59e9a70a6 100644 --- a/doc/source/netscript1.rst +++ b/doc/source/netscript1.rst @@ -2,14 +2,18 @@ Netscript 1.0 ============= -Netscript 1.0 is implemented using modified version of Neil Fraser's +Netscript 1.0 is implemented using a modified version of Neil Fraser's `JS-Interpreter `_. -This interpreter was created for ES5, which means that the code written -for Netscript 1.0 must be compliant for that version. However, some additional -ES6+ features are implemented through polyfills. +This is an ES5 JavaScript interpreter. This means that (almost) any JavaScript feature +that is available in ES5 is also available in Netscript 1.0. However, this also means +that the interpreter does not natively support any JavaScript features introduced in versions +ES6 or after. -Netscript 1.0 scripts end with the ".script" extension. +If you are confused by the ES5/ES6/etc. terminology, consider reading this: +`WTF is ES6, ES8, ES2017, ECMAScript... `_ + +Netscript 1.0 scripts end with the ".script" extension in their filenames. Which ES6+ features are supported? ---------------------------------- diff --git a/doc/source/netscriptfunctions.rst b/doc/source/netscriptfunctions.rst index 514a3d484..2e558944f 100644 --- a/doc/source/netscriptfunctions.rst +++ b/doc/source/netscriptfunctions.rst @@ -659,14 +659,14 @@ getNextHacknetNodeCost .. js:function:: getNextHacknetNodeCost() - Deprecated (no longer usable). See :doc:`netscripthacknetnodeapi` + Deprecated (no longer usable). See :doc:`netscripthacknetnodeapi` purchaseHacknetNode ^^^^^^^^^^^^^^^^^^^ .. js:function:: purchaseHacknetNode() - Deprecated (no longer usable). See :doc:`netscripthacknetnodeapi` + Deprecated (no longer usable). See :doc:`netscripthacknetnodeapi` getPurchasedServerCost ^^^^^^^^^^^^^^^^^^^^^^ @@ -772,6 +772,18 @@ write is set to "w", then the data is written in "write" mode which means that it will overwrite all existing data on the text file. If *mode* is set to any other value then the data will be written in "append" mode which means that the data will be added at the end of the text file. +tryWrite +^^^^^^^^ + +.. js:function:: tryWrite(port, data="") + + :param number port: Port to be written to + :param string data: Data to try to write + :returns: True if the data is successfully written to the port, and false otherwise + + Attempts to write data to the specified Netscript Port. If the port is full, the data will + not be written. Otherwise, the data will be written normally + read ^^^^ @@ -813,6 +825,17 @@ clear If the *port/fn* argument is a string, then it specifies the name of a text file (.txt) and will delete all data from that text file. +getPortHandle +^^^^^^^^^^^^^ + +.. js:function:: getPortHandle(port) + + :param number port: Port number + + Get a handle to a Netscript Port. See more details here: :ref:`netscript_ports` + + **WARNING:** Port Handles only work in :ref:`netscriptjs`. They will not work in :ref:`netscript1`. + rm ^^ diff --git a/doc/source/netscriptmisc.rst b/doc/source/netscriptmisc.rst index 5686f817a..ac7cb9610 100644 --- a/doc/source/netscriptmisc.rst +++ b/doc/source/netscriptmisc.rst @@ -1,9 +1,13 @@ +.. _netscript_misc: + Netscript Miscellaneous ======================= +.. _netscript_ports: + Netscript Ports --------------- -Netscript ports are endpoints that can be used to communicate between scripts. +Netscript Ports are endpoints that can be used to communicate between scripts. A port is implemented as a sort of serialized queue, where you can only write and read one element at a time from the port. When you read data from a port, the element that is read is removed from the port. @@ -55,9 +59,11 @@ And the data in port 1 will look like:: **Port Handles** +WARNING: Port Handles only work in :ref:`netscriptjs`. They do not work in :ref:`netscript1` + The :js:func:`getPortHandle` Netscript function can be used to get a handle to a Netscript Port. This handle allows you to access several new port-related functions and the -port's underlying data structure, which is just a Javascript array. The functions are: +port's underlying data structure, which is just a JavaScript array. The functions are: .. js:method:: NetscriptPort.write(data) diff --git a/doc/source/terminal.rst b/doc/source/terminal.rst index dcdd0a9c7..257f7542f 100644 --- a/doc/source/terminal.rst +++ b/doc/source/terminal.rst @@ -412,3 +412,18 @@ Then it could be removed using:: $ unalias "r" It is not necessary to differentiate between global and non-global aliases when using 'unalias' + +wget +^^^^ + + $ wget [url] [target file] + +Retrieves data from a url and downloads it to a file on the current server. +The data can only be downloaded to a script (.script, .ns, .js) or a text file +(.txt). If the target file already exists, it will be overwritten by this command. + +Note that will not be possible to download data from many websites because they +do not allow cross-origin origin sharing (CORS). This includes websites such +as gist and pastebin. One notable site it will work on is rawgithub. Example:: + + $ wget https://raw.githubusercontent.com/danielyxie/bitburner/master/README.md game_readme.txt diff --git a/index.html b/index.html index 19f370c7f..300047c63 100644 --- a/index.html +++ b/index.html @@ -2,19 +2,17 @@ - Bitburner + Bitburner - development - - + - + @@ -202,20 +201,20 @@ The Hacknet is a global, decentralized network of machines. It is used by hackers all around the world to anonymously share computing power and perform distributed cyberattacks without the fear of being traced. -

+

Here, you can purchase a Hacknet Node, a specialized machine that can connect and contribute its resources to the Hacknet network. This allows you to take a small percentage of profits from hacks performed on the network. Essentially, you are renting out your Node's computing power. -

+

Each Hacknet Node you purchase will passively earn you money. Each Hacknet Node can be upgraded in order to increase its computing power and thereby increase the profit you earn from it.

Purchase Hacknet Node -
+

- Money:
- Total Hacknet Node Prodution: + Money:
+ Total Hacknet Node Production:

x1 @@ -452,7 +451,7 @@

This page displays any programs that you are able to create. Writing the code for a program takes time, which - can vary based on how complex the program is. If you are working on creating on a program you can cancel + can vary based on how complex the program is. If you are working on creating a program you can cancel at any time. Your progress will be saved and you can continue later.

@@ -625,9 +624,9 @@

You have entered the Slums, a poverty-ridden district filled with gangs, criminals, and - other shadowy entities. The city's government and police have neglected this area for years...


- - In the Slums you can commit crimes to earn money and experience. Crime attempts are not always + other shadowy entities. The city's government and police have neglected this area for years... +


+ In the Slums, you can commit crimes to earn money and experience. Crime attempts are not always successful. Your chance at successfully committing a crime is determined by your stats.

Shoplift @@ -673,7 +672,7 @@

- Welcome to the World Stock Exchange (WSE)!

+ Welcome to the World Stock Exchange (WSE)!

To begin trading, you must first purchase an account. WSE accounts will persist after you 'reset' by installing Augmentations. @@ -686,7 +685,7 @@ TIX, short for Trade Information eXchange, is the communications protocol supported by the WSE. Purchasing access to the TIX API lets you write code to create your own algorithmic/automated trading strategies. -

+

If you purchase access to the TIX API, you will retain that access even after you 'reset' by installing Augmentations.

@@ -696,7 +695,7 @@

Four Sigma's (4S) Market Data Feed provides information about stocks that will help your trading strategies. -

+

If you purchase access to 4S Market Data and/or the 4S TIX API, you will retain that access even after you 'reset' by installing Augmentations.

@@ -714,8 +713,8 @@ Expand tickers Collapse tickers -

- +

+ Update Watchlist
@@ -744,7 +743,7 @@
- Save Game - Options + Save Game + Options
@@ -856,7 +855,7 @@ - - - + diff --git a/package-lock.json b/package-lock.json index 7831b8bea..7de055c9c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -805,6 +805,12 @@ "multicast-dns-service-types": "1.1.0" } }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, "boom": { "version": "2.10.1", "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", @@ -1085,6 +1091,16 @@ "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", "dev": true }, + "camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "dev": true, + "requires": { + "no-case": "2.3.2", + "upper-case": "1.1.3" + } + }, "camelcase": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", @@ -1684,6 +1700,23 @@ } } }, + "clean-css": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "dev": true, + "requires": { + "source-map": "0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "cli-cursor": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", @@ -2186,6 +2219,30 @@ "source-list-map": "2.0.0" } }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, + "requires": { + "boolbase": "1.0.0", + "css-what": "2.1.0", + "domutils": "1.5.1", + "nth-check": "1.0.1" + }, + "dependencies": { + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "requires": { + "dom-serializer": "0.1.0", + "domelementtype": "1.3.0" + } + } + } + }, "css-selector-tokenizer": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz", @@ -2197,6 +2254,12 @@ "regexpu-core": "1.0.0" } }, + "css-what": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", + "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=", + "dev": true + }, "cssesc": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", @@ -2529,6 +2592,23 @@ "esutils": "2.0.2" } }, + "dom-converter": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.1.4.tgz", + "integrity": "sha1-pF71cnuJDJv/5tfIduexnLDhfzs=", + "dev": true, + "requires": { + "utila": "0.3.3" + }, + "dependencies": { + "utila": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.3.3.tgz", + "integrity": "sha1-1+jn1+MJEHCSsF+NloiCTWM6QiY=", + "dev": true + } + } + }, "dom-serializer": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", @@ -4795,12 +4875,86 @@ "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", "dev": true }, + "html-minifier": { + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.20.tgz", + "integrity": "sha512-ZmgNLaTp54+HFKkONyLFEfs5dd/ZOtlquKaTnqIWFmx3Av5zG6ZPcV2d0o9XM2fXOTxxIf6eDcwzFFotke/5zA==", + "dev": true, + "requires": { + "camel-case": "3.0.0", + "clean-css": "4.2.1", + "commander": "2.17.1", + "he": "1.1.1", + "param-case": "2.1.1", + "relateurl": "0.2.7", + "uglify-js": "3.4.8" + }, + "dependencies": { + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "uglify-js": { + "version": "3.4.8", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.8.tgz", + "integrity": "sha512-WatYTD84gP/867bELqI2F/2xC9PQBETn/L+7RGq9MQOA/7yFBNvY1UwXqvtILeE6n0ITwBXxp34M0/o70dzj6A==", + "dev": true, + "requires": { + "commander": "2.17.1", + "source-map": "0.6.1" + } + } + } + }, "html-tags": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-2.0.0.tgz", "integrity": "sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos=", "dev": true }, + "html-webpack-plugin": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", + "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", + "dev": true, + "requires": { + "html-minifier": "3.5.20", + "loader-utils": "0.2.17", + "lodash": "4.17.10", + "pretty-error": "2.1.1", + "tapable": "1.0.0", + "toposort": "1.0.7", + "util.promisify": "1.0.0" + }, + "dependencies": { + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true, + "requires": { + "big.js": "3.2.0", + "emojis-list": "2.1.0", + "json5": "0.5.1", + "object-assign": "4.1.1" + } + } + } + }, "htmlparser2": { "version": "3.9.2", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", @@ -6385,6 +6539,12 @@ "signal-exit": "3.0.2" } }, + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", + "dev": true + }, "lru-cache": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", @@ -7001,6 +7161,15 @@ "integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==", "dev": true }, + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "requires": { + "lower-case": "1.1.4" + } + }, "node-forge": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", @@ -7608,6 +7777,15 @@ } } }, + "nth-check": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", + "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", + "dev": true, + "requires": { + "boolbase": "1.0.0" + } + }, "num2fraction": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", @@ -7731,6 +7909,16 @@ "object-keys": "1.0.11" } }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "1.1.2", + "es-abstract": "1.12.0" + } + }, "object.omit": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", @@ -7931,6 +8119,15 @@ "readable-stream": "2.3.4" } }, + "param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "dev": true, + "requires": { + "no-case": "2.3.2" + } + }, "parse-asn1": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", @@ -8984,6 +9181,16 @@ "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", "dev": true }, + "pretty-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", + "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", + "dev": true, + "requires": { + "renderkid": "2.0.1", + "utila": "0.4.0" + } + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -9360,6 +9567,12 @@ "jsesc": "0.5.0" } }, + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "dev": true + }, "remark": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/remark/-/remark-9.0.0.tgz", @@ -9422,6 +9635,81 @@ "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", "dev": true }, + "renderkid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.1.tgz", + "integrity": "sha1-iYyr/Ivt5Le5ETWj/9Mj5YwNsxk=", + "dev": true, + "requires": { + "css-select": "1.2.0", + "dom-converter": "0.1.4", + "htmlparser2": "3.3.0", + "strip-ansi": "3.0.1", + "utila": "0.3.3" + }, + "dependencies": { + "domhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.1.0.tgz", + "integrity": "sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ=", + "dev": true, + "requires": { + "domelementtype": "1.3.0" + } + }, + "domutils": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.1.6.tgz", + "integrity": "sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU=", + "dev": true, + "requires": { + "domelementtype": "1.3.0" + } + }, + "htmlparser2": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz", + "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=", + "dev": true, + "requires": { + "domelementtype": "1.3.0", + "domhandler": "2.1.0", + "domutils": "1.1.6", + "readable-stream": "1.0.34" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "utila": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.3.3.tgz", + "integrity": "sha1-1+jn1+MJEHCSsF+NloiCTWM6QiY=", + "dev": true + } + } + }, "repeat-element": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", @@ -11693,6 +11981,12 @@ } } }, + "toposort": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz", + "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=", + "dev": true + }, "tough-cookie": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", @@ -12232,6 +12526,12 @@ "integrity": "sha512-d4SJySNBXDaQp+DPrziv3xGS6w3d2Xt69FijJr86zMPBy23JEloMCEOUBBzuN7xCtjLCnmB9tI/z7SBCahHBOw==", "dev": true }, + "upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", + "dev": true + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -12377,6 +12677,22 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "dev": true, + "requires": { + "define-properties": "1.1.2", + "object.getownpropertydescriptors": "2.0.3" + } + }, + "utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", + "dev": true + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", diff --git a/package.json b/package.json index beba2a4e3..fd98ddd96 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "eslint": "^4.19.1", "eslint-plugin-node": "^6.0.1", "file-loader": "^1.1.11", + "html-webpack-plugin": "^3.2.0", "i18n-webpack-plugin": "^1.0.0", "istanbul": "^0.4.5", "js-beautify": "^1.5.10", diff --git a/src/Bladeburner.js b/src/Bladeburner.js index dd5bd7d7e..fcf1494c1 100644 --- a/src/Bladeburner.js +++ b/src/Bladeburner.js @@ -44,9 +44,9 @@ var DifficultyToTimeFactor = 10; //Action Difficulty divided by this to ge var DiffMultExponentialFactor = 0.28; var DiffMultLinearFactor = 650; -var EffAgiLinearFactor = 90e3; -var EffDexLinearFactor = 90e3; -var EffAgiExponentialFactor = 0.031; +var EffAgiLinearFactor = 40e3; +var EffDexLinearFactor = 40e3; +var EffAgiExponentialFactor = 0.032; var EffDexExponentialFactor = 0.03; var BaseRecruitmentTimeNeeded = 300; //Base time needed (s) to complete a Recruitment action @@ -659,7 +659,7 @@ function Bladeburner(params={}) { this.storedCycles = 0; - this.randomEventCounter = getRandomInt(300, 600); //5-10 minutes + this.randomEventCounter = getRandomInt(240, 600); //4-10 minutes //These times are in seconds this.actionTimeToComplete = 0; //0 or -1 is an infinite running action (like training) @@ -734,7 +734,7 @@ Bladeburner.prototype.create = function() { "whatever city you are currently in.", baseDifficulty:125,difficultyFac:1.02,rewardFac:1.041, rankGain:0.3, hpLoss:0.5, - count:getRandomInt(100, 500), countGrowth:getRandomInt(5, 75)/10, + count:getRandomInt(25, 500), countGrowth:getRandomInt(5, 75)/10, weights:{hack:0,str:0.05,def:0.05,dex:0.35,agi:0.35,cha:0.1, int:0.05}, decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.9, int:1}, isStealth:true @@ -746,7 +746,7 @@ Bladeburner.prototype.create = function() { "current city, and will also increase its chaos level.", baseDifficulty:250, difficultyFac:1.04,rewardFac:1.085, rankGain:0.9, hpLoss:1, - count:getRandomInt(25, 750), countGrowth:getRandomInt(5, 75)/10, + count:getRandomInt(5, 500), countGrowth:getRandomInt(5, 75)/10, weights:{hack:0,str:0.15,def:0.15,dex:0.25,agi:0.25,cha:0.1, int:0.1}, decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.8, int:0.9}, isKill:true @@ -758,7 +758,7 @@ Bladeburner.prototype.create = function() { "city, and will also increase its chaos level.", baseDifficulty:200, difficultyFac:1.03, rewardFac:1.065, rankGain:0.6, hpLoss:1, - count:getRandomInt(50, 1000), countGrowth:getRandomInt(5,75)/10, + count:getRandomInt(5, 500), countGrowth:getRandomInt(5,75)/10, weights:{hack:0,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0.1, int:0.1}, decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.8, int:0.9}, isKill:true @@ -773,7 +773,7 @@ Bladeburner.prototype.create = function() { "You will NOT lose HP from failed Investigation ops.", baseDifficulty:400, difficultyFac:1.03,rewardFac:1.07,reqdRank:25, rankGain:2.2, rankLoss:0.2, - count:getRandomInt(50, 200), countGrowth:getRandomInt(10, 40)/10, + count:getRandomInt(1, 250), countGrowth:getRandomInt(10, 40)/10, weights:{hack:0.25,str:0.05,def:0.05,dex:0.2,agi:0.1,cha:0.25, int:0.1}, decays:{hack:0.85,str:0.9,def:0.9,dex:0.9,agi:0.9,cha:0.7, int:0.9}, isStealth:true @@ -786,7 +786,7 @@ Bladeburner.prototype.create = function() { "data.", baseDifficulty:500, difficultyFac:1.04, rewardFac:1.09, reqdRank:100, rankGain:4.4, rankLoss:0.4, hpLoss:2, - count:getRandomInt(25, 300), countGrowth:getRandomInt(10, 40)/10, + count:getRandomInt(1, 250), countGrowth:getRandomInt(10, 40)/10, weights:{hack:0.2,str:0.05,def:0.05,dex:0.2,agi:0.2,cha:0.2, int:0.1}, decays:{hack:0.8,str:0.9,def:0.9,dex:0.9,agi:0.9,cha:0.7, int:0.9}, isStealth:true @@ -797,7 +797,7 @@ Bladeburner.prototype.create = function() { "notorious Synthoid criminals.", baseDifficulty:650, difficultyFac:1.04, rewardFac:1.095, reqdRank:500, rankGain:5.5, rankLoss:0.5, hpLoss:2.5, - count:getRandomInt(25,400), countGrowth:getRandomInt(3, 40)/10, + count:getRandomInt(1, 300), countGrowth:getRandomInt(3, 40)/10, weights:{hack:0.25,str:0.05,def:0.05,dex:0.25,agi:0.1,cha:0.2, int:0.1}, decays:{hack:0.8,str:0.85,def:0.85,dex:0.85,agi:0.85,cha:0.7, int:0.9}, isStealth:true @@ -809,7 +809,7 @@ Bladeburner.prototype.create = function() { "in order for this Operation to be successful", baseDifficulty:800, difficultyFac:1.045, rewardFac:1.1, reqdRank:3000, rankGain:55,rankLoss:2.5,hpLoss:50, - count:getRandomInt(25, 150), countGrowth:getRandomInt(2, 40)/10, + count:getRandomInt(1, 200), countGrowth:getRandomInt(2, 40)/10, weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0, int:0.1}, decays:{hack:0.7,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.9}, isKill:true @@ -821,7 +821,7 @@ Bladeburner.prototype.create = function() { "drawing any attention. Stealth and discretion are key.", baseDifficulty:1000, difficultyFac:1.05, rewardFac:1.11, reqdRank:20e3, rankGain:22, rankLoss:2, hpLoss:10, - count:getRandomInt(25, 250), countGrowth:getRandomInt(1, 20)/10, + count:getRandomInt(1, 250), countGrowth:getRandomInt(1, 20)/10, weights:{hack:0.1,str:0.1,def:0.1,dex:0.3,agi:0.3,cha:0, int:0.1}, decays:{hack:0.7,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.9}, isStealth:true, isKill:true @@ -833,7 +833,7 @@ Bladeburner.prototype.create = function() { "in the Synthoid communities.", baseDifficulty:1500, difficultyFac:1.06, rewardFac:1.14, reqdRank:50e3, rankGain:44, rankLoss:4, hpLoss:5, - count:getRandomInt(25, 200), countGrowth:getRandomInt(1, 20)/10, + count:getRandomInt(1, 200), countGrowth:getRandomInt(1, 20)/10, weights:{hack:0.1,str:0.1,def:0.1,dex:0.3,agi:0.3,cha:0, int:0.1}, decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.8}, isStealth:true, isKill:true @@ -900,7 +900,7 @@ Bladeburner.prototype.process = function() { this.randomEventCounter -= seconds; if (this.randomEventCounter <= 0) { this.randomEvent(); - this.randomEventCounter = getRandomInt(300, 600); + this.randomEventCounter = getRandomInt(240, 600); } this.processAction(seconds); @@ -1833,7 +1833,7 @@ Bladeburner.prototype.createOverviewContent = function() { "Bonus time makes the Bladeburner mechanic progress faster, up to 5x the normal speed." }); DomElems.overviewSkillPoints = createElement("p", {display:"block"}); - + DomElems.overviewAugSuccessMult = createElement("p", {display:"block"}); DomElems.overviewAugMaxStaminaMult = createElement("p", {display:"block"}); @@ -3703,7 +3703,7 @@ function initBladeburner() { name:SkillNames.Overclock, desc:"Each level of this skill decreases the time it takes " + "to attempt a Contract, Operation, and BlackOp by 1% (Max Level: 95)", - baseCost:5, costInc:1, maxLvl:95, + baseCost:5, costInc:1.1, maxLvl:95, actionTime:1 }); Skills[SkillNames.EvasiveSystem] = new Skill({ diff --git a/src/Constants.js b/src/Constants.js index 719a80040..9a405122c 100644 --- a/src/Constants.js +++ b/src/Constants.js @@ -6,6 +6,9 @@ let CONSTANTS = { //the player will have this level assuming no multipliers. Multipliers can cause skills to go above this. MaxSkillLevel: 975, + //Milliseconds per game cycle + MilliPerCycle: 200, + //How much reputation is needed to join a megacorporation's faction CorpFactionRepRequirement: 200e3, @@ -493,9 +496,24 @@ let CONSTANTS = { "World Stock Exchange account and TIX API Access
", LatestUpdate: - "v0.40.3
" + - "* Added a setting in .fconf for enabling line-wrap in the Terminal input
" - + `v0.40.3
+ -----------------------------------------------
+ * Bladeburner Changes:
+ *** Increased the effect that agi and dexterity have on action time
+ *** Starting number of contracts/operations available will be slightly lower
+ *** Random events will now happen slightly more often
+ *** Slightly increased the rate at which the Overclock skill point cost increases
+ -----------------------------------------------
+ * The maximum volatility of stocks is now randomized (randomly generated within a certain range every time the game resets)
+ * Increased the range of possible values for initial stock prices
+ * b1t_flum3.exe program can now be created immediately at Hacking level 1 (rather than hacking level 5)
+ * UI improvements for the character overview panel and the left-hand menu (by mat-jaworski)
+ * Updated documentation to reflect the fact that Netscript port handles (getPortHandle()) only works in NetscriptJS (2.0), NOT Netscript 1.0
+ * Added tryWrite() Netscript function
+ * When working (for a company/faction), experience is gained immediately/continuously rather than all at once when the work is finished
+ * Added a setting in .fconf for enabling line-wrap in the Terminal input
+ * Added 'wget' Terminal command
+ * Improved the introductory tutorial` } export {CONSTANTS}; diff --git a/src/CreateProgram.js b/src/CreateProgram.js index dd8159e78..f3a808a54 100644 --- a/src/CreateProgram.js +++ b/src/CreateProgram.js @@ -83,9 +83,9 @@ const Programs = { time: CONSTANTS.MillisecondsPerQuarterHour, }), BitFlume: new Program("b1t_flum3.exe", { - level: 5, + level: 1, tooltip:"This program creates a portal to the BitNode Nexus (allows you to restart and switch BitNodes)", - req: function() {return Player.sourceFiles.length > 0 && Player.hacking_skill >= 5}, + req: function() {return Player.sourceFiles.length > 0 && Player.hacking_skill >= 1}, time: CONSTANTS.MillisecondsPerFiveMinutes / 5, }), // special because you can't create it. diff --git a/src/Faction.js b/src/Faction.js index 688021af9..133675548 100644 --- a/src/Faction.js +++ b/src/Faction.js @@ -115,7 +115,7 @@ function factionExists(name) { // This might change in the future for balance function initFactions() { for(const name in FactionInfos) { - resetFaction(new Faction(name)); + resetFaction(new Faction(name)); } } diff --git a/src/FactionInfo.ts b/src/FactionInfo.ts index ee0695779..8e9d636cf 100644 --- a/src/FactionInfo.ts +++ b/src/FactionInfo.ts @@ -66,26 +66,43 @@ class FactionInfo { export const FactionInfos: IMap = { // Endgame Illuminati: new FactionInfo( - "Humanity never changes. No matter how civilized society becomes, it will eventually fall back into chaos. And " + - "from this chaos, we are the Invisible hand that guides them to order.", - [], true, true, true, false), + "Humanity never changes. No matter how civilized society becomes, it will eventually fall back into chaos. " + + "And from this chaos, we are the Invisible hand that guides them to order. ", + [], + true, + true, + true, + false), Daedalus: new FactionInfo( "Yesterday we obeyed kings and bent our necks to emperors. Today we kneel only to truth.", - [], true, true, true, false), + [], + true, + true, + true, + false), "The Covenant": new FactionInfo( "Surrender yourself. Give up your empty individuality to become part of something great, something eternal. " + "Become a slave. Submit your mind, body, and soul. Only then can you set yourself free.
" + "
" + "Only then can you discover immortality.", - [], true, true, true, false), + [], + true, + true, + true, + false), // Megacorporations, each forms its own faction ECorp: new FactionInfo( - "ECorp's mission is simple: to connect the world of today with the technology of tomorrow. With our wide range " + - "of Internet-related software and commercial hardware, ECorp makes the world's information universally accessible.", - [], true, true, true, true), + "ECorp's mission is simple: to connect the world of today with the technology of tomorrow. With our wide " + + "range of Internet-related software and commercial hardware, ECorp makes the world's information " + + "universally accessible.", + [], + true, + true, + true, + true), MegaCorp: new FactionInfo( "MegaCorp does things that others don't. We imagine. We create. We invent. We build things that others have " + @@ -93,66 +110,110 @@ export const FactionInfos: IMap = { "unprecendented scale, in ways that no other company can.
" + "
" + "In our labs and factories and on the ground with customers, MegaCorp is ushering in a new era for the world.", - [], true, true, true, true), + [], + true, + true, + true, + true), "Bachman & Associates": new FactionInfo( - "Where Law and Business meet - thats where we are.
" + + "Where Law and Business meet - thats where we are.
" + "
" + "Legal Insight - Business Instinct - Experience Innovation", - [], true, true, true, true), + [], + true, + true, + true, + true), "Blade Industries": new FactionInfo( "Augmentation is salvation", - [], true, true, true, true), + [], + true, + true, + true, + true), NWO: new FactionInfo( - "The human being does not truly desire freedom. It wants to be observed, understood, and judged. It wants to be " + - "given purpose and direction in its life. That is why humans created God. And that is why humans created " + + "The human being does not truly desire freedom. It wants to be observed, understood, and judged. It wants to " + + "be given purpose and direction in its life. That is why humans created God. And that is why humans created " + "civilization - not because of willingness, but because of a need to be incorporated into higher orders of " + "structure and meaning.", - [], true, true, true, true), + [], + true, + true, + true, + true), "Clarke Incorporated": new FactionInfo( "Unlocking the power of the genome", - [], true, true, true, true), + [], + true, + true, + true, + true), "OmniTek Incorporated": new FactionInfo( "Simply put, our mission is to design and build robots that make a difference", - [], true, true, true, true), + [], + true, + true, + true, + true), "Four Sigma": new FactionInfo( - "The scientific method is the best way to approach investing. Big strategies backed up with big data. Driven by " + - "deep learning and innovative ideas. And improved by iteration. That's Four Sigma.", - [], true, true, true, true), + "The scientific method is the best way to approach investing. Big strategies backed up with big data. Driven " + + "by deep learning and innovative ideas. And improved by iteration. That's Four Sigma.", + [], + true, + true, + true, + true), "KuaiGong International": new FactionInfo( "Dream big. Work hard. Make history.", - [], true, true, true, true), + [], + true, + true, + true, + true), // Other Corporations "Fulcrum Secret Technologies": new FactionInfo( "The human organism has an innate desire to worship. That is why they created gods. If there were no gods, it " + "would be necessary to create them. And now we can.", - [], true, true, false, true), + [], + true, + true, + false, + true), // Hacker groups BitRunners: new FactionInfo( - "Our entire lives are controlled by bits. All of our actions, our thoughts, our personal information. It's all " + - "transformed into bits, stored in bits, communicated through bits. It’s impossible for any person to move, to " + - "live, to operate at any level without the use of bits. And when a person moves, lives, and operates, they leave " + - "behind their bits, mere traces of seemingly meaningless fragments of information. But these bits can be " + + "Our entire lives are controlled by bits. All of our actions, our thoughts, our personal information. It's " + + "all transformed into bits, stored in bits, communicated through bits. It’s impossible for any person to move, " + + "to live, to operate at any level without the use of bits. And when a person moves, lives, and operates, they " + + "leave behind their bits, mere traces of seemingly meaningless fragments of information. But these bits can be " + "reconstructed. Transformed. Used.
" + "
" + "Those who run the bits, run the world", - [], true, true, false, false), + [], + true, + true, + false, + false), "The Black Hand": new FactionInfo( - "The world, so afraid of strong government, now has no government. Only power - Digital power. Financial power. " + - "Technological power. And those at the top rule with an invisible hand. They built a society where the rich get " + - "richer, and everyone else suffers.
" + + "The world, so afraid of strong government, now has no government. Only power - Digital power. Financial " + + "power. Technological power. And those at the top rule with an invisible hand. They built a society where the " + + "rich get richer, and everyone else suffers.
" + "
" + "So much pain. So many lives. Their darkness must end.", - [], true, true, true, false), + [], + true, + true, + true, + false), NiteSec: new FactionInfo( " __..__
" + @@ -190,40 +251,108 @@ export const FactionInfos: IMap = { " d .dNITESEC $ |
" + " :bp.__.gNITESEC$$ :$ ;
" + " NITESECNITESECNIT $$b :
", - [], true, true, false, false), + [], + true, + true, + false, + false), // City factions, essentially governments Aevum: new FactionInfo( "The Silicon City", - ["Chongqing", "New Tokyo", "Ishima", "Volhaven"], true, true, true, true), + [ + "Chongqing", + "New Tokyo", + "Ishima", + "Volhaven", + ], + true, + true, + true, + true), Chongqing: new FactionInfo( "Serve the people", - ["Sector-12", "Aevum", "Volhaven"], true, true, true, true), + [ + "Sector-12", + "Aevum", + "Volhaven", + ], + true, + true, + true, + true), Ishima: new FactionInfo( "The East Asian Order of the Future", - ["Sector-12", "Aevum", "Volhaven"], true, true, true, true), + [ + "Sector-12", + "Aevum", + "Volhaven", + ], + true, + true, + true, + true), "New Tokyo": new FactionInfo( "Asia's World City", - ["Sector-12", "Aevum", "Volhaven"], true, true, true, true), + [ + "Sector-12", + "Aevum", + "Volhaven", + ], + true, + true, + true, + true), "Sector-12": new FactionInfo( "The City of the Future", - ["Chongqing", "New Tokyo", "Ishima", "Volhaven"], true, true, true, true), + [ + "Chongqing", + "New Tokyo", + "Ishima", + "Volhaven", + ], + true, + true, + true, + true), Volhaven: new FactionInfo( "Benefit, Honour, and Glory", - ["Chongqing", "Sector-12", "New Tokyo", "Aevum", "Ishima"], true, true, true, true), + [ + "Chongqing", + "Sector-12", + "New Tokyo", + "Aevum", + "Ishima", + ], + true, + true, + true, + true), // Criminal Organizations/Gangs "Speakers for the Dead": new FactionInfo( "It is better to reign in hell than to serve in heaven.", - [], true, true, true, true), + [], + true, + true, + true, + true), "The Dark Army": new FactionInfo( "The World doesn't care about right or wrong. It's all about power.", - [], true, true, true, false), + [], + true, + true, + true, + false), "The Syndicate": new FactionInfo( "Honor holds you back", - [], true, true, true, true), + [], + true, + true, + true, + true), Silhouette: new FactionInfo( "Corporations have filled the void of power left behind by the collapse of Western government. The issue is " + @@ -231,35 +360,64 @@ export const FactionInfos: IMap = { "corporations, you don't even know who you're working for.
" + "
" + "That's terror. Terror, fear, and corruption. All born into the system, all propagated by the system.", - [], true, true, true, false), + [], + true, + true, + true, + false), Tetrads: new FactionInfo( "Following the Mandate of Heaven and Carrying out the Way", - [], false, false, true, true), + [], + false, + false, + true, + true), "Slum Snakes": new FactionInfo( "Slum Snakes rule!", - [], false, false, true, true), + [], + false, + false, + true, + true), // Earlygame factions - factions the player will prestige with early on that don't belong in other categories. Netburners: new FactionInfo( "~~//*>H4CK|\|3T 8URN3R5**>?>\\~~", - [], true, true, false, false), + [], + true, + true, + false, + false), "Tian Di Hui": new FactionInfo( "Obey Heaven and Work Righteousness", - [], true, true, false, true), + [], + true, + true, + false, + true), CyberSec: new FactionInfo( "The Internet is the first thing that humanity has built that humanity doesn’t understand, the largest " + "experiment in anarchy that we have ever had. And as the world becomes increasingly dominated by the internet, " + - "society approaches the brink of total chaos. We serve only to protect society, to protect humanity, to protect " + - "the world from its imminent collapse.", - [], true, true, false, false), + "society approaches the brink of total chaos. We serve only to protect society, to protect humanity, to " + + "protect the world from its imminent collapse.", + [], + true, + true, + false, + false), // Special Factions Bladeburners: new FactionInfo( - "It's too bad they won't live. But then again, who does?

Note that for this faction, reputation can only " + - "be gained through Bladeburner actions. Completing Bladeburner contracts/operations will increase your reputation.", - [], false, false, false, false), + "It's too bad they won't live. But then again, who does?

Note that for this faction, reputation can " + + "only be gained through Bladeburner actions. Completing Bladeburner contracts/operations will increase your " + + "reputation.", + [], + false, + false, + false, + false), }; diff --git a/src/Hacking.js b/src/Hacking.js new file mode 100644 index 000000000..457c4fc6c --- /dev/null +++ b/src/Hacking.js @@ -0,0 +1,90 @@ +import { BitNodeMultipliers } from "./BitNodeMultipliers"; +import { Player } from "./Player"; +import { Server } from "./Server"; + +/** + * Returns the chance the player has to successfully hack a server + */ +export function calculateHackingChance(server) { + const hackFactor = 1.75; + const intFactor = 0.2; + const difficultyMult = (100 - server.hackDifficulty) / 100; + const skillMult = (hackFactor * Player.hacking_skill) + (intFactor * Player.intelligence); + const skillChance = (skillMult - server.requiredHackingSkill) / skillMult; + const chance = skillChance * difficultyMult * Player.hacking_chance_mult; + if (chance > 1) { return 1; } + if (chance < 0) { return 0; } + + return chance; +} + +/** + * Returns the amount of hacking experience the player will gain upon + * successfully hacking a server + */ +export function calculateHackingExpGain(server) { + const baseExpGain = 3; + const diffFactor = 0.3; + if (server.baseDifficulty == null) { + server.baseDifficulty = server.hackDifficulty; + } + var expGain = baseExpGain; + expGain += (server.baseDifficulty * Player.hacking_exp_mult * diffFactor); + + return expGain * BitNodeMultipliers.HackExpGain; +} + +/** + * Returns the percentage of money that will be stolen from a server if + * it is successfully hacked (returns the decimal form, not the actual percent value) + */ +export function calculatePercentMoneyHacked(server) { + // Adjust if needed for balancing. This is the divisor for the final calculation + const balanceFactor = 240; + + const difficultyMult = (100 - server.hackDifficulty) / 100; + const skillMult = (Player.hacking_skill - (server.requiredHackingSkill - 1)) / Player.hacking_skill; + const percentMoneyHacked = difficultyMult * skillMult * Player.hacking_money_mult / balanceFactor; + if (percentMoneyHacked < 0) { return 0; } + if (percentMoneyHacked > 1) { return 1; } + + return percentMoneyHacked * BitNodeMultipliers.ScriptHackMoney; +} + +/** + * Returns time it takes to complete a hack on a server, in seconds + */ +export function calculateHackingTime(server) { + const difficultyMult = server.requiredHackingSkill * server.hackDifficulty; + + const baseDiff = 500; + const baseSkill = 50; + const diffFactor = 2.5; + const intFactor = 0.1; + var skillFactor = (diffFactor * difficultyMult + baseDiff); + // tslint:disable-next-line + skillFactor /= (Player.hacking_skill + baseSkill + (intFactor * Player.intelligence)); + + const hackTimeMultiplier = 5; + const hackingTime = hackTimeMultiplier * skillFactor / Player.hacking_speed_mult; + + return hackingTime; +} + +/** + * Returns time it takes to complete a grow operation on a server, in seconds + */ +export function calculateGrowTime(server) { + const growTimeMultiplier = 3.2; // Relative to hacking time. 16/5 = 3.2 + + return growTimeMultiplier * calculateHackingTime(server); +} + +/** + * Returns time it takes to complete a weaken operation on a server, in seconds + */ +export function calculateWeakenTime(server) { + const weakenTimeMultiplier = 4; // Relative to hacking time + + return weakenTimeMultiplier * calculateHackingTime(server); +} diff --git a/src/HacknetNode.js b/src/HacknetNode.js index 8f1faa218..edb212c05 100644 --- a/src/HacknetNode.js +++ b/src/HacknetNode.js @@ -2,7 +2,7 @@ import {BitNodeMultipliers} from "./BitNodeMultipliers"; import {CONSTANTS} from "./Constants"; import {Engine} from "./engine"; import {iTutorialSteps, iTutorialNextStep, - iTutorialIsRunning, currITutorialStep} from "./InteractiveTutorial"; + ITutorial} from "./InteractiveTutorial"; import {Player} from "./Player"; import {dialogBoxCreate} from "../utils/DialogBox"; import {clearEventListeners} from "../utils/uiHelpers/clearEventListeners"; @@ -245,8 +245,8 @@ Reviver.constructors.HacknetNode = HacknetNode; function purchaseHacknet() { /* INTERACTIVE TUTORIAL */ - if (iTutorialIsRunning) { - if (currITutorialStep == iTutorialSteps.HacknetNodesIntroduction) { + if (ITutorial.isRunning) { + if (ITutorial.currStep === iTutorialSteps.HacknetNodesIntroduction) { iTutorialNextStep(); } else { return; @@ -446,7 +446,7 @@ function updateHacknetNodesContent() { //Update player's money updateText("hacknet-nodes-player-money", "$" + formatNumber(Player.money.toNumber(), 2)); - updateText("hacknet-nodes-total-production", "$" + formatNumber(Player.totalHacknetNodeProduction, 2) + " / second"); + updateText("hacknet-nodes-total-production", "$" + formatNumber(Player.totalHacknetNodeProduction, 2) + " / sec"); //Update information in each owned hacknet node for (var i = 0; i < Player.hacknetNodes.length; ++i) { @@ -548,7 +548,7 @@ function updateHacknetNodeDomElement(nodeObj) { updateText("hacknet-node-name-" + nodeName, nodeName); updateText("hacknet-node-total-production-" + nodeName, "$" + formatNumber(nodeObj.totalMoneyGenerated, 2)); - updateText("hacknet-node-production-rate-" + nodeName, "($" + formatNumber(nodeObj.moneyGainRatePerSecond, 2) + " / second)"); + updateText("hacknet-node-production-rate-" + nodeName, "($" + formatNumber(nodeObj.moneyGainRatePerSecond, 2) + " / sec)"); updateText("hacknet-node-level-" + nodeName, nodeObj.level); updateText("hacknet-node-ram-" + nodeName, nodeObj.ram + "GB"); updateText("hacknet-node-cores-" + nodeName, nodeObj.cores); diff --git a/src/Infiltration.js b/src/Infiltration.js index feb8b0609..504c3407f 100644 --- a/src/Infiltration.js +++ b/src/Infiltration.js @@ -162,7 +162,7 @@ function nextInfiltrationLevel(inst) { if (!e.isTrusted) {return false;} var res = attemptInfiltrationKill(inst); if (res[0]) { - writeInfiltrationStatusText("You SUCCESSFULLY killed the security bots! Unfortunately you alerted the " + + writeInfiltrationStatusText("You SUCCESSFULLY killed the security bots! Unfortunately you alerted the " + "rest of the facility's security. The facility's security " + "level increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%"); Player.karma -= 1; @@ -170,7 +170,7 @@ function nextInfiltrationLevel(inst) { return false; } else { var dmgTaken = Math.max(1, Math.round(1.5 * inst.securityLevel / Player.defense)); - writeInfiltrationStatusText("You FAILED to kill the security bots. The bots fight back " + + writeInfiltrationStatusText("You FAILED to kill the security bots. The bots fight back " + "and raise the alarm! You take " + dmgTaken + " damage and " + "the facility's security level increases by " + formatNumber((res[1]*100)-100, 2).toString() + "%"); @@ -186,12 +186,12 @@ function nextInfiltrationLevel(inst) { if (!e.isTrusted) {return false;} var res = attemptInfiltrationAssassinate(inst); if (res[0]) { - writeInfiltrationStatusText("You SUCCESSFULLY assassinated the security bots without being detected!"); + writeInfiltrationStatusText("You SUCCESSFULLY assassinated the security bots without being detected!"); Player.karma -= 1; endInfiltrationLevel(inst); return false; } else { - writeInfiltrationStatusText("You FAILED to assassinate the security bots. The bots have not detected " + + writeInfiltrationStatusText("You FAILED to assassinate the security bots. The bots have not detected " + "you but are now more alert for an intruder. The facility's security level " + "has increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%"); } @@ -209,7 +209,7 @@ function nextInfiltrationLevel(inst) { if (!e.isTrusted) {return false;} var res = attemptInfiltrationKill(inst); if (res[0]) { - writeInfiltrationStatusText("You SUCCESSFULLY killed the security guard! Unfortunately you alerted the " + + writeInfiltrationStatusText("You SUCCESSFULLY killed the security guard! Unfortunately you alerted the " + "rest of the facility's security. The facility's security " + "level has increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%"); Player.karma -= 3; @@ -218,7 +218,7 @@ function nextInfiltrationLevel(inst) { return false; } else { var dmgTaken = Math.max(1, Math.round(inst.securityLevel / Player.defense)); - writeInfiltrationStatusText("You FAILED to kill the security guard. The guard fights back " + + writeInfiltrationStatusText("You FAILED to kill the security guard. The guard fights back " + "and raises the alarm! You take " + dmgTaken + " damage and " + "the facility's security level has increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%"); @@ -236,13 +236,13 @@ function nextInfiltrationLevel(inst) { if (!e.isTrusted) {return false;} var res = attemptInfiltrationAssassinate(inst); if (res[0]) { - writeInfiltrationStatusText("You SUCCESSFULLY assassinated the security guard without being detected!"); + writeInfiltrationStatusText("You SUCCESSFULLY assassinated the security guard without being detected!"); Player.karma -= 3; ++Player.numPeopleKilled; endInfiltrationLevel(inst); return false; } else { - writeInfiltrationStatusText("You FAILED to assassinate the security guard. The guard has not detected " + + writeInfiltrationStatusText("You FAILED to assassinate the security guard. The guard has not detected " + "you but is now more alert for an intruder. The facility's security level " + "has increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%"); } @@ -259,14 +259,14 @@ function nextInfiltrationLevel(inst) { if (!e.isTrusted) {return false;} var res = attemptInfiltrationKnockout(inst); if (res[0]) { - writeInfiltrationStatusText("You SUCCESSFULLY knocked out the security guard! " + + writeInfiltrationStatusText("You SUCCESSFULLY knocked out the security guard! " + "Unfortunately you made a lot of noise and alerted other security."); writeInfiltrationStatusText("The facility's security level increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%"); endInfiltrationLevel(inst); return false; } else { var dmgTaken = Math.max(1, Math.round(inst.securityLevel / Player.defense)); - writeInfiltrationStatusText("You FAILED to knockout the security guard. The guard " + + writeInfiltrationStatusText("You FAILED to knockout the security guard. The guard " + "raises the alarm and fights back! You take " + dmgTaken + " damage and " + "the facility's security level increases by " + formatNumber((res[1]*100)-100, 2).toString() + "%"); if (Player.takeDamage(dmgTaken)) { @@ -282,13 +282,13 @@ function nextInfiltrationLevel(inst) { if (!e.isTrusted) {return false;} var res = attemptInfiltrationStealthKnockout(inst); if (res[0]) { - writeInfiltrationStatusText("You SUCCESSFULLY knocked out the security guard without making " + + writeInfiltrationStatusText("You SUCCESSFULLY knocked out the security guard without making " + "any noise!"); endInfiltrationLevel(inst); return false; } else { var dmgTaken = Math.max(1, Math.round(inst.securityLevel / Player.defense)); - writeInfiltrationStatusText("You FAILED to stealthily knockout the security guard. The guard " + + writeInfiltrationStatusText("You FAILED to stealthily knockout the security guard. The guard " + "raises the alarm and fights back! You take " + dmgTaken + " damage and " + "the facility's security level increases by " + formatNumber((res[1]*100)-100, 2).toString() + "%"); if (Player.takeDamage(dmgTaken)) { @@ -304,12 +304,12 @@ function nextInfiltrationLevel(inst) { if (!e.isTrusted) {return false;} var res = attemptInfiltrationHack(inst); if (res[0]) { - writeInfiltrationStatusText("You SUCCESSFULLY hacked and disabled the security system!"); + writeInfiltrationStatusText("You SUCCESSFULLY hacked and disabled the security system!"); writeInfiltrationStatusText("The facility's security level increased by " + ((res[1]*100) - 100).toString() + "%"); endInfiltrationLevel(inst); return false; } else { - writeInfiltrationStatusText("You FAILED to hack the security system. The facility's " + + writeInfiltrationStatusText("You FAILED to hack the security system. The facility's " + "security level increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%"); } updateInfiltrationButtons(inst, scenario); @@ -321,12 +321,12 @@ function nextInfiltrationLevel(inst) { if (!e.isTrusted) {return false;} var res = attemptInfiltrationDestroySecurity(inst); if (res[0]) { - writeInfiltrationStatusText("You SUCCESSFULLY and violently destroy the security system!"); + writeInfiltrationStatusText("You SUCCESSFULLY and violently destroy the security system!"); writeInfiltrationStatusText("The facility's security level increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%"); endInfiltrationLevel(inst); return false; } else { - writeInfiltrationStatusText("You FAILED to destroy the security system. The facility's " + + writeInfiltrationStatusText("You FAILED to destroy the security system. The facility's " + "security level increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%"); } updateInfiltrationButtons(inst, scenario); @@ -338,11 +338,11 @@ function nextInfiltrationLevel(inst) { if (!e.isTrusted) {return false;} var res = attemptInfiltrationSneak(inst); if (res[0]) { - writeInfiltrationStatusText("You SUCCESSFULLY sneak past the security undetected!"); + writeInfiltrationStatusText("You SUCCESSFULLY sneak past the security undetected!"); endInfiltrationLevel(inst); return false; } else { - writeInfiltrationStatusText("You FAILED and were detected while trying to sneak past security! The facility's " + + writeInfiltrationStatusText("You FAILED and were detected while trying to sneak past security! The facility's " + "security level increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%"); } updateInfiltrationButtons(inst, scenario); @@ -354,12 +354,12 @@ function nextInfiltrationLevel(inst) { if (!e.isTrusted) {return false;} var res = attemptInfiltrationPickLockedDoor(inst); if (res[0]) { - writeInfiltrationStatusText("You SUCCESSFULLY pick the locked door!"); + writeInfiltrationStatusText("You SUCCESSFULLY pick the locked door!"); writeInfiltrationStatusText("The facility's security level increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%"); endInfiltrationLevel(inst); return false; } else { - writeInfiltrationStatusText("You FAILED to pick the locked door. The facility's security level " + + writeInfiltrationStatusText("You FAILED to pick the locked door. The facility's security level " + "increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%"); } updateInfiltrationButtons(inst, scenario); @@ -377,13 +377,13 @@ function nextInfiltrationLevel(inst) { } var res = attemptInfiltrationBribe(inst); if (res[0]) { - writeInfiltrationStatusText("You SUCCESSFULLY bribed a guard to let you through " + + writeInfiltrationStatusText("You SUCCESSFULLY bribed a guard to let you through " + "to the next clearance level for $" + bribeAmt); Player.loseMoney(bribeAmt); endInfiltrationLevel(inst); return false; } else { - writeInfiltrationStatusText("You FAILED to bribe a guard! The guard is alerting " + + writeInfiltrationStatusText("You FAILED to bribe a guard! The guard is alerting " + "other security guards about your presence! The facility's " + "security level increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%"); } @@ -396,12 +396,12 @@ function nextInfiltrationLevel(inst) { if (!e.isTrusted) {return false;} var res = attemptInfiltrationEscape(inst); if (res[0]) { - writeInfiltrationStatusText("You SUCCESSFULLY escape from the facility with the stolen classified " + + writeInfiltrationStatusText("You SUCCESSFULLY escape from the facility with the stolen classified " + "documents and company secrets!"); endInfiltration(inst, true); return false; } else { - writeInfiltrationStatusText("You FAILED to escape from the facility. You took 1 damage. The facility's " + + writeInfiltrationStatusText("You FAILED to escape from the facility. You took 1 damage. The facility's " + "security level increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%"); if (Player.takeDamage(1)) { endInfiltration(inst, false); @@ -429,9 +429,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 ($" + - formatNumber(secretMoneyValue, 2) + "), or they " + - "could be given to factions for reputation (" + formatNumber(secretValue, 3) + " rep)"); + "These classified secrets could probably be sold for money ($" + + formatNumber(secretMoneyValue, 2) + "), or they " + + "could be given to factions for reputation (" + formatNumber(secretValue, 3) + " rep)"); } //Increase security level based on difficulty @@ -469,17 +469,18 @@ function updateInfiltrationLevelText(inst) { var expMultiplier = 2 * inst.clearanceLevel / inst.maxClearanceLevel; document.getElementById("infiltration-level-text").innerHTML = - "Facility name: " + inst.companyName + "
" + - "Clearance Level: " + inst.clearanceLevel + "
" + - "Security Level: " + formatNumber(inst.securityLevel, 3) + "

" + - "Total reputation value of secrets stolen: " + formatNumber(totalValue, 3) + "
" + - "Total monetary value of secrets stolen: $" + formatNumber(totalMoneyValue, 2) + "

" + - "Hack exp gained: " + formatNumber(inst.hackingExpGained * expMultiplier, 3) + "
" + - "Str exp gained: " + formatNumber(inst.strExpGained * expMultiplier, 3) + "
" + - "Def exp gained: " + formatNumber(inst.defExpGained * expMultiplier, 3) + "
" + - "Dex exp gained: " + formatNumber(inst.dexExpGained * expMultiplier, 3) + "
" + - "Agi exp gained: " + formatNumber(inst.agiExpGained * expMultiplier, 3) + "
" + - "Cha exp gained: " + formatNumber(inst.chaExpGained * expMultiplier, 3); + "Facility name:    " + inst.companyName + "
" + + "Clearance Level:  " + inst.clearanceLevel + "
" + + "Security Level:   " + formatNumber(inst.securityLevel, 3) + "

" + + "Total value of stolen secrets
" + + "Reputation:       " + formatNumber(totalValue, 3) + "
" + + "Money:           $" + formatNumber(totalMoneyValue, 2) + "

" + + "Hack exp gained:  " + formatNumber(inst.hackingExpGained * expMultiplier, 3) + "
" + + "Str exp gained:   " + formatNumber(inst.strExpGained * expMultiplier, 3) + "
" + + "Def exp gained:   " + formatNumber(inst.defExpGained * expMultiplier, 3) + "
" + + "Dex exp gained:   " + formatNumber(inst.dexExpGained * expMultiplier, 3) + "
" + + "Agi exp gained:   " + formatNumber(inst.agiExpGained * expMultiplier, 3) + "
" + + "Cha exp gained:   " + formatNumber(inst.chaExpGained * expMultiplier, 3); } function updateInfiltrationButtons(inst, scenario) { diff --git a/src/InteractiveTutorial.js b/src/InteractiveTutorial.js index 953b52cb5..5eccc4d09 100644 --- a/src/InteractiveTutorial.js +++ b/src/InteractiveTutorial.js @@ -1,55 +1,75 @@ import {Engine} from "./engine"; import {Player} from "./Player"; -import {dialogBoxCreate} from "../utils/DialogBox"; +import {Settings} from "./Settings"; import {clearEventListeners} from "../utils/uiHelpers/clearEventListeners"; +import {createElement} from "../utils/uiHelpers/createElement"; +import {createPopup} from "../utils/uiHelpers/createPopup"; +import {removeElementById} from "../utils/uiHelpers/removeElementById"; -/* InteractiveTutorial.js */ -let iTutorialSteps = { - Start: "Start", - GoToCharacterPage: "Click on the Character page menu link", - CharacterPage: "Introduction to Character page", - CharacterGoToTerminalPage: "Click on the Terminal link", - TerminalIntro: "Introduction to terminal interface", - TerminalHelp: "Using the help command to display all options in terminal", - TerminalLs: "Use the ls command to show all programs/scripts. Right now we have NUKE.exe", - TerminalScan: "Using the scan command to display all available connections", - TerminalScanAnalyze1: "Use the scan-analyze command to show hacking related information", - TerminalScanAnalyze2: "Use the scan-analyze command with a depth of 3", - TerminalConnect: "Using the telnet/connect command to connect to another server", - TerminalAnalyze: "Use the analyze command to display details about this server", - TerminalNuke: "Use the NUKE Program to gain root access to a server", - TerminalManualHack: "Use the hack command to manually hack a server", - TerminalHackingMechanics: "Briefly explain hacking mechanics", - TerminalCreateScript: "Create a script using nano", - TerminalTypeScript: "This occurs in the Script Editor page...type the script then save and close", - TerminalFree: "Use the free command to check RAM", - TerminalRunScript: "Use the run command to run a script", - TerminalGoToActiveScriptsPage: "Go to the ActiveScriptsPage", - ActiveScriptsPage: "Introduction to the Active Scripts Page", - ActiveScriptsToTerminal: "Go from Active Scripts Page Back to Terminal", - TerminalTailScript: "Use the tail command to show a script's logs", - GoToHacknetNodesPage: "Go to the Hacknet Nodes page", - HacknetNodesIntroduction: "Introduction to Hacknet Nodesm and have user purchase one", - HacknetNodesGoToWorldPage: "Go to the world page", - WorldDescription: "Tell the user to explore..theres a lot of different stuff to do out there", - TutorialPageInfo: "The tutorial page contains a lot of info on different subjects", - End: "End", +//Ordered array of keys to Interactive Tutorial Steps +const orderedITutorialSteps = [ + "Start", + "GoToCharacterPage", //Click on 'Stats' page + "CharacterPage", //Introduction to 'Stats' page + "CharacterGoToTerminalPage", //Go back to Terminal + "TerminalIntro", //Introduction to Terminal + "TerminalHelp", //Using 'help' Terminal command + "TerminalLs", //Using 'ls' Terminal command + "TerminalScan", //Using 'scan' Terminal command + "TerminalScanAnalyze1", //Using 'scan-analyze' Terminal command + "TerminalScanAnalyze2", //Using 'scan-analyze 3' Terminal command + "TerminalConnect", //Connecting to foodnstuff + "TerminalAnalyze", //Analyzing foodnstuff + "TerminalNuke", //NUKE foodnstuff + "TerminalManualHack", //Hack foodnstuff + "TerminalHackingMechanics", //Explanation of hacking mechanics + "TerminalCreateScript", //Create a script using 'nano' + "TerminalTypeScript", //Script Editor page - Type script and then save & close + "TerminalFree", //Using 'Free' Terminal command + "TerminalRunScript", //Running script using 'run' Terminal command + "TerminalGoToActiveScriptsPage", + "ActiveScriptsPage", + "ActiveScriptsToTerminal", + "TerminalTailScript", + "GoToHacknetNodesPage", + "HacknetNodesIntroduction", + "HacknetNodesGoToWorldPage", + "WorldDescription", + "TutorialPageInfo", + "End" +] + +//Create an 'enum' for the Steps +const iTutorialSteps = {}; +for (let i = 0; i < orderedITutorialSteps.length; ++i) { + iTutorialSteps[orderedITutorialSteps[i]] = i; } -var currITutorialStep = iTutorialSteps.Start; -var iTutorialIsRunning = false; +var ITutorial = { + currStep: 0, //iTutorialSteps.Start + isRunning: false, + + //Keeps track of whether each step has been done + stepIsDone: {}, +} function iTutorialStart() { + //Initialize Interactive Tutorial state by settings 'done' for each state to false + ITutorial.stepIsDone = {}; + for (let i = 0; i < orderedITutorialSteps.length; ++i) { + ITutorial.stepIsDone[i] = false; + } + + Engine.loadTerminalContent(); + //Don't autosave during this interactive tutorial - Engine.Counters.autoSaveCounter = 999000000000; + Engine.Counters.autoSaveCounter = Infinity; console.log("Interactive Tutorial started"); - currITutorialStep = iTutorialSteps.Start; - iTutorialIsRunning = true; + ITutorial.currStep = 0; + ITutorial.isRunning = true; document.getElementById("interactive-tutorial-container").style.display = "block"; - iTutorialEvaluateStep(); - //Exit tutorial button var exitButton = clearEventListeners("interactive-tutorial-exit"); exitButton.addEventListener("click", function() { @@ -59,142 +79,150 @@ function iTutorialStart() { //Back button var backButton = clearEventListeners("interactive-tutorial-back"); - backButton.style.display = "none"; backButton.addEventListener("click", function() { iTutorialPrevStep(); return false; }); + + //Next button + var nextButton = clearEventListeners("interactive-tutorial-next"); + nextButton.addEventListener("click", function() { + iTutorialNextStep(); + return false; + }); + + iTutorialEvaluateStep(); } function iTutorialEvaluateStep() { - if (!iTutorialIsRunning) {console.log("Interactive Tutorial not running"); return;} - switch(currITutorialStep) { + if (!ITutorial.isRunning) {console.log("Interactive Tutorial not running"); return;} + + //Disable and clear main menu + var terminalMainMenu = clearEventListeners("terminal-menu-link"); + var statsMainMenu = clearEventListeners("stats-menu-link"); + var activeScriptsMainMenu = clearEventListeners("active-scripts-menu-link"); + var hacknetMainMenu = clearEventListeners("hacknet-nodes-menu-link"); + var cityMainMenu = clearEventListeners("city-menu-link"); + var tutorialMainMenu = clearEventListeners("tutorial-menu-link"); + terminalMainMenu.removeAttribute("class"); + statsMainMenu.removeAttribute("class"); + activeScriptsMainMenu.removeAttribute("class"); + hacknetMainMenu.removeAttribute("class"); + cityMainMenu.removeAttribute("class"); + tutorialMainMenu.removeAttribute("class"); + + //Interactive Tutorial Next button + var nextBtn = document.getElementById("interactive-tutorial-next"); + + switch(ITutorial.currStep) { case iTutorialSteps.Start: Engine.loadTerminalContent(); - iTutorialSetText("Welcome to Bitburner, a cyberpunk-themed incremental RPG! " + "The game takes place in a dark, dystopian future...The year is 2077...

" + "This tutorial will show you the basics of the game. " + "You may skip the tutorial at any time."); - var next = clearEventListeners("interactive-tutorial-next"); - next.style.display = "inline-block"; - next.addEventListener("click", function() { - iTutorialNextStep(); - return false; - }); + nextBtn.style.display = "inline-block"; break; case iTutorialSteps.GoToCharacterPage: + Engine.loadTerminalContent(); iTutorialSetText("Let's start by heading to the Stats page. Click the 'Stats' tab on " + "the main navigation menu (left-hand side of the screen)"); + nextBtn.style.display = "none"; - //No next button - var next = clearEventListeners("interactive-tutorial-next"); - next.style.display = "none"; - - //Flash Character tab - document.getElementById("stats-menu-link").setAttribute("class", "flashing-button"); - - //Initialize everything necessary to open the "Character" page - var charaterMainMenuButton = document.getElementById("stats-menu-link"); - charaterMainMenuButton.addEventListener("click", function() { + //Flash 'Stats' menu and set its tutorial click handler + statsMainMenu.setAttribute("class", "flashing-button"); + statsMainMenu.addEventListener("click", function() { Engine.loadCharacterContent(); iTutorialNextStep(); //Opening the character page will go to the next step - clearEventListeners("stats-menu-link"); return false; }); break; case iTutorialSteps.CharacterPage: + Engine.loadCharacterContent(); iTutorialSetText("The Stats page shows a lot of important information about your progress, " + "such as your skills, money, and bonuses/multipliers. ") - var next = clearEventListeners("interactive-tutorial-next"); - next.style.display = "inline-block"; - next.addEventListener("click", function() { - iTutorialNextStep(); - return false; - }); + nextBtn.style.display = "inline-block"; break; case iTutorialSteps.CharacterGoToTerminalPage: + Engine.loadCharacterContent(); iTutorialSetText("Let's head to your computer's terminal by clicking the 'Terminal' tab on the " + "main navigation menu."); - //No next button - var next = clearEventListeners("interactive-tutorial-next"); - next.style.display = "none"; + nextBtn.style.display = "none"; - document.getElementById("terminal-menu-link").setAttribute("class", "flashing-button"); - - //Initialize everything necessary to open the 'Terminal' Page - var terminalMainMenuButton = document.getElementById("terminal-menu-link"); - terminalMainMenuButton.addEventListener("click", function() { + //Flash 'Terminal' menu and set its tutorial click handler + terminalMainMenu.setAttribute("class", "flashing-button"); + terminalMainMenu.addEventListener("click", function() { Engine.loadTerminalContent(); iTutorialNextStep(); - clearEventListeners("terminal-menu-link"); return false; }); break; case iTutorialSteps.TerminalIntro: + Engine.loadTerminalContent(); iTutorialSetText("The Terminal is used to interface with your home computer as well as " + "all of the other machines around the world."); - var next = clearEventListeners("interactive-tutorial-next"); - next.style.display = "inline-block"; - next.addEventListener("click", function() { - iTutorialNextStep(); - return false; - }); + nextBtn.style.display = "inline-block"; break; case iTutorialSteps.TerminalHelp: + Engine.loadTerminalContent(); iTutorialSetText("Let's try it out. Start by entering the 'help' command into the Terminal " + "(Don't forget to press Enter after typing the command)"); - var next = clearEventListeners("interactive-tutorial-next"); - next.style.display = "none"; - //next step triggered by terminal command + nextBtn.style.display = "none"; //next step triggered by terminal command break; case iTutorialSteps.TerminalLs: + Engine.loadTerminalContent(); iTutorialSetText("The 'help' command displays a list of all available Terminal commands, how to use them, " + "and a description of what they do.

Let's try another command. Enter the 'ls' command"); - //next step triggered by terminal command + nextBtn.style.display = "none"; //next step triggered by terminal command break; case iTutorialSteps.TerminalScan: + Engine.loadTerminalContent(); iTutorialSetText("'ls' is a basic command that shows all of the contents (programs/scripts) " + "on the computer. Right now, it shows that you have a program called 'NUKE.exe' on your computer. " + "We'll get to what this does later.

Using your home computer's terminal, you can connect " + "to other machines throughout the world. Let's do that now by first entering " + - "the 'scan' command. "); - //next step triggered by terminal command + "the 'scan' command."); + nextBtn.style.display = "none"; //next step triggered by terminal command break; case iTutorialSteps.TerminalScanAnalyze1: + Engine.loadTerminalContent(); iTutorialSetText("The 'scan' command shows all available network connections. In other words, " + "it displays a list of all servers that can be connected to from your " + "current machine. A server is identified by either its IP or its hostname.

" + "That's great and all, but there's so many servers. Which one should you go to? " + "The 'scan-analyze' command gives some more detailed information about servers on the " + "network. Try it now"); - //next step triggered by terminal command + nextBtn.style.display = "none"; //next step triggered by terminal command break; case iTutorialSteps.TerminalScanAnalyze2: + Engine.loadTerminalContent(); iTutorialSetText("You just ran 'scan-analyze' with a depth of one. This command shows more detailed " + "information about each server that you can connect to (servers that are a distance of " + "one node away).

It is also possible to run 'scan-analyze' with " + "a higher depth. Let's try a depth of two with the following command: 'scan-analyze 2'.") - //next step triggered by terminal command + nextBtn.style.display = "none"; //next step triggered by terminal command break; case iTutorialSteps.TerminalConnect: + Engine.loadTerminalContent(); iTutorialSetText("Now you can see information about all servers that are up to two nodes away, as well " + "as figure out how to navigate to those servers through the network. You can only connect to " + "a server that is one node away. To connect to a machine, use the 'connect [ip/hostname]' command. You can type in " + "the ip or the hostname, but dont use both.

" + "From the results of the 'scan-analyze' command, we can see that the 'foodnstuff' server is " + "only one node away. Let's connect so it now using: 'connect foodnstuff'"); - //next step triggered by terminal command + nextBtn.style.display = "none"; //next step triggered by terminal command break; case iTutorialSteps.TerminalAnalyze: + Engine.loadTerminalContent(); iTutorialSetText("You are now connected to another machine! What can you do now? You can hack it!

In the year 2077, currency has " + "become digital and decentralized. People and corporations store their money " + "on servers and computers. Using your hacking abilities, you can hack servers " + "to steal money and gain experience.

" + "Before you try to hack a server, you should run diagnostics using the 'analyze' command"); - //next step triggered by terminal command + nextBtn.style.display = "none"; //next step triggered by terminal command break; case iTutorialSteps.TerminalNuke: + Engine.loadTerminalContent(); iTutorialSetText("When the 'analyze' command finishes running it will show useful information " + "about hacking the server.

For this server, the required hacking skill is only 1, " + "which means you can hack it right now. However, in order to hack a server " + @@ -203,14 +231,16 @@ function iTutorialEvaluateStep() { "open ports.

The 'analyze' results shows that there do not need to be any open ports " + "on this machine for the NUKE virus to work, so go ahead and run the virus using the " + "'run NUKE.exe' command."); - //next step triggered by terminal command + nextBtn.style.display = "none"; //next step triggered by terminal command break; case iTutorialSteps.TerminalManualHack: + Engine.loadTerminalContent(); iTutorialSetText("You now have root access! You can hack the server using the 'hack' command. " + "Try doing that now."); - //next step triggered by terminal command + nextBtn.style.display = "none"; //next step triggered by terminal command break; case iTutorialSteps.TerminalHackingMechanics: + Engine.loadTerminalContent(); iTutorialSetText("You are now attempting to hack the server. Note that performing a hack takes time and " + "only has a certain percentage chance " + "of success. This time and success chance is determined by a variety of factors, including " + @@ -220,30 +250,25 @@ function iTutorialEvaluateStep() { "the server's security level.

The amount of money on a server is not limitless. So, if " + "you constantly hack a server and deplete its money, then you will encounter " + "diminishing returns in your hacking."); - var next = clearEventListeners("interactive-tutorial-next"); - next.style.display = "inline-block"; - next.addEventListener("click", function() { - iTutorialNextStep(); - return false; - }); + nextBtn.style.display = "inline-block"; break; case iTutorialSteps.TerminalCreateScript: + Engine.loadTerminalContent(); iTutorialSetText("Hacking is the core mechanic of the game and is necessary for progressing. However, " + "you don't want to be hacking manually the entire time. You can automate your hacking " + "by writing scripts!

To create a new script or edit an existing one, you can use the 'nano' " + "command. Scripts must end with the '.script' extension. Let's make a script now by " + "entering 'nano foodnstuff.script' after the hack command finishes running (Sidenote: Pressing ctrl + c" + " will end a command like hack early)"); - var next = clearEventListeners("interactive-tutorial-next"); - next.style.display = "none"; - //next step triggered by terminal command + nextBtn.style.display = "none"; //next step triggered by terminal command break; case iTutorialSteps.TerminalTypeScript: + Engine.loadScriptEditorContent("foodnstuff.script", ""); iTutorialSetText("This is the script editor. You can use it to program your scripts. Scripts are " + "written in the Netscript language, a programming language created for " + "this game. There are details about the Netscript language in the documentation, which " + "can be accessed in the 'Tutorial' tab on the main navigation menu. I highly suggest you check " + - "it out after this tutorial. For now, just copy " + + "it out after this tutorial. For now, just copy " + "and paste the following code into the script editor:

" + "while(true) {
" + "  hack('foodnstuff');
" + @@ -251,21 +276,24 @@ function iTutorialEvaluateStep() { "For anyone with basic programming experience, this code should be straightforward. " + "This script will continuously hack the 'foodnstuff' server.

" + "To save and close the script editor, press the button in the bottom left, or press ctrl + b."); - //next step triggered in saveAndCloseScriptEditor() (Script.js) + nextBtn.style.display = "none"; //next step triggered in saveAndCloseScriptEditor() (Script.js) break; case iTutorialSteps.TerminalFree: + Engine.loadTerminalContent(); iTutorialSetText("Now we'll run the script. Scripts require a certain amount of RAM to run, and can be " + "run on any machine which you have root access to. Different servers have different " + "amounts of RAM. You can also purchase more RAM for your home server.

To check how much " + "RAM is available on this machine, enter the 'free' command."); - //next step triggered by terminal commmand + nextBtn.style.display = "none"; //next step triggered by terminal commmand break; case iTutorialSteps.TerminalRunScript: + Engine.loadTerminalContent(); iTutorialSetText("We have 16GB of free RAM on this machine, which is enough to run our " + "script. Let's run our script using 'run foodnstuff.script'."); - //next step triggered by terminal commmand + nextBtn.style.display = "none"; //next step triggered by terminal commmand break; case iTutorialSteps.TerminalGoToActiveScriptsPage: + Engine.loadTerminalContent(); iTutorialSetText("Your script is now running! The script might take a few seconds to 'fully start up'. " + "Your scripts will continuously run in the background and will automatically stop if " + "the code ever completes (the 'foodnstuff.script' will never complete because it " + @@ -274,117 +302,114 @@ function iTutorialEvaluateStep() { "much slower rate.

" + "Let's check out some statistics for our running scripts by clicking the " + "'Active Scripts' link in the main navigation menu."); - document.getElementById("active-scripts-menu-link").setAttribute("class", "flashing-button"); - var activeScriptsMainMenuButton = document.getElementById("active-scripts-menu-link"); - activeScriptsMainMenuButton.addEventListener("click", function() { + nextBtn.style.display = "none"; + + //Flash 'Active Scripts' menu and set its tutorial click handler + activeScriptsMainMenu.setAttribute("class", "flashing-button"); + activeScriptsMainMenu.addEventListener("click", function() { Engine.loadActiveScriptsContent(); iTutorialNextStep(); - clearEventListeners("active-scripts-menu-link"); return false; }); break; case iTutorialSteps.ActiveScriptsPage: + Engine.loadActiveScriptsContent(); iTutorialSetText("This page displays stats/information about all of your scripts that are " + "running across every existing server. You can use this to gauge how well " + "your scripts are doing. Let's go back to the Terminal now using the 'Terminal'" + "link."); - document.getElementById("terminal-menu-link").setAttribute("class", "flashing-button"); - //Initialize everything necessary to open the 'Terminal' Page - var terminalMainMenuButton = clearEventListeners("terminal-menu-link"); - terminalMainMenuButton.addEventListener("click", function() { + nextBtn.style.display = "none"; + + //Flash 'Terminal' button and set its tutorial click handler + terminalMainMenu.setAttribute("class", "flashing-button"); + terminalMainMenu.addEventListener("click", function() { Engine.loadTerminalContent(); iTutorialNextStep(); - clearEventListeners("terminal-menu-link"); return false; }); break; case iTutorialSteps.ActiveScriptsToTerminal: + Engine.loadTerminalContent(); iTutorialSetText("One last thing about scripts, each active script contains logs that detail " + "what it's doing. We can check these logs using the 'tail' command. Do that " + "now for the script we just ran by typing 'tail foodnstuff.script'"); - //next step triggered by terminal command + nextBtn.style.display = "none"; //next step triggered by terminal command break; case iTutorialSteps.TerminalTailScript: + Engine.loadTerminalContent(); iTutorialSetText("The log for this script won't show much right now (it might show nothing at all) because it " + "just started running...but check back again in a few minutes!

" + "This pretty much covers the basics of hacking. To learn more about writing " + "scripts using the Netscript language, select the 'Tutorial' link in the " + - "main navigation menu to look at the documentation. For now, let's move on " + - "to something else!"); - var next = clearEventListeners("interactive-tutorial-next"); - next.style.display = "inline-block"; - next.addEventListener("click", function() { - iTutorialNextStep(); - return false; - }); + "main navigation menu to look at the documentation. " + + "If you are an experienced JavaScript " + + "developer, I would highly suggest you check out the section on " + + "NetscriptJS/Netscript 2.0.

For now, let's move on to something else!"); + nextBtn.style.display = "inline-block"; break; case iTutorialSteps.GoToHacknetNodesPage: + Engine.loadTerminalContent(); iTutorialSetText("Hacking is not the only way to earn money. One other way to passively " + "earn money is by purchasing and upgrading Hacknet Nodes. Let's go to " + "the 'Hacknet Nodes' page through the main navigation menu now."); - document.getElementById("hacknet-nodes-menu-link").setAttribute("class", "flashing-button"); - var hacknetNodesButton = clearEventListeners("hacknet-nodes-menu-link"); - var next = clearEventListeners("interactive-tutorial-next"); - next.style.display = "none"; - hacknetNodesButton.addEventListener("click", function() { + nextBtn.style.display = "none"; + + //Flash 'Hacknet' menu and set its tutorial click handler + hacknetMainMenu.setAttribute("class", "flashing-button"); + hacknetMainMenu.addEventListener("click", function() { Engine.loadHacknetNodesContent(); iTutorialNextStep(); - clearEventListeners("hacknet-nodes-menu-link"); return false; }); break; case iTutorialSteps.HacknetNodesIntroduction: + Engine.loadHacknetNodesContent(); iTutorialSetText("From this page you can purchase new Hacknet Nodes and upgrade your " + "existing ones. Let's purchase a new one now."); - //Next step triggered by purchaseHacknet() (HacknetNode.js) + nextBtn.style.display = "none"; //Next step triggered by purchaseHacknet() (HacknetNode.js) break; case iTutorialSteps.HacknetNodesGoToWorldPage: + Engine.loadHacknetNodesContent(); iTutorialSetText("You just purchased a Hacknet Node! This Hacknet Node will passively " + "earn you money over time, both online and offline. When you get enough " + " money, you can upgrade " + "your newly-purchased Hacknet Node below.

" + "Let's go to the 'City' page through the main navigation menu."); - document.getElementById("city-menu-link").setAttribute("class", "flashing-button"); - var worldButton = clearEventListeners("city-menu-link"); - worldButton.addEventListener("click", function() { + nextBtn.style.display = "none"; + + //Flash 'City' menu and set its tutorial click handler + cityMainMenu.setAttribute("class", "flashing-button"); + cityMainMenu.addEventListener("click", function() { Engine.loadWorldContent(); iTutorialNextStep(); - clearEventListeners("city-menu-link"); return false; }); break; case iTutorialSteps.WorldDescription: + Engine.loadWorldContent(); iTutorialSetText("This page lists all of the different locations you can currently " + "travel to. Each location has something that you can do. " + "There's a lot of content out in the world, make sure " + "you explore and discover!

" + "Lastly, click on the 'Tutorial' link in the main navigation menu."); - document.getElementById("tutorial-menu-link").setAttribute("class", "flashing-button"); - var tutorialButton = clearEventListeners("tutorial-menu-link"); - tutorialButton.addEventListener("click", function() { + nextBtn.style.display = "none"; + + //Flash 'Tutorial' menu and set its tutorial click handler + tutorialMainMenu.setAttribute("class", "flashing-button"); + tutorialMainMenu.addEventListener("click", function() { Engine.loadTutorialContent(); iTutorialNextStep(); - clearEventListeners("tutorial-menu-link"); return false; }); break; - case iTutorialSteps.TutorialPageInfo: + Engine.loadTutorialContent(); iTutorialSetText("This page contains a lot of different documentation about the game's " + "content and mechanics. I know it's a lot, but I highly suggest you read " + "(or at least skim) through this before you start playing. That's the end of the tutorial. " + "Hope you enjoy the game!"); - var next = clearEventListeners("interactive-tutorial-next"); - next.style.display = "inline-block"; - next.innerHTML = "Finish Tutorial"; - - var backButton = clearEventListeners("interactive-tutorial-back"); - backButton.style.display = "none"; - - next.addEventListener("click", function() { - iTutorialNextStep(); - return false; - }); + nextBtn.style.display = "inline-block"; + nextBtn.innerHTML = "Finish Tutorial"; break; case iTutorialSteps.End: iTutorialEnd(); @@ -392,264 +417,85 @@ function iTutorialEvaluateStep() { default: throw new Error("Invalid tutorial step"); } + + if (ITutorial.stepIsDone[ITutorial.currStep] === true) { + nextBtn.style.display = "inline-block"; + } } //Go to the next step and evaluate it function iTutorialNextStep() { - switch(currITutorialStep) { - case iTutorialSteps.Start: - currITutorialStep = iTutorialSteps.GoToCharacterPage; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.GoToCharacterPage: + //Special behavior for certain steps + if (ITutorial.currStep === iTutorialSteps.GoToCharacterPage) { document.getElementById("stats-menu-link").removeAttribute("class"); - currITutorialStep = iTutorialSteps.CharacterPage; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.CharacterPage: - currITutorialStep = iTutorialSteps.CharacterGoToTerminalPage; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.CharacterGoToTerminalPage: - document.getElementById("terminal-menu-link").removeAttribute("class"); - currITutorialStep = iTutorialSteps.TerminalIntro; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalIntro: - currITutorialStep = iTutorialSteps.TerminalHelp; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalHelp: - currITutorialStep = iTutorialSteps.TerminalLs; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalLs: - currITutorialStep = iTutorialSteps.TerminalScan; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalScan: - currITutorialStep = iTutorialSteps.TerminalScanAnalyze1; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalScanAnalyze1: - currITutorialStep = iTutorialSteps.TerminalScanAnalyze2; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalScanAnalyze2: - currITutorialStep = iTutorialSteps.TerminalConnect; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalConnect: - currITutorialStep = iTutorialSteps.TerminalAnalyze; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalAnalyze: - currITutorialStep = iTutorialSteps.TerminalNuke; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalNuke: - currITutorialStep = iTutorialSteps.TerminalManualHack; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalManualHack: - currITutorialStep = iTutorialSteps.TerminalHackingMechanics; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalHackingMechanics: - currITutorialStep = iTutorialSteps.TerminalCreateScript; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalCreateScript: - currITutorialStep = iTutorialSteps.TerminalTypeScript; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalTypeScript: - currITutorialStep = iTutorialSteps.TerminalFree; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalFree: - currITutorialStep = iTutorialSteps.TerminalRunScript; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalRunScript: - currITutorialStep = iTutorialSteps.TerminalGoToActiveScriptsPage; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalGoToActiveScriptsPage: - document.getElementById("active-scripts-menu-link").removeAttribute("class"); - currITutorialStep = iTutorialSteps.ActiveScriptsPage; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.ActiveScriptsPage: - document.getElementById("terminal-menu-link").removeAttribute("class"); - currITutorialStep = iTutorialSteps.ActiveScriptsToTerminal; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.ActiveScriptsToTerminal: - currITutorialStep = iTutorialSteps.TerminalTailScript; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalTailScript: - currITutorialStep = iTutorialSteps.GoToHacknetNodesPage; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.GoToHacknetNodesPage: - document.getElementById("hacknet-nodes-menu-link").removeAttribute("class"); - currITutorialStep = iTutorialSteps.HacknetNodesIntroduction; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.HacknetNodesIntroduction: - currITutorialStep = iTutorialSteps.HacknetNodesGoToWorldPage; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.HacknetNodesGoToWorldPage: - document.getElementById("city-menu-link").removeAttribute("class"); - currITutorialStep = iTutorialSteps.WorldDescription; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.WorldDescription: - document.getElementById("tutorial-menu-link").removeAttribute("class"); - currITutorialStep = iTutorialSteps.TutorialPageInfo; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TutorialPageInfo: - currITutorialStep = iTutorialSteps.End; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.End: - break; - default: - throw new Error("Invalid tutorial step"); } + if (ITutorial.currStep === iTutorialSteps.CharacterGoToTerminalPage) { + document.getElementById("terminal-menu-link").removeAttribute("class"); + } + if (ITutorial.currStep === iTutorialSteps.TerminalGoToActiveScriptsPage) { + document.getElementById("active-scripts-menu-link").removeAttribute("class"); + } + if (ITutorial.currStep === iTutorialSteps.ActiveScriptsPage) { + document.getElementById("terminal-menu-link").removeAttribute("class"); + } + if (ITutorial.currStep === iTutorialSteps.GoToHacknetNodesPage) { + document.getElementById("hacknet-nodes-menu-link").removeAttribute("class"); + } + if (ITutorial.currStep === iTutorialSteps.HacknetNodesGoToWorldPage) { + document.getElementById("city-menu-link").removeAttribute("class"); + } + if (ITutorial.currStep === iTutorialSteps.WorldDescription) { + document.getElementById("tutorial-menu-link").removeAttribute("class"); + } + + ITutorial.stepIsDone[ITutorial.currStep] = true; + if (ITutorial.currStep < iTutorialSteps.End) { + ITutorial.currStep += 1; + } + iTutorialEvaluateStep(); } //Go to previous step and evaluate function iTutorialPrevStep() { - switch(currITutorialStep) { - case iTutorialSteps.Start: - currITutorialStep = iTutorialSteps.Start; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.GoToCharacterPage: - currITutorialStep = iTutorialSteps.Start; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.CharacterPage: - currITutorialStep = iTutorialSteps.GoToCharacterPage; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.CharacterGoToTerminalPage: - currITutorialStep = iTutorialSteps.CharacterPage; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalIntro: - currITutorialStep = iTutorialSteps.CharacterGoToTerminalPage; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalHelp: - currITutorialStep = iTutorialSteps.TerminalIntro; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalLs: - currITutorialStep = iTutorialSteps.TerminalHelp; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalScan: - currITutorialStep = iTutorialSteps.TerminalLs; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalConnect: - currITutorialStep = iTutorialSteps.TerminalScan; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalAnalyze: - currITutorialStep = iTutorialSteps.TerminalConnect; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalNuke: - currITutorialStep = iTutorialSteps.TerminalAnalyze; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalManualHack: - currITutorialStep = iTutorialSteps.TerminalNuke; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalHackingMechanics: - currITutorialStep = iTutorialSteps.TerminalManualHack; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalCreateScript: - currITutorialStep = iTutorialSteps.TerminalManualHack; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalTypeScript: - currITutorialStep = iTutorialSteps.TerminalCreateScript; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalFree: - currITutorialStep = iTutorialSteps.TerminalTypeScript; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalRunScript: - currITutorialStep = iTutorialSteps.TerminalFree; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalGoToActiveScriptsPage: - currITutorialStep = iTutorialSteps.TerminalRunScript; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.ActiveScriptsPage: - currITutorialStep = iTutorialSteps.TerminalGoToActiveScriptsPage; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.ActiveScriptsToTerminal: - currITutorialStep = iTutorialSteps.ActiveScriptsPage; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TerminalTailScript: - currITutorialStep = iTutorialSteps.ActiveScriptsToTerminal; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.GoToHacknetNodesPage: - currITutorialStep = iTutorialSteps.TerminalTailScript; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.HacknetNodesIntroduction: - currITutorialStep = iTutorialSteps.GoToHacknetNodesPage; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.HacknetNodesGoToWorldPage: - currITutorialStep = iTutorialSteps.HacknetNodesIntroduction; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.WorldDescription: - currITutorialStep = iTutorialSteps.HacknetNodesGoToWorldPage; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.TutorialPageInfo: - currITutorialStep = iTutorialSteps.WorldDescription; - iTutorialEvaluateStep(); - break; - case iTutorialSteps.End: - break; - default: - throw new Error("Invalid tutorial step"); + if (ITutorial.currStep > iTutorialSteps.Start) { + ITutorial.currStep -= 1; } + iTutorialEvaluateStep(); } function iTutorialEnd() { //Re-enable auto save - Engine.Counters.autoSaveCounter = 300; + if (Settings.AutosaveInterval === 0) { + Engine.Counters.autoSaveCounter = Infinity; + } else { + Engine.Counters.autoSaveCounter = Settings.AutosaveInterval * 5; + } + console.log("Ending interactive tutorial"); Engine.init(); - currITutorialStep = iTutorialSteps.End; - iTutorialIsRunning = false; + ITutorial.currStep = iTutorialSteps.End; + ITutorial.isRunning = false; document.getElementById("interactive-tutorial-container").style.display = "none"; - dialogBoxCreate("If you are new to the game, the following links may be useful for you!

" + - "Getting Started Guide" + - "Wiki

" + - "The Beginner's Guide to Hacking was added to your home computer! It contains some tips/pointers for starting out with the game. " + - "To read it, go to Terminal and enter

cat hackers-starting-handbook.lit"); + + //Create a popup with final introductory stuff + var popupId = "interactive-tutorial-ending-popup"; + var txt = createElement("p", { + innerHTML: + "If you are new to the game, the following links may be useful for you!

" + + "Getting Started Guide" + + "Wiki" + + "Documentation

" + + "The Beginner's Guide to Hacking was added to your home computer! It contains some tips/pointers for starting out with the game. " + + "To read it, go to Terminal and enter

cat hackers-starting-handbook.lit" + }); + var gotitBtn = createElement("a", { + class:"a-link-button", float:"right", padding:"6px", innerText:"Got it!", + clickListener:()=>{ + removeElementById(popupId); + } + }); + createPopup(popupId, [txt, gotitBtn]); + Player.getHomeComputer().messages.push("hackers-starting-handbook.lit"); } @@ -660,5 +506,4 @@ function iTutorialSetText(txt) { textBox.parentElement.scrollTop = 0; // this resets scroll position } -export {iTutorialSteps, iTutorialEnd, iTutorialStart, iTutorialNextStep, currITutorialStep, - iTutorialIsRunning}; +export {iTutorialSteps, iTutorialEnd, iTutorialStart, iTutorialNextStep, ITutorial}; diff --git a/src/Literature.js b/src/Literature.js index bb80e6a70..c38c2e1b8 100644 --- a/src/Literature.js +++ b/src/Literature.js @@ -23,7 +23,11 @@ function initLiterature() { var title, fn, txt; title = "The Beginner's Guide to Hacking"; fn = "hackers-starting-handbook.lit"; - txt = "When starting out, hacking is the most profitable way to earn money and progress. This " + + txt = "Some resources:

" + + "Learn to Program

" + + "For Experienced JavaScript Developers: NetscriptJS

" + + "Netscript Documentation

" + + "When starting out, hacking is the most profitable way to earn money and progress. This " + "is a brief collection of tips/pointers on how to make the most out of your hacking scripts.

" + "-hack() and grow() both work by percentages. hack() steals a certain percentage of the " + "money on a server, and grow() increases the amount of money on a server by some percentage (multiplicatively)

" + diff --git a/src/Location.js b/src/Location.js index f7cb96f31..4abaee68f 100644 --- a/src/Location.js +++ b/src/Location.js @@ -1001,7 +1001,7 @@ function displayLocationContent() { slumsDealDrugs.innerHTML = "Deal Drugs (" + (drugsChance*100).toFixed(3) + "% chance of success)"; slumsDealDrugs.innerHTML += ' Attempt to deal drugs '; slumsBondForgery.style.display = "block"; - slumsBondForgery.innerHTML = "Bond Forgery(" + (bondChance*100).toFixed(3) + "% chance of success)"; + slumsBondForgery.innerHTML = "Bond Forgery (" + (bondChance*100).toFixed(3) + "% chance of success)"; slumsBondForgery.innerHTML += " Attempt to forge corporate bonds"; slumsTrafficArms.style.display = "block"; slumsTrafficArms.innerHTML = "Traffick Illegal Arms (" + (armsChance*100).toFixed(3) + "% chance of success)"; diff --git a/src/NetscriptEvaluator.js b/src/NetscriptEvaluator.js index a6c6668c7..d1989f01a 100644 --- a/src/NetscriptEvaluator.js +++ b/src/NetscriptEvaluator.js @@ -911,61 +911,5 @@ function isScriptErrorMessage(msg) { return true; } -//The same as Player's calculateHackingChance() function but takes in the server as an argument -function scriptCalculateHackingChance(server) { - var difficultyMult = (100 - server.hackDifficulty) / 100; - var skillMult = (1.75 * Player.hacking_skill) + (0.2 * Player.intelligence); - var skillChance = (skillMult - server.requiredHackingSkill) / skillMult; - var chance = skillChance * difficultyMult * Player.hacking_chance_mult; - if (chance > 1) {return 1;} - if (chance < 0) {return 0;} - else {return chance;} -} - -//The same as Player's calculateHackingTime() function but takes in the server as an argument -function scriptCalculateHackingTime(server) { - var difficultyMult = server.requiredHackingSkill * server.hackDifficulty; - var skillFactor = (2.5 * difficultyMult + 500) / (Player.hacking_skill + 50 + (0.1 * Player.intelligence)); - var hackingTime = 5 * skillFactor / Player.hacking_speed_mult; //This is in seconds - return hackingTime; -} - -//The same as Player's calculateExpGain() function but takes in the server as an argument -function scriptCalculateExpGain(server) { - if (server.baseDifficulty == null) { - server.baseDifficulty = server.hackDifficulty; - } - return (server.baseDifficulty * Player.hacking_exp_mult * 0.3 + 3) * BitNodeMultipliers.HackExpGain; -} - -//The same as Player's calculatePercentMoneyHacked() function but takes in the server as an argument -function scriptCalculatePercentMoneyHacked(server) { - var difficultyMult = (100 - server.hackDifficulty) / 100; - var skillMult = (Player.hacking_skill - (server.requiredHackingSkill - 1)) / Player.hacking_skill; - var percentMoneyHacked = difficultyMult * skillMult * Player.hacking_money_mult / 240; - if (percentMoneyHacked < 0) {return 0;} - if (percentMoneyHacked > 1) {return 1;} - return percentMoneyHacked * BitNodeMultipliers.ScriptHackMoney; -} - -//Amount of time to execute grow() in milliseconds -function scriptCalculateGrowTime(server) { - var difficultyMult = server.requiredHackingSkill * server.hackDifficulty; - var skillFactor = (2.5 * difficultyMult + 500) / (Player.hacking_skill + 50 + (0.1 * Player.intelligence)); - var growTime = 16 * skillFactor / Player.hacking_speed_mult; //This is in seconds - return growTime * 1000; -} - -//Amount of time to execute weaken() in milliseconds -function scriptCalculateWeakenTime(server) { - var difficultyMult = server.requiredHackingSkill * server.hackDifficulty; - var skillFactor = (2.5 * difficultyMult + 500) / (Player.hacking_skill + 50 + (0.1 * Player.intelligence)); - var weakenTime = 20 * skillFactor / Player.hacking_speed_mult; //This is in seconds - return weakenTime * 1000; -} - -export {makeRuntimeRejectMsg, netscriptDelay, runScriptFromScript, - scriptCalculateHackingChance, scriptCalculateHackingTime, - scriptCalculateExpGain, scriptCalculatePercentMoneyHacked, - scriptCalculateGrowTime, scriptCalculateWeakenTime, evaluate, +export {makeRuntimeRejectMsg, netscriptDelay, runScriptFromScript, evaluate, isScriptErrorMessage, killNetscriptDelay, evaluateImport}; diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 7b7a9ba5a..7a0d2fb3a 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -13,6 +13,12 @@ import {Companies, Company, CompanyPosition, import {CONSTANTS} from "./Constants"; import {Programs} from "./CreateProgram"; import {DarkWebItems} from "./DarkWeb"; +import {calculateHackingChance, + calculateHackingExpGain, + calculatePercentMoneyHacked, + calculateHackingTime, + calculateGrowTime, + calculateWeakenTime} from "./Hacking"; import {AllGangs} from "./Gang"; import {Factions, Faction, joinFaction, factionExists, purchaseAugmentation} from "./Faction"; @@ -28,11 +34,12 @@ import {Server, getServer, AddToAllServers, GetServerByHostname} from "./Server"; import {Settings} from "./Settings"; import {SpecialServerIps} from "./SpecialServerIps"; +import {Stock} from "./Stock"; import {StockMarket, StockSymbols, SymbolToStockMap, initStockSymbols, initStockMarket, initSymbolToStockMap, stockMarketCycle, buyStock, sellStock, updateStockPrices, displayStockMarketContent, updateStockTicker, updateStockPlayerPosition, - Stock, shortStock, sellShort, OrderTypes, + shortStock, sellShort, OrderTypes, PositionTypes, placeOrder, cancelOrder} from "./StockMarket"; import {post} from "./ui/postToTerminal"; import {TextFile, getTextFile, createTextFile} from "./TextFile"; @@ -42,10 +49,8 @@ import {unknownBladeburnerActionErrorMessage, checkBladeburnerAccess} from "./NetscriptBladeburner.js"; import {WorkerScript, workerScripts, killWorkerScript, NetscriptPorts} from "./NetscriptWorker"; -import {makeRuntimeRejectMsg, netscriptDelay, runScriptFromScript, - scriptCalculateHackingChance, scriptCalculateHackingTime, - scriptCalculateExpGain, scriptCalculatePercentMoneyHacked, - scriptCalculateGrowTime, scriptCalculateWeakenTime} from "./NetscriptEvaluator"; +import {makeRuntimeRejectMsg, netscriptDelay, + runScriptFromScript} from "./NetscriptEvaluator"; import {NetscriptPort} from "./NetscriptPort"; import Decimal from "decimal.js"; @@ -292,7 +297,7 @@ function NetscriptFunctions(workerScript) { } //Calculate the hacking time - var hackingTime = scriptCalculateHackingTime(server); //This is in seconds + var hackingTime = calculateHackingTime(server); //This is in seconds //No root access or skill level too low if (server.hasAdminRights == false) { @@ -308,14 +313,14 @@ function NetscriptFunctions(workerScript) { if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.hack == null) { workerScript.scriptRef.log("Attempting to hack " + ip + " in " + hackingTime.toFixed(3) + " seconds (t=" + threads + ")"); } - return netscriptDelay(hackingTime* 1000, workerScript).then(function() { + return netscriptDelay(hackingTime * 1000, workerScript).then(function() { if (workerScript.env.stopFlag) {return Promise.reject(workerScript);} - var hackChance = scriptCalculateHackingChance(server); + var hackChance = calculateHackingChance(server); var rand = Math.random(); - var expGainedOnSuccess = scriptCalculateExpGain(server) * threads; + var expGainedOnSuccess = calculateHackingExpGain(server) * threads; var expGainedOnFailure = (expGainedOnSuccess / 4); if (rand < hackChance) { //Success! - const percentHacked = scriptCalculatePercentMoneyHacked(server); + const percentHacked = calculatePercentMoneyHacked(server); let maxThreadNeeded = Math.ceil(1/percentHacked*(server.moneyAvailable/server.moneyMax)); if (isNaN(maxThreadNeeded)) { //Server has a 'max money' of 0 (probably). @@ -390,18 +395,18 @@ function NetscriptFunctions(workerScript) { throw makeRuntimeRejectMsg(workerScript, "Cannot grow this server (" + server.hostname + ") because user does not have root access"); } - var growTime = scriptCalculateGrowTime(server); + var growTime = calculateGrowTime(server); if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.grow == null) { - workerScript.scriptRef.log("Executing grow() on server " + server.hostname + " in " + formatNumber(growTime/1000, 3) + " seconds (t=" + threads + ")"); + workerScript.scriptRef.log("Executing grow() on server " + server.hostname + " in " + formatNumber(growTime, 3) + " seconds (t=" + threads + ")"); } - return netscriptDelay(growTime, workerScript).then(function() { + return netscriptDelay(growTime * 1000, workerScript).then(function() { if (workerScript.env.stopFlag) {return Promise.reject(workerScript);} const moneyBefore = server.moneyAvailable; server.moneyAvailable += (1 * threads); //It can be grown even if it has no money var growthPercentage = processSingleServerGrowth(server, 450 * threads); const moneyAfter = server.moneyAvailable; workerScript.scriptRef.recordGrow(server.ip, threads); - var expGain = scriptCalculateExpGain(server) * threads; + var expGain = calculateHackingExpGain(server) * threads; if (growthPercentage == 1) { expGain = 0; } @@ -437,16 +442,16 @@ function NetscriptFunctions(workerScript) { throw makeRuntimeRejectMsg(workerScript, "Cannot weaken this server (" + server.hostname + ") because user does not have root access"); } - var weakenTime = scriptCalculateWeakenTime(server); + var weakenTime = calculateWeakenTime(server); if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.weaken == null) { workerScript.scriptRef.log("Executing weaken() on server " + server.hostname + " in " + - formatNumber(weakenTime/1000, 3) + " seconds (t=" + threads + ")"); + formatNumber(weakenTime, 3) + " seconds (t=" + threads + ")"); } - return netscriptDelay(weakenTime, workerScript).then(function() { + return netscriptDelay(weakenTime * 1000, workerScript).then(function() { if (workerScript.env.stopFlag) {return Promise.reject(workerScript);} server.weaken(CONSTANTS.ServerWeakenAmount * threads); workerScript.scriptRef.recordWeaken(server.ip, threads); - var expGain = scriptCalculateExpGain(server) * threads; + var expGain = calculateHackingExpGain(server) * threads; if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.weaken == null) { workerScript.scriptRef.log("Server security level on " + server.hostname + " weakened to " + server.hackDifficulty + ". Gained " + formatNumber(expGain, 4) + " hacking exp (t=" + threads + ")"); @@ -1843,6 +1848,25 @@ function NetscriptFunctions(workerScript) { throw makeRuntimeRejectMsg(workerScript, "Invalid argument passed in for write: " + port); } }, + tryWrite : function(port, data="") { + if (workerScript.checkingRam) { + return updateStaticRam("tryWrite", CONSTANTS.ScriptReadWriteRamCost); + } + updateDynamicRam("tryWrite", CONSTANTS.ScriptReadWriteRamCost); + if (!isNaN(port)) { + port = Math.round(port); + if (port < 1 || port > CONSTANTS.NumNetscriptPorts) { + throw makeRuntimeRejectMsg(workerScript, "ERROR: tryWrite() called on invalid port: " + port + ". Only ports 1-" + CONSTANTS.NumNetscriptPorts + " are valid."); + } + var port = NetscriptPorts[port-1]; + if (port == null || !(port instanceof NetscriptPort)) { + throw makeRuntimeRejectMsg(workerScript, "Could not find port: " + port + ". This is a bug contact the game developer"); + } + return port.tryWrite(data); + } else { + throw makeRuntimeRejectMsg(workerScript, "Invalid argument passed in for tryWrite: " + port); + } + }, read : function(port) { if (workerScript.checkingRam) { return updateStaticRam("read", CONSTANTS.ScriptReadWriteRamCost); @@ -2057,7 +2081,7 @@ function NetscriptFunctions(workerScript) { workerScript.scriptRef.log("getHackTime() failed. Invalid IP or hostname passed in: " + ip); throw makeRuntimeRejectMsg(workerScript, "getHackTime() failed. Invalid IP or hostname passed in: " + ip); } - return scriptCalculateHackingTime(server); //Returns seconds + return calculateHackingTime(server); //Returns seconds }, getGrowTime : function(ip) { if (workerScript.checkingRam) { @@ -2069,7 +2093,7 @@ function NetscriptFunctions(workerScript) { workerScript.scriptRef.log("getGrowTime() failed. Invalid IP or hostname passed in: " + ip); throw makeRuntimeRejectMsg(workerScript, "getGrowTime() failed. Invalid IP or hostname passed in: " + ip); } - return scriptCalculateGrowTime(server) / 1000; //Returns seconds + return calculateGrowTime(server); //Returns seconds }, getWeakenTime : function(ip) { if (workerScript.checkingRam) { @@ -2081,7 +2105,7 @@ function NetscriptFunctions(workerScript) { workerScript.scriptRef.log("getWeakenTime() failed. Invalid IP or hostname passed in: " + ip); throw makeRuntimeRejectMsg(workerScript, "getWeakenTime() failed. Invalid IP or hostname passed in: " + ip); } - return scriptCalculateWeakenTime(server) / 1000; //Returns seconds + return calculateWeakenTime(server); //Returns seconds }, getScriptIncome : function(scriptname, ip) { if (workerScript.checkingRam) { diff --git a/src/Player.js b/src/Player.js index 48d4771a5..c05c9c8cb 100644 --- a/src/Player.js +++ b/src/Player.js @@ -30,6 +30,8 @@ import numeral from "numeral/min/numeral.min"; import {formatNumber, convertTimeMsToTimeElapsedString} from "../utils/StringHelperFunctions"; +const CYCLES_PER_SEC = 1000 / CONSTANTS.MilliPerCycle; + function PlayerObject() { //Skills and stats this.hacking_skill = 1; @@ -119,11 +121,6 @@ function PlayerObject() { this.crime_money_mult = 1; this.crime_success_mult = 1; - //Flag to let the engine know the player is starting an action - // Current actions: hack, analyze - this.startAction = false; - this.actionTime = 0; - //Flags/variables for working (Company, Faction, Creating Program, Taking Class) this.isWorking = false; this.workType = ""; @@ -265,9 +262,6 @@ PlayerObject.prototype.prestigeAugmentation = function() { this.queuedAugmentations = []; - this.startAction = false; - this.actionTime = 0; - this.isWorking = false; this.currentWorkFactionName = ""; this.currentWorkFactionDescription = ""; @@ -349,9 +343,6 @@ PlayerObject.prototype.prestigeSourceFile = function() { this.queuedAugmentations = []; this.augmentations = []; - this.startAction = false; - this.actionTime = 0; - this.isWorking = false; this.currentWorkFactionName = ""; this.currentWorkFactionDescription = ""; @@ -498,74 +489,10 @@ PlayerObject.prototype.resetMultipliers = function() { this.bladeburner_success_chance_mult = 1; } -//Calculates the chance of hacking a server -//The formula is: -// (2 * hacking_chance_multiplier * hacking_skill - requiredLevel) 100 - difficulty -// ----------------------------------------------------------- * ----------------- -// (2 * hacking_chance_multiplier * hacking_skill) 100 -PlayerObject.prototype.calculateHackingChance = function() { - var difficultyMult = (100 - this.getCurrentServer().hackDifficulty) / 100; - var skillMult = (1.75 * this.hacking_skill) + (0.2 * this.intelligence); - var skillChance = (skillMult - this.getCurrentServer().requiredHackingSkill) / skillMult; - var chance = skillChance * difficultyMult * this.hacking_chance_mult; - if (chance > 1) {return 1;} - if (chance < 0) {return 0;} - return chance; -} - -//Calculate the time it takes to hack a server in seconds. Returns the time -//The formula is: -// (2.5 * requiredLevel * difficulty + 200) -// ----------------------------------- * hacking_speed_multiplier -// hacking_skill + 100 -PlayerObject.prototype.calculateHackingTime = function() { - var difficultyMult = this.getCurrentServer().requiredHackingSkill * this.getCurrentServer().hackDifficulty; - var skillFactor = (2.5 * difficultyMult + 200) / (this.hacking_skill + 100 + (0.1 * this.intelligence)); - return 5 * skillFactor / this.hacking_speed_mult; -} - -//Calculates the PERCENTAGE of a server's money that the player will hack from the server if successful -//The formula is: -// (hacking_skill - (requiredLevel-1)) 100 - difficulty -// --------------------------------------* ----------------------- * hacking_money_multiplier -// hacking_skill 100 -PlayerObject.prototype.calculatePercentMoneyHacked = function() { - var difficultyMult = (100 - this.getCurrentServer().hackDifficulty) / 100; - var skillMult = (this.hacking_skill - (this.getCurrentServer().requiredHackingSkill - 1)) / this.hacking_skill; - var percentMoneyHacked = difficultyMult * skillMult * this.hacking_money_mult / 240; - console.log("Percent money hacked calculated to be: " + percentMoneyHacked); - if (percentMoneyHacked < 0) {return 0;} - if (percentMoneyHacked > 1) {return 1;} - return percentMoneyHacked * BitNodeMultipliers.ManualHackMoney; -} - -//Returns how much EXP the player gains on a successful hack -//The formula is: -// difficulty * requiredLevel * hacking_multiplier -PlayerObject.prototype.calculateExpGain = function() { - var s = this.getCurrentServer(); - if (s.baseDifficulty == null) { - s.baseDifficulty = s.hackDifficulty; - } - return (s.baseDifficulty * this.hacking_exp_mult * 0.3 + 3) * BitNodeMultipliers.HackExpGain; -} - -//Hack/Analyze a server. Return the amount of time the hack will take. This lets the Terminal object know how long to disable itself for -//This assumes that the server being hacked is not purchased by the player, that the player's hacking skill is greater than the -//required hacking skill and that the player has admin rights. -PlayerObject.prototype.hack = function() { - this.actionTime = this.calculateHackingTime(); - console.log("Hacking time: " + this.actionTime); - this.startAction = true; //Set the startAction flag so the engine starts the hacking process -} - -PlayerObject.prototype.analyze = function() { - this.actionTime = 1; - this.startAction = true; -} - PlayerObject.prototype.hasProgram = function(programName) { var home = Player.getHomeComputer(); + if (home == null) {return false;} + for (var i = 0; i < home.programs.length; ++i) { if (programName.toLowerCase() == home.programs[i].toLowerCase()) {return true;} } @@ -688,6 +615,7 @@ PlayerObject.prototype.resetWorkStatus = function() { this.workChaExpGainRate = 0; this.workRepGainRate = 0; this.workMoneyGainRate = 0; + this.workMoneyLossRate = 0; this.workHackExpGained = 0; this.workStrExpGained = 0; @@ -709,24 +637,109 @@ PlayerObject.prototype.resetWorkStatus = function() { document.getElementById("work-in-progress-text").innerHTML = ""; } -PlayerObject.prototype.gainWorkExp = function() { - this.gainHackingExp(this.workHackExpGained); - this.gainStrengthExp(this.workStrExpGained); - this.gainDefenseExp(this.workDefExpGained); - this.gainDexterityExp(this.workDexExpGained); - this.gainAgilityExp(this.workAgiExpGained); - this.gainCharismaExp(this.workChaExpGained); +PlayerObject.prototype.processWorkEarnings = function(numCycles=1) { + var hackExpGain = this.workHackExpGainRate * numCycles; + var strExpGain = this.workStrExpGainRate * numCycles; + var defExpGain = this.workDefExpGainRate * numCycles; + var dexExpGain = this.workDexExpGainRate * numCycles; + var agiExpGain = this.workAgiExpGainRate * numCycles; + var chaExpGain = this.workChaExpGainRate * numCycles; + + this.gainHackingExp(hackExpGain); + this.gainStrengthExp(strExpGain); + this.gainDefenseExp(defExpGain); + this.gainDexterityExp(dexExpGain); + this.gainAgilityExp(agiExpGain); + this.gainCharismaExp(chaExpGain); + this.workHackExpGained += hackExpGain; + this.workStrExpGained += strExpGain; + this.workDefExpGained += defExpGain; + this.workDexExpGained += dexExpGain; + this.workAgiExpGained += agiExpGain; + this.workChaExpGained += chaExpGain; + this.workRepGained += this.workRepGainRate * numCycles; + this.workMoneyGained += this.workMoneyGainRate * numCycles; + this.workMoneyGained -= this.workMoneyLossRate * numCycles; } /* Working for Company */ +PlayerObject.prototype.startWork = function() { + this.resetWorkStatus(); + this.isWorking = true; + this.workType = CONSTANTS.WorkTypeCompany; + + this.workHackExpGainRate = this.getWorkHackExpGain(); + this.workStrExpGainRate = this.getWorkStrExpGain(); + this.workDefExpGainRate = this.getWorkDefExpGain(); + this.workDexExpGainRate = this.getWorkDexExpGain(); + this.workAgiExpGainRate = this.getWorkAgiExpGain(); + this.workChaExpGainRate = this.getWorkChaExpGain(); + this.workRepGainRate = this.getWorkRepGain(); + this.workMoneyGainRate = this.getWorkMoneyGain(); + + this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer8Hours; + + //Remove all old event listeners from Cancel button + var newCancelButton = clearEventListeners("work-in-progress-cancel-button"); + newCancelButton.innerHTML = "Cancel Work"; + newCancelButton.addEventListener("click", function() { + Player.finishWork(true); + return false; + }); + + //Display Work In Progress Screen + Engine.loadWorkInProgressContent(); +} + +PlayerObject.prototype.work = function(numCycles) { + //Cap the number of cycles being processed to whatever would put you at + //the work time limit (8 hours) + var overMax = false; + if (this.timeWorked + (Engine._idleSpeed * numCycles) >= CONSTANTS.MillisecondsPer8Hours) { + overMax = true; + numCycles = Math.round((CONSTANTS.MillisecondsPer8Hours - this.timeWorked) / Engine._idleSpeed); + } + this.timeWorked += Engine._idleSpeed * numCycles; + + this.workRepGainRate = this.getWorkRepGain(); + this.processWorkEarnings(numCycles); + + //If timeWorked == 8 hours, then finish. You can only gain 8 hours worth of exp and money + if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer8Hours) { + return this.finishWork(false); + } + + var comp = Companies[this.companyName], companyRep = "0"; + if (comp == null || !(comp instanceof Company)) { + console.log("ERROR: Could not find Company: " + this.companyName); + } else { + companyRep = comp.playerReputation; + } + + var txt = document.getElementById("work-in-progress-text"); + txt.innerHTML = "You are currently working as a " + this.companyPosition.positionName + + " at " + this.companyName + " (Current Company Reputation: " + + formatNumber(companyRep, 0) + ")

" + + "You have been working for " + convertTimeMsToTimeElapsedString(this.timeWorked) + "

" + + "You have earned:

" + + "$" + formatNumber(this.workMoneyGained, 2) + " ($" + formatNumber(this.workMoneyGainRate * CYCLES_PER_SEC, 2) + " / sec)

" + + formatNumber(this.workRepGained, 4) + " (" + formatNumber(this.workRepGainRate * CYCLES_PER_SEC, 4) + " / sec) reputation for this company

" + + formatNumber(this.workHackExpGained, 4) + " (" + formatNumber(this.workHackExpGainRate * CYCLES_PER_SEC, 4) + " / sec) hacking exp

" + + formatNumber(this.workStrExpGained, 4) + " (" + formatNumber(this.workStrExpGainRate * CYCLES_PER_SEC, 4) + " / sec) strength exp
" + + formatNumber(this.workDefExpGained, 4) + " (" + formatNumber(this.workDefExpGainRate * CYCLES_PER_SEC, 4) + " / sec) defense exp
" + + formatNumber(this.workDexExpGained, 4) + " (" + formatNumber(this.workDexExpGainRate * CYCLES_PER_SEC, 4) + " / sec) dexterity exp
" + + formatNumber(this.workAgiExpGained, 4) + " (" + formatNumber(this.workAgiExpGainRate * CYCLES_PER_SEC, 4) + " / sec) agility exp

" + + formatNumber(this.workChaExpGained, 4) + " (" + formatNumber(this.workChaExpGainRate * CYCLES_PER_SEC, 4) + " / sec) charisma exp

" + + "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." +} + PlayerObject.prototype.finishWork = function(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.gainWorkExp(); - var company = Companies[this.companyName]; company.playerReputation += (this.workRepGained); @@ -773,91 +786,6 @@ PlayerObject.prototype.finishWork = function(cancelled, sing=false) { this.resetWorkStatus(); } -PlayerObject.prototype.startWork = function() { - this.resetWorkStatus(); - this.isWorking = true; - this.workType = CONSTANTS.WorkTypeCompany; - - this.workHackExpGainRate = this.getWorkHackExpGain(); - this.workStrExpGainRate = this.getWorkStrExpGain(); - this.workDefExpGainRate = this.getWorkDefExpGain(); - this.workDexExpGainRate = this.getWorkDexExpGain(); - this.workAgiExpGainRate = this.getWorkAgiExpGain(); - this.workChaExpGainRate = this.getWorkChaExpGain(); - this.workRepGainRate = this.getWorkRepGain(); - this.workMoneyGainRate = this.getWorkMoneyGain(); - - this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer8Hours; - - //Remove all old event listeners from Cancel button - var newCancelButton = clearEventListeners("work-in-progress-cancel-button"); - newCancelButton.innerHTML = "Cancel Work"; - newCancelButton.addEventListener("click", function() { - Player.finishWork(true); - return false; - }); - - //Display Work In Progress Screen - Engine.loadWorkInProgressContent(); -} - -PlayerObject.prototype.work = function(numCycles) { - this.workRepGainRate = this.getWorkRepGain(); - - this.workHackExpGained += this.workHackExpGainRate * numCycles; - this.workStrExpGained += this.workStrExpGainRate * numCycles; - this.workDefExpGained += this.workDefExpGainRate * numCycles; - this.workDexExpGained += this.workDexExpGainRate * numCycles; - this.workAgiExpGained += this.workAgiExpGainRate * numCycles; - this.workChaExpGained += this.workChaExpGainRate * numCycles; - this.workRepGained += this.workRepGainRate * numCycles; - this.workMoneyGained += this.workMoneyGainRate * numCycles; - - var cyclesPerSec = 1000 / Engine._idleSpeed; - - this.timeWorked += Engine._idleSpeed * numCycles; - - //If timeWorked == 8 hours, then finish. You can only gain 8 hours worth of exp and money - if (this.timeWorked >= CONSTANTS.MillisecondsPer8Hours) { - var maxCycles = CONSTANTS.GameCyclesPer8Hours; - this.workHackExpGained = this.workHackExpGainRate * maxCycles; - this.workStrExpGained = this.workStrExpGainRate * maxCycles; - this.workDefExpGained = this.workDefExpGainRate * maxCycles; - this.workDexExpGained = this.workDexExpGainRate * maxCycles; - this.workAgiExpGained = this.workAgiExpGainRate * maxCycles; - this.workChaExpGained = this.workChaExpGainRate * maxCycles; - this.workRepGained = this.workRepGainRate * maxCycles; - this.workMoneyGained = this.workMoneyGainRate * maxCycles; - this.finishWork(false); - return; - } - - var comp = Companies[this.companyName], companyRep = "0"; - if (comp == null || !(comp instanceof Company)) { - console.log("ERROR: Could not find Company: " + this.companyName); - } else { - companyRep = comp.playerReputation; - } - - var txt = document.getElementById("work-in-progress-text"); - txt.innerHTML = "You are currently working as a " + this.companyPosition.positionName + - " at " + this.companyName + " (Current Company Reputation: " + - formatNumber(companyRep, 0) + ")

" + - "You have been working for " + convertTimeMsToTimeElapsedString(this.timeWorked) + "

" + - "You have earned:

" + - "$" + formatNumber(this.workMoneyGained, 2) + " ($" + formatNumber(this.workMoneyGainRate * cyclesPerSec, 2) + " / sec)

" + - formatNumber(this.workRepGained, 4) + " (" + formatNumber(this.workRepGainRate * cyclesPerSec, 4) + " / sec) reputation for this company

" + - formatNumber(this.workHackExpGained, 4) + " (" + formatNumber(this.workHackExpGainRate * cyclesPerSec, 4) + " / sec) hacking exp

" + - formatNumber(this.workStrExpGained, 4) + " (" + formatNumber(this.workStrExpGainRate * cyclesPerSec, 4) + " / sec) strength exp
" + - formatNumber(this.workDefExpGained, 4) + " (" + formatNumber(this.workDefExpGainRate * cyclesPerSec, 4) + " / sec) defense exp
" + - formatNumber(this.workDexExpGained, 4) + " (" + formatNumber(this.workDexExpGainRate * cyclesPerSec, 4) + " / sec) dexterity exp
" + - formatNumber(this.workAgiExpGained, 4) + " (" + formatNumber(this.workAgiExpGainRate * cyclesPerSec, 4) + " / sec) agility exp

" + - formatNumber(this.workChaExpGained, 4) + " (" + formatNumber(this.workChaExpGainRate * cyclesPerSec, 4) + " / sec) charisma exp

" + - "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." - -} - PlayerObject.prototype.startWorkPartTime = function() { this.resetWorkStatus(); this.isWorking = true; @@ -886,57 +814,50 @@ PlayerObject.prototype.startWorkPartTime = function() { } PlayerObject.prototype.workPartTime = function(numCycles) { - this.workRepGainRate = this.getWorkRepGain(); - - this.workHackExpGained += this.workHackExpGainRate * numCycles; - this.workStrExpGained += this.workStrExpGainRate * numCycles; - this.workDefExpGained += this.workDefExpGainRate * numCycles; - this.workDexExpGained += this.workDexExpGainRate * numCycles; - this.workAgiExpGained += this.workAgiExpGainRate * numCycles; - this.workChaExpGained += this.workChaExpGainRate * numCycles; - this.workRepGained += this.workRepGainRate * numCycles; - this.workMoneyGained += this.workMoneyGainRate * numCycles; - - var cyclesPerSec = 1000 / Engine._idleSpeed; - + //Cap the number of cycles being processed to whatever would put you at the + //work time limit (8 hours) + var overMax = false; + if (this.timeWorked + (Engine._idleSpeed * numCycles) >= CONSTANTS.MillisecondsPer8Hours) { + overMax = true; + numCycles = Math.round((CONSTANTS.MillisecondsPer8Hours - this.timeWorked) / Engine._idleSpeed); + } this.timeWorked += Engine._idleSpeed * numCycles; + this.workRepGainRate = this.getWorkRepGain(); + this.processWorkEarnings(numCycles); + //If timeWorked == 8 hours, then finish. You can only gain 8 hours worth of exp and money - if (this.timeWorked >= CONSTANTS.MillisecondsPer8Hours) { - var maxCycles = CONSTANTS.GameCyclesPer8Hours; - this.workHackExpGained = this.workHackExpGainRate * maxCycles; - this.workStrExpGained = this.workStrExpGainRate * maxCycles; - this.workDefExpGained = this.workDefExpGainRate * maxCycles; - this.workDexExpGained = this.workDexExpGainRate * maxCycles; - this.workAgiExpGained = this.workAgiExpGainRate * maxCycles; - this.workChaExpGained = this.workChaExpGainRate * maxCycles; - this.workRepGained = this.workRepGainRate * maxCycles; - this.workMoneyGained = this.workMoneyGainRate * maxCycles; - this.finishWorkPartTime(); - return; + if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer8Hours) { + return this.finishWorkPartTime(); + } + + var comp = Companies[this.companyName], companyRep = "0"; + if (comp == null || !(comp instanceof Company)) { + console.log("ERROR: Could not find Company: " + this.companyName); + } else { + companyRep = comp.playerReputation; } var txt = document.getElementById("work-in-progress-text"); txt.innerHTML = "You are currently working as a " + this.companyPosition.positionName + - " at " + Player.companyName + "

" + + " at " + Player.companyName + " (Current Company Reputation: " + + formatNumber(companyRep, 0) + ")

" + "You have been working for " + convertTimeMsToTimeElapsedString(this.timeWorked) + "

" + "You have earned:

" + - "$" + formatNumber(this.workMoneyGained, 2) + " ($" + formatNumber(this.workMoneyGainRate * cyclesPerSec, 2) + " / sec)

" + - formatNumber(this.workRepGained, 4) + " (" + formatNumber(this.workRepGainRate * cyclesPerSec, 4) + " / sec) reputation for this company

" + - formatNumber(this.workHackExpGained, 4) + " (" + formatNumber(this.workHackExpGainRate * cyclesPerSec, 4) + " / sec) hacking exp

" + - formatNumber(this.workStrExpGained, 4) + " (" + formatNumber(this.workStrExpGainRate * cyclesPerSec, 4) + " / sec) strength exp
" + - formatNumber(this.workDefExpGained, 4) + " (" + formatNumber(this.workDefExpGainRate * cyclesPerSec, 4) + " / sec) defense exp
" + - formatNumber(this.workDexExpGained, 4) + " (" + formatNumber(this.workDexExpGainRate * cyclesPerSec, 4) + " / sec) dexterity exp
" + - formatNumber(this.workAgiExpGained, 4) + " (" + formatNumber(this.workAgiExpGainRate * cyclesPerSec, 4) + " / sec) agility exp

" + - formatNumber(this.workChaExpGained, 4) + " (" + formatNumber(this.workChaExpGainRate * cyclesPerSec, 4) + " / sec) charisma exp

" + + "$" + formatNumber(this.workMoneyGained, 2) + " ($" + formatNumber(this.workMoneyGainRate * CYCLES_PER_SEC, 2) + " / sec)

" + + formatNumber(this.workRepGained, 4) + " (" + formatNumber(this.workRepGainRate * CYCLES_PER_SEC, 4) + " / sec) reputation for this company

" + + formatNumber(this.workHackExpGained, 4) + " (" + formatNumber(this.workHackExpGainRate * CYCLES_PER_SEC, 4) + " / sec) hacking exp

" + + formatNumber(this.workStrExpGained, 4) + " (" + formatNumber(this.workStrExpGainRate * CYCLES_PER_SEC, 4) + " / sec) strength exp
" + + formatNumber(this.workDefExpGained, 4) + " (" + formatNumber(this.workDefExpGainRate * CYCLES_PER_SEC, 4) + " / sec) defense exp
" + + formatNumber(this.workDexExpGained, 4) + " (" + formatNumber(this.workDexExpGainRate * CYCLES_PER_SEC, 4) + " / sec) dexterity exp
" + + formatNumber(this.workAgiExpGained, 4) + " (" + formatNumber(this.workAgiExpGainRate * CYCLES_PER_SEC, 4) + " / sec) agility exp

" + + formatNumber(this.workChaExpGained, 4) + " (" + formatNumber(this.workChaExpGainRate * CYCLES_PER_SEC, 4) + " / sec) charisma exp

" + "You will automatically finish after working for 8 hours. You can cancel earlier if you wish,
" + "and there will be no penalty because this is a part-time job."; } PlayerObject.prototype.finishWorkPartTime = function(sing=false) { - this.gainWorkExp(); - var company = Companies[this.companyName]; company.playerReputation += (this.workRepGained); @@ -978,51 +899,6 @@ PlayerObject.prototype.finishWorkPartTime = function(sing=false) { } /* Working for Faction */ -PlayerObject.prototype.finishFactionWork = function(cancelled, sing=false) { - this.gainWorkExp(); - - var faction = Factions[this.currentWorkFactionName]; - faction.playerReputation += (this.workRepGained); - - this.gainMoney(this.workMoneyGained); - - this.updateSkillLevels(); - - var txt = "You worked for your faction " + faction.name + " for a total of " + convertTimeMsToTimeElapsedString(this.timeWorked) + "

" + - "You earned a total of:
" + - "$" + formatNumber(this.workMoneyGained, 2) + "
" + - formatNumber(this.workRepGained, 4) + " reputation for the faction
" + - formatNumber(this.workHackExpGained, 4) + " hacking exp
" + - formatNumber(this.workStrExpGained, 4) + " strength exp
" + - formatNumber(this.workDefExpGained, 4) + " defense exp
" + - formatNumber(this.workDexExpGained, 4) + " dexterity exp
" + - formatNumber(this.workAgiExpGained, 4) + " agility exp
" + - formatNumber(this.workChaExpGained, 4) + " charisma exp
"; - if (!sing) {dialogBoxCreate(txt);} - - var mainMenu = document.getElementById("mainmenu-container"); - mainMenu.style.visibility = "visible"; - - this.isWorking = false; - - Engine.loadFactionContent(); - displayFactionContent(faction.name); - if (sing) { - var res="You worked for your faction " + faction.name + " for a total of " + convertTimeMsToTimeElapsedString(this.timeWorked) + ". " + - "You earned " + - formatNumber(this.workRepGained, 4) + " rep, " + - formatNumber(this.workHackExpGained, 4) + " hacking exp, " + - formatNumber(this.workStrExpGained, 4) + " str exp, " + - formatNumber(this.workDefExpGained, 4) + " def exp, " + - formatNumber(this.workDexExpGained, 4) + " dex exp, " + - formatNumber(this.workAgiExpGained, 4) + " agi exp, and " + - formatNumber(this.workChaExpGained, 4) + " cha exp."; - this.resetWorkStatus(); - return res; - } - this.resetWorkStatus(); -} - PlayerObject.prototype.startFactionWork = function(faction) { //Update reputation gain rate to account for faction favor var favorMult = 1 + (faction.favor / 100); @@ -1117,52 +993,81 @@ PlayerObject.prototype.workForFaction = function(numCycles) { this.workRepGainRate *= favorMult; this.workRepGainRate *= BitNodeMultipliers.FactionWorkRepGain; - this.workHackExpGained += this.workHackExpGainRate * numCycles; - this.workStrExpGained += this.workStrExpGainRate * numCycles; - this.workDefExpGained += this.workDefExpGainRate * numCycles; - this.workDexExpGained += this.workDexExpGainRate * numCycles; - this.workAgiExpGained += this.workAgiExpGainRate * numCycles; - this.workChaExpGained += this.workChaExpGainRate * numCycles; - this.workRepGained += this.workRepGainRate * numCycles; - this.workMoneyGained += this.workMoneyGainRate * numCycles; - - var cyclesPerSec = 1000 / Engine._idleSpeed; - + //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) { + overMax = true; + numCycles = Math.round((CONSTANTS.MillisecondsPer20Hours - this.timeWorked) / Engine._idleSpeed); + } this.timeWorked += Engine._idleSpeed * numCycles; + this.processWorkEarnings(numCycles); + //If timeWorked == 20 hours, then finish. You can only work for the faction for 20 hours - if (this.timeWorked >= CONSTANTS.MillisecondsPer20Hours) { - var maxCycles = CONSTANTS.GameCyclesPer20Hours; - this.timeWorked = CONSTANTS.MillisecondsPer20Hours; - this.workHackExpGained = this.workHackExpGainRate * maxCycles; - this.workStrExpGained = this.workStrExpGainRate * maxCycles; - this.workDefExpGained = this.workDefExpGainRate * maxCycles; - this.workDexExpGained = this.workDexExpGainRate * maxCycles; - this.workAgiExpGained = this.workAgiExpGainRate * maxCycles; - this.workChaExpGained = this.workChaExpGainRate * maxCycles; - this.workRepGained = this.workRepGainRate * maxCycles; - this.workMoneyGained = this.workMoneyGainRate * maxCycles; - this.finishFactionWork(false); + if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer20Hours) { + return this.finishWork(false); } var txt = document.getElementById("work-in-progress-text"); txt.innerHTML = "You are currently " + this.currentWorkFactionDescription + " for your faction " + faction.name + - " (Current Faction Reputation: " + formatNumber(faction.playerReputation, 0) + "). " + + " (Current Faction Reputation: " + formatNumber(faction.playerReputation, 0) + ").
" + "You have been doing this for " + convertTimeMsToTimeElapsedString(this.timeWorked) + "

" + "You have earned:

" + - "$" + formatNumber(this.workMoneyGained, 2) + " (" + formatNumber(this.workMoneyGainRate * cyclesPerSec, 2) + " / sec)

" + - formatNumber(this.workRepGained, 4) + " (" + formatNumber(this.workRepGainRate * cyclesPerSec, 4) + " / sec) reputation for this faction

" + - formatNumber(this.workHackExpGained, 4) + " (" + formatNumber(this.workHackExpGainRate * cyclesPerSec, 4) + " / sec) hacking exp

" + - formatNumber(this.workStrExpGained, 4) + " (" + formatNumber(this.workStrExpGainRate * cyclesPerSec, 4) + " / sec) strength exp
" + - formatNumber(this.workDefExpGained, 4) + " (" + formatNumber(this.workDefExpGainRate * cyclesPerSec, 4) + " / sec) defense exp
" + - formatNumber(this.workDexExpGained, 4) + " (" + formatNumber(this.workDexExpGainRate * cyclesPerSec, 4) + " / sec) dexterity exp
" + - formatNumber(this.workAgiExpGained, 4) + " (" + formatNumber(this.workAgiExpGainRate * cyclesPerSec, 4) + " / sec) agility exp

" + - formatNumber(this.workChaExpGained, 4) + " (" + formatNumber(this.workChaExpGainRate * cyclesPerSec, 4) + " / sec) charisma exp

" + + "$" + formatNumber(this.workMoneyGained, 2) + " (" + formatNumber(this.workMoneyGainRate * CYCLES_PER_SEC, 2) + " / sec)

" + + formatNumber(this.workRepGained, 4) + " (" + formatNumber(this.workRepGainRate * CYCLES_PER_SEC, 4) + " / sec) reputation for this faction

" + + formatNumber(this.workHackExpGained, 4) + " (" + formatNumber(this.workHackExpGainRate * CYCLES_PER_SEC, 4) + " / sec) hacking exp

" + + formatNumber(this.workStrExpGained, 4) + " (" + formatNumber(this.workStrExpGainRate * CYCLES_PER_SEC, 4) + " / sec) strength exp
" + + formatNumber(this.workDefExpGained, 4) + " (" + formatNumber(this.workDefExpGainRate * CYCLES_PER_SEC, 4) + " / sec) defense exp
" + + formatNumber(this.workDexExpGained, 4) + " (" + formatNumber(this.workDexExpGainRate * CYCLES_PER_SEC, 4) + " / sec) dexterity exp
" + + formatNumber(this.workAgiExpGained, 4) + " (" + formatNumber(this.workAgiExpGainRate * CYCLES_PER_SEC, 4) + " / sec) agility exp

" + + formatNumber(this.workChaExpGained, 4) + " (" + formatNumber(this.workChaExpGainRate * CYCLES_PER_SEC, 4) + " / sec) charisma exp

" + "You will automatically finish after working for 20 hours. You can cancel earlier if you wish.
" + "There is no penalty for cancelling earlier."; } +PlayerObject.prototype.finishFactionWork = function(cancelled, sing=false) { + var faction = Factions[this.currentWorkFactionName]; + faction.playerReputation += (this.workRepGained); + + this.gainMoney(this.workMoneyGained); + + this.updateSkillLevels(); + + var txt = "You worked for your faction " + faction.name + " for a total of " + convertTimeMsToTimeElapsedString(this.timeWorked) + "

" + + "You earned a total of:
" + + "$" + formatNumber(this.workMoneyGained, 2) + "
" + + formatNumber(this.workRepGained, 4) + " reputation for the faction
" + + formatNumber(this.workHackExpGained, 4) + " hacking exp
" + + formatNumber(this.workStrExpGained, 4) + " strength exp
" + + formatNumber(this.workDefExpGained, 4) + " defense exp
" + + formatNumber(this.workDexExpGained, 4) + " dexterity exp
" + + formatNumber(this.workAgiExpGained, 4) + " agility exp
" + + formatNumber(this.workChaExpGained, 4) + " charisma exp
"; + if (!sing) {dialogBoxCreate(txt);} + + var mainMenu = document.getElementById("mainmenu-container"); + mainMenu.style.visibility = "visible"; + + this.isWorking = false; + + Engine.loadFactionContent(); + displayFactionContent(faction.name); + if (sing) { + var res="You worked for your faction " + faction.name + " for a total of " + convertTimeMsToTimeElapsedString(this.timeWorked) + ". " + + "You earned " + + formatNumber(this.workRepGained, 4) + " rep, " + + formatNumber(this.workHackExpGained, 4) + " hacking exp, " + + formatNumber(this.workStrExpGained, 4) + " str exp, " + + formatNumber(this.workDefExpGained, 4) + " def exp, " + + formatNumber(this.workDexExpGained, 4) + " dex exp, " + + formatNumber(this.workAgiExpGained, 4) + " agi exp, and " + + formatNumber(this.workChaExpGained, 4) + " cha exp."; + this.resetWorkStatus(); + return res; + } + this.resetWorkStatus(); +} //Money gained per game cycle PlayerObject.prototype.getWorkMoneyGain = function() { @@ -1438,36 +1343,25 @@ PlayerObject.prototype.takeClass = function(numCycles) { this.timeWorked += Engine._idleSpeed * numCycles; var className = this.className; - this.workHackExpGained += this.workHackExpGainRate * numCycles; - this.workStrExpGained += this.workStrExpGainRate * numCycles; - this.workDefExpGained += this.workDefExpGainRate * numCycles; - this.workDexExpGained += this.workDexExpGainRate * numCycles; - this.workAgiExpGained += this.workAgiExpGainRate * numCycles; - this.workChaExpGained += this.workChaExpGainRate * numCycles; - this.workRepGained += this.workRepGainRate * numCycles; - this.workMoneyGained += this.workMoneyGainRate * numCycles; - this.workMoneyGained -= this.workMoneyLossRate * numCycles; - - var cyclesPerSec = 1000 / Engine._idleSpeed; + this.processWorkEarnings(numCycles); var txt = document.getElementById("work-in-progress-text"); txt.innerHTML = "You have been " + className + " for " + convertTimeMsToTimeElapsedString(this.timeWorked) + "

" + "This has cost you:
" + - "$" + formatNumber(this.workMoneyGained, 2) + " ($" + formatNumber(this.workMoneyLossRate * cyclesPerSec, 2) + " / sec)

" + + "$" + formatNumber(this.workMoneyGained, 2) + " ($" + formatNumber(this.workMoneyLossRate * CYCLES_PER_SEC, 2) + " / sec)

" + "You have gained:
" + - formatNumber(this.workHackExpGained, 4) + " (" + formatNumber(this.workHackExpGainRate * cyclesPerSec, 4) + " / sec) hacking exp
" + - formatNumber(this.workStrExpGained, 4) + " (" + formatNumber(this.workStrExpGainRate * cyclesPerSec, 4) + " / sec) strength exp
" + - formatNumber(this.workDefExpGained, 4) + " (" + formatNumber(this.workDefExpGainRate * cyclesPerSec, 4) + " / sec) defense exp
" + - formatNumber(this.workDexExpGained, 4) + " (" + formatNumber(this.workDexExpGainRate * cyclesPerSec, 4) + " / sec) dexterity exp
" + - formatNumber(this.workAgiExpGained, 4) + " (" + formatNumber(this.workAgiExpGainRate * cyclesPerSec, 4) + " / sec) agility exp
" + - formatNumber(this.workChaExpGained, 4) + " (" + formatNumber(this.workChaExpGainRate * cyclesPerSec, 4) + " / sec) charisma exp
" + + formatNumber(this.workHackExpGained, 4) + " (" + formatNumber(this.workHackExpGainRate * CYCLES_PER_SEC, 4) + " / sec) hacking exp
" + + formatNumber(this.workStrExpGained, 4) + " (" + formatNumber(this.workStrExpGainRate * CYCLES_PER_SEC, 4) + " / sec) strength exp
" + + formatNumber(this.workDefExpGained, 4) + " (" + formatNumber(this.workDefExpGainRate * CYCLES_PER_SEC, 4) + " / sec) defense exp
" + + formatNumber(this.workDexExpGained, 4) + " (" + formatNumber(this.workDexExpGainRate * CYCLES_PER_SEC, 4) + " / sec) dexterity exp
" + + formatNumber(this.workAgiExpGained, 4) + " (" + formatNumber(this.workAgiExpGainRate * CYCLES_PER_SEC, 4) + " / sec) agility exp
" + + formatNumber(this.workChaExpGained, 4) + " (" + formatNumber(this.workChaExpGainRate * CYCLES_PER_SEC, 4) + " / sec) charisma exp
" + "You may cancel at any time"; } //The 'sing' argument defines whether or not this function was called //through a Singularity Netscript function PlayerObject.prototype.finishClass = function(sing=false) { - this.gainWorkExp(); this.gainIntelligenceExp(CONSTANTS.IntelligenceClassBaseExpGain * Math.round(this.timeWorked / 1000)); if (this.workMoneyGained > 0) { @@ -1642,7 +1536,12 @@ PlayerObject.prototype.finishCrime = function(cancelled) { } } - this.gainWorkExp(); + this.gainHackingExp(this.workHackExpGained); + this.gainStrengthExp(this.workStrExpGained); + this.gainDefenseExp(this.workDefExpGained); + this.gainDexterityExp(this.workDexExpGained); + this.gainAgilityExp(this.workAgiExpGained); + this.gainCharismaExp(this.workChaExpGained); } this.committingCrimeThruSingFn = false; this.singFnCrimeWorkerScript = null; @@ -2049,7 +1948,7 @@ PlayerObject.prototype.reapplyAllSourceFiles = function() { //those requirements and will return an array of all factions that the Player should //receive an invitation to PlayerObject.prototype.checkForFactionInvitations = function() { - let invitedFactions = []; //Array which will hold all Factions th eplayer should be invited to + let invitedFactions = []; //Array which will hold all Factions the player should be invited to var numAugmentations = this.augmentations.length; diff --git a/src/SaveObject.js b/src/SaveObject.js index c83734979..bf4bb9be2 100644 --- a/src/SaveObject.js +++ b/src/SaveObject.js @@ -516,9 +516,9 @@ function loadImportedGame(saveObj, saveString) { Player.lastUpdate = Engine._lastUpdate; Engine.start(); //Run main game loop and Scripts loop - dialogBoxCreate("While you were offline, your scripts generated $" + - formatNumber(offlineProductionFromScripts, 2) + " and your Hacknet Nodes generated $" + - formatNumber(offlineProductionFromHacknetNodes, 2)); + dialogBoxCreate("While you were offline, your scripts generated $" + + formatNumber(offlineProductionFromScripts, 2) + " and your Hacknet Nodes generated $" + + formatNumber(offlineProductionFromHacknetNodes, 2) + ""); return true; } diff --git a/src/Script.js b/src/Script.js index fca23dc83..670ff2cd7 100644 --- a/src/Script.js +++ b/src/Script.js @@ -21,7 +21,7 @@ import {CONSTANTS} from "./Constants"; import {Engine} from "./engine"; import {FconfSettings, parseFconfSettings} from "./Fconf"; import {iTutorialSteps, iTutorialNextStep, - iTutorialIsRunning, currITutorialStep} from "./InteractiveTutorial"; + ITutorial} from "./InteractiveTutorial"; import {evaluateImport} from "./NetscriptEvaluator"; import {NetscriptFunctions} from "./NetscriptFunctions"; import {addWorkerScript, @@ -281,8 +281,9 @@ function saveAndCloseScriptEditor() { var filename = document.getElementById("script-editor-filename").value; var editor = ace.edit('javascript-editor'); var code = editor.getValue(); - if (iTutorialIsRunning && currITutorialStep == iTutorialSteps.TerminalTypeScript) { - if (filename != "foodnstuff.script") { + if (ITutorial.isRunning && ITutorial.currStep === iTutorialSteps.TerminalTypeScript) { + //Make sure filename + code properly follow tutorial + if (filename !== "foodnstuff.script") { dialogBoxCreate("Leave the script name as 'foodnstuff'!"); return; } @@ -291,7 +292,23 @@ function saveAndCloseScriptEditor() { dialogBoxCreate("Please copy and paste the code from the tutorial!"); return; } - iTutorialNextStep(); + + //Save the script + let s = Player.getCurrentServer(); + for (var i = 0; i < s.scripts.length; i++) { + if (filename == s.scripts[i].filename) { + s.scripts[i].saveScript(); + Engine.loadTerminalContent(); + return iTutorialNextStep(); + } + } + + //If the current script does NOT exist, create a new one + let script = new Script(); + script.saveScript(); + s.scripts.push(script); + + return iTutorialNextStep(); } if (filename == "") { diff --git a/src/Settings.ts b/src/Settings.ts index ddf5cb0ac..ac081a236 100644 --- a/src/Settings.ts +++ b/src/Settings.ts @@ -39,6 +39,11 @@ interface IDefaultSettings { */ SuppressFactionInvites: boolean; + /** + * Whether to show a popup message when player is hospitalized from taking too much damage + */ + SuppressHospitalizationPopup: boolean; + /** * Whether the user should be shown a dialog box whenever they receive a new message file. */ @@ -48,11 +53,6 @@ interface IDefaultSettings { * Whether the user should be asked to confirm travelling between cities. */ SuppressTravelConfirmation: boolean; - - /** - * Whether to show a popup message when player is hospitalized from taking too much damage - */ - SuppressHospitalizationPopup: boolean; } /** @@ -95,9 +95,9 @@ const defaultSettings: IDefaultSettings = { MaxPortCapacity: 50, SuppressBuyAugmentationConfirmation: false, SuppressFactionInvites: false, + SuppressHospitalizationPopup: false, SuppressMessages: false, SuppressTravelConfirmation: false, - SuppressHospitalizationPopup: false, }; /** @@ -114,9 +114,9 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = { MaxPortCapacity: defaultSettings.MaxPortCapacity, SuppressBuyAugmentationConfirmation: defaultSettings.SuppressBuyAugmentationConfirmation, SuppressFactionInvites: defaultSettings.SuppressFactionInvites, + SuppressHospitalizationPopup: defaultSettings.SuppressHospitalizationPopup, SuppressMessages: defaultSettings.SuppressMessages, SuppressTravelConfirmation: defaultSettings.SuppressTravelConfirmation, - SuppressHospitalizationPopup: defaultSettings.SuppressHospitalizationPopup, ThemeBackgroundColor: "#000000", ThemeFontColor: "#66ff33", ThemeHighlightColor: "#ffffff", diff --git a/src/Stock.ts b/src/Stock.ts new file mode 100644 index 000000000..6d5928430 --- /dev/null +++ b/src/Stock.ts @@ -0,0 +1,95 @@ +import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver"; + +export class Stock { + /** + * Initializes a Stock from a JSON save state + */ + static fromJSON(value: any): Stock { + return Generic_fromJSON(Stock, value.data); + } + + /** + * The stock's ticker symbol + */ + readonly symbol: string; + + /** + * Name of the company that the stock is for + */ + readonly name: string; + + /** + * Stock's share price + */ + price: number; + + /** + * Number of shares the player owns in the LONG position + */ + playerShares: number; + + /** + * Average price of stocks that the player owns in the LONG position + */ + playerAvgPx: number; + + /** + * Number of shares the player owns in the SHORT position + */ + playerShortShares: number; + + /** + * Average price of stocks that the player owns in the SHORT position + */ + playerAvgShortPx: number; + + /** + * Maximum volatility + */ + readonly mv: number; + + /** + * Bear or bull (more likely to go up or down, based on otlkMag) + */ + b: boolean; + + /** + * Outlook magnitude. Represents the stock's forecast and likelihood + * of increasing/decreasing (based on whether its in bear or bull mode) + */ + otlkMag: number; + + /** + * The HTML element that displays the stock's info in the UI + */ + posTxtEl: HTMLElement | null; + + constructor(name: string="", + symbol: string="", + mv: number=1, + b: boolean=true, + otlkMag: number=0, + initPrice: number = 10e3) { + this.name = name; + this.symbol = symbol; + this.price = initPrice; + this.playerShares = 0; + this.playerAvgPx = 0; + this.playerShortShares = 0; + this.playerAvgShortPx = 0; + this.mv = mv; + this.b = b; + this.otlkMag = otlkMag; + + this.posTxtEl = null; + } + + /** + * Serialize the Stock to a JSON save state. + */ + toJSON(): any { + return Generic_toJSON("Stock", this); + } +} + +Reviver.constructors.Stock = Stock; diff --git a/src/StockMarket.js b/src/StockMarket.js index 4f9d9bd4e..e5c829014 100755 --- a/src/StockMarket.js +++ b/src/StockMarket.js @@ -3,6 +3,7 @@ import {Locations} from "./Locations"; import {hasWallStreetSF, wallStreetSFLvl} from "./NetscriptFunctions"; import {WorkerScript} from "./NetscriptWorker"; import {Player} from "./Player"; +import {Stock} from "./Stock"; import {dialogBoxCreate} from "../utils/DialogBox"; import {clearEventListeners} from "../utils/uiHelpers/clearEventListeners"; @@ -24,32 +25,6 @@ import {yesNoBoxCreate, yesNoTxtInpBoxCreate, let StockPriceCap = 1e9; //Put a limit on how high a price can go -function Stock(name, symbol, mv, b, otlkMag, initPrice=10000) { - this.symbol = symbol; - this.name = name; - this.price = initPrice; - - this.playerShares = 0; - this.playerAvgPx = 0; - this.playerShortShares = 0; - this.playerAvgShortPx = 0; - this.mv = mv; - this.b = b; - this.otlkMag = otlkMag; - - this.posTxtEl = null; -} - -Stock.prototype.toJSON = function() { - return Generic_toJSON("Stock", this); -} - -Stock.fromJSON = function(value) { - return Generic_fromJSON(Stock, value.data); -} - -Reviver.constructors.Stock = Stock; - var OrderTypes = { LimitBuy: "Limit Buy Order", LimitSell: "Limit Sell Order", @@ -253,135 +228,135 @@ function initStockMarket() { } var ecorp = Locations.AevumECorp; - var ecorpStk = new Stock(ecorp, StockSymbols[ecorp], 0.45, true, 19, getRandomInt(20000, 25000)); + var ecorpStk = new Stock(ecorp, StockSymbols[ecorp], getRandomInt(40, 50)/100, true, 19, getRandomInt(17e3, 28e3)); StockMarket[ecorp] = ecorpStk; var megacorp = Locations.Sector12MegaCorp; - var megacorpStk = new Stock(megacorp, StockSymbols[megacorp], 0.45, true, 19, getRandomInt(25000, 33000)); + var megacorpStk = new Stock(megacorp, StockSymbols[megacorp], getRandomInt(40,50)/100, true, 19, getRandomInt(24e3, 34e3)); StockMarket[megacorp] = megacorpStk; var blade = Locations.Sector12BladeIndustries; - var bladeStk = new Stock(blade, StockSymbols[blade], 0.75, true, 13, getRandomInt(15000, 22000)); + var bladeStk = new Stock(blade, StockSymbols[blade], getRandomInt(70, 80)/100, true, 13, getRandomInt(12e3, 25e3)); StockMarket[blade] = bladeStk; var clarke = Locations.AevumClarkeIncorporated; - var clarkeStk = new Stock(clarke, StockSymbols[clarke], 0.7, true, 12, getRandomInt(15000, 20000)); + var clarkeStk = new Stock(clarke, StockSymbols[clarke], getRandomInt(65, 75)/100, true, 12, getRandomInt(10e3, 25e3)); StockMarket[clarke] = clarkeStk; var omnitek = Locations.VolhavenOmniTekIncorporated; - var omnitekStk = new Stock(omnitek, StockSymbols[omnitek], 0.65, true, 12, getRandomInt(35000, 40000)); + var omnitekStk = new Stock(omnitek, StockSymbols[omnitek], getRandomInt(60, 70)/100, true, 12, getRandomInt(32e3, 43e3)); StockMarket[omnitek] = omnitekStk; var foursigma = Locations.Sector12FourSigma; - var foursigmaStk = new Stock(foursigma, StockSymbols[foursigma], 1.05, true, 17, getRandomInt(60000, 70000)); + var foursigmaStk = new Stock(foursigma, StockSymbols[foursigma], getRandomInt(100, 110)/100, true, 17, getRandomInt(50e3, 80e3)); StockMarket[foursigma] = foursigmaStk; var kuaigong = Locations.ChongqingKuaiGongInternational; - var kuaigongStk = new Stock(kuaigong, StockSymbols[kuaigong], 0.8, true, 10, getRandomInt(20000, 24000)); + var kuaigongStk = new Stock(kuaigong, StockSymbols[kuaigong], getRandomInt(75, 85)/100, true, 10, getRandomInt(16e3, 28e3)); StockMarket[kuaigong] = kuaigongStk; var fulcrum = Locations.AevumFulcrumTechnologies; - var fulcrumStk = new Stock(fulcrum, StockSymbols[fulcrum], 1.25, true, 16, getRandomInt(30000, 35000)); + var fulcrumStk = new Stock(fulcrum, StockSymbols[fulcrum], getRandomInt(120, 130)/100, true, 16, getRandomInt(29e3, 36e3)); StockMarket[fulcrum] = fulcrumStk; var storm = Locations.IshimaStormTechnologies; - var stormStk = new Stock(storm, StockSymbols[storm], 0.85, true, 7, getRandomInt(21000, 24000)); + var stormStk = new Stock(storm, StockSymbols[storm], getRandomInt(80, 90)/100, true, 7, getRandomInt(20e3, 25e3)); StockMarket[storm] = stormStk; var defcomm = Locations.NewTokyoDefComm; - var defcommStk = new Stock(defcomm, StockSymbols[defcomm], 0.65, true, 10, getRandomInt(10000, 15000)); + var defcommStk = new Stock(defcomm, StockSymbols[defcomm], getRandomInt(60, 70)/100, true, 10, getRandomInt(6e3, 19e3)); StockMarket[defcomm] = defcommStk; var helios = Locations.VolhavenHeliosLabs; - var heliosStk = new Stock(helios, StockSymbols[helios], 0.6, true, 9, getRandomInt(12000, 16000)); + var heliosStk = new Stock(helios, StockSymbols[helios], getRandomInt(55, 65)/100, true, 9, getRandomInt(10e3, 18e3)); StockMarket[helios] = heliosStk; var vitalife = Locations.NewTokyoVitaLife; - var vitalifeStk = new Stock(vitalife, StockSymbols[vitalife], 0.75, true, 7, getRandomInt(10000, 12000)); + var vitalifeStk = new Stock(vitalife, StockSymbols[vitalife], getRandomInt(70, 80)/100, true, 7, getRandomInt(8e3, 14e3)); StockMarket[vitalife] = vitalifeStk; var icarus = Locations.Sector12IcarusMicrosystems; - var icarusStk = new Stock(icarus, StockSymbols[icarus], 0.65, true, 7.5, getRandomInt(16000, 20000)); + var icarusStk = new Stock(icarus, StockSymbols[icarus], getRandomInt(60, 70)/100, true, 7.5, getRandomInt(12e3, 24e3)); StockMarket[icarus] = icarusStk; var universalenergy = Locations.Sector12UniversalEnergy; - var universalenergyStk = new Stock(universalenergy, StockSymbols[universalenergy], 0.55, true, 10, getRandomInt(20000, 25000)); + var universalenergyStk = new Stock(universalenergy, StockSymbols[universalenergy], getRandomInt(50, 60)/100, true, 10, getRandomInt(16e3, 29e3)); StockMarket[universalenergy] = universalenergyStk; var aerocorp = Locations.AevumAeroCorp; - var aerocorpStk = new Stock(aerocorp, StockSymbols[aerocorp], 0.6, true, 6, getRandomInt(10000, 15000)); + var aerocorpStk = new Stock(aerocorp, StockSymbols[aerocorp], getRandomInt(55, 65)/100, true, 6, getRandomInt(8e3, 17e3)); StockMarket[aerocorp] = aerocorpStk; var omnia = Locations.VolhavenOmniaCybersystems; - var omniaStk = new Stock(omnia, StockSymbols[omnia], 0.7, true, 4.5, getRandomInt(9000, 12000)); + var omniaStk = new Stock(omnia, StockSymbols[omnia], getRandomInt(65, 75)/100, true, 4.5, getRandomInt(6e3, 15e3)); StockMarket[omnia] = omniaStk; var solaris = Locations.ChongqingSolarisSpaceSystems; - var solarisStk = new Stock(solaris, StockSymbols[solaris], 0.75, true, 8.5, getRandomInt(18000, 24000)); + var solarisStk = new Stock(solaris, StockSymbols[solaris], getRandomInt(70, 80)/100, true, 8.5, getRandomInt(14e3, 28e3)); StockMarket[solaris] = solarisStk; var globalpharm = Locations.NewTokyoGlobalPharmaceuticals; - var globalpharmStk = new Stock(globalpharm, StockSymbols[globalpharm], 0.6, true, 10.5, getRandomInt(18000, 24000)); + var globalpharmStk = new Stock(globalpharm, StockSymbols[globalpharm], getRandomInt(55, 65)/100, true, 10.5, getRandomInt(12e3, 30e3)); StockMarket[globalpharm] = globalpharmStk; var nova = Locations.IshimaNovaMedical; - var novaStk = new Stock(nova, StockSymbols[nova], 0.75, true, 5, getRandomInt(18000, 24000)); + var novaStk = new Stock(nova, StockSymbols[nova], getRandomInt(70, 80)/100, true, 5, getRandomInt(15e3, 27e3)); StockMarket[nova] = novaStk; var watchdog = Locations.AevumWatchdogSecurity; - var watchdogStk = new Stock(watchdog, StockSymbols[watchdog], 2.5, true, 1.5, getRandomInt(5000, 7500)); + var watchdogStk = new Stock(watchdog, StockSymbols[watchdog], getRandomInt(240, 260)/100, true, 1.5, getRandomInt(4e3, 8.5e3)); StockMarket[watchdog] = watchdogStk; var lexocorp = Locations.VolhavenLexoCorp; - var lexocorpStk = new Stock(lexocorp, StockSymbols[lexocorp], 1.25, true, 6, getRandomInt(5000, 7500)); + var lexocorpStk = new Stock(lexocorp, StockSymbols[lexocorp], getRandomInt(115, 135)/100, true, 6, getRandomInt(4.5e3, 8e3)); StockMarket[lexocorp] = lexocorpStk; var rho = Locations.AevumRhoConstruction; - var rhoStk = new Stock(rho, StockSymbols[rho], 0.6, true, 1, getRandomInt(3000, 6000)); + var rhoStk = new Stock(rho, StockSymbols[rho], getRandomInt(50, 70)/100, true, 1, getRandomInt(2e3, 7e3)); StockMarket[rho] = rhoStk; var alpha = Locations.Sector12AlphaEnterprises; - var alphaStk = new Stock(alpha, StockSymbols[alpha], 1.9, true, 10, getRandomInt(5000, 7500)); + var alphaStk = new Stock(alpha, StockSymbols[alpha], getRandomInt(175, 205)/100, true, 10, getRandomInt(4e3, 8.5e3)); StockMarket[alpha] = alphaStk; var syscore = Locations.VolhavenSysCoreSecurities; - var syscoreStk = new Stock(syscore, StockSymbols[syscore], 1.6, true, 3, getRandomInt(4000, 7000)) + var syscoreStk = new Stock(syscore, StockSymbols[syscore], getRandomInt(150, 170)/100, true, 3, getRandomInt(3e3, 8e3)); StockMarket[syscore] = syscoreStk; var computek = Locations.VolhavenCompuTek; - var computekStk = new Stock(computek, StockSymbols[computek], 0.9, true, 4, getRandomInt(2000, 5000)); + var computekStk = new Stock(computek, StockSymbols[computek], getRandomInt(80, 100)/100, true, 4, getRandomInt(1e3, 6e3)); StockMarket[computek] = computekStk; var netlink = Locations.AevumNetLinkTechnologies; - var netlinkStk = new Stock(netlink, StockSymbols[netlink], 4.2, true, 1, getRandomInt(2000, 4000)); + var netlinkStk = new Stock(netlink, StockSymbols[netlink], getRandomInt(400, 430)/100, true, 1, getRandomInt(1e3, 5e3)); StockMarket[netlink] = netlinkStk; var omega = Locations.IshimaOmegaSoftware; - var omegaStk = new Stock(omega, StockSymbols[omega], 1, true, 0.5, getRandomInt(3000, 6000)); + var omegaStk = new Stock(omega, StockSymbols[omega], getRandomInt(90, 110)/100, true, 0.5, getRandomInt(1e3, 8e3)); StockMarket[omega] = omegaStk; var fns = Locations.Sector12FoodNStuff; - var fnsStk = new Stock(fns, StockSymbols[fns], 0.75, false, 1, getRandomInt(1000, 4000)); + var fnsStk = new Stock(fns, StockSymbols[fns], getRandomInt(70, 80)/100, false, 1, getRandomInt(500, 4.5e3)); StockMarket[fns] = fnsStk; var sigmacosm = "Sigma Cosmetics"; - var sigmacosmStk = new Stock(sigmacosm, StockSymbols[sigmacosm], 2.8, true, 0, getRandomInt(2000, 3000)); + var sigmacosmStk = new Stock(sigmacosm, StockSymbols[sigmacosm], getRandomInt(260, 300)/100, true, 0, getRandomInt(1.5e3, 3.5e3)); StockMarket[sigmacosm] = sigmacosmStk; var joesguns = "Joes Guns"; - var joesgunsStk = new Stock(joesguns, StockSymbols[joesguns], 3.8, true, 1, getRandomInt(500, 1000)); + var joesgunsStk = new Stock(joesguns, StockSymbols[joesguns], getRandomInt(360, 400)/100, true, 1, getRandomInt(250, 1.5e3)); StockMarket[joesguns] = joesgunsStk; var catalyst = "Catalyst Ventures"; - var catalystStk = new Stock(catalyst, StockSymbols[catalyst], 1.45, true, 13.5, getRandomInt(500, 1000)); + var catalystStk = new Stock(catalyst, StockSymbols[catalyst], getRandomInt(120, 175)/100, true, 13.5, getRandomInt(250, 1.5e3)); StockMarket[catalyst] = catalystStk; var microdyne = "Microdyne Technologies"; - var microdyneStk = new Stock(microdyne, StockSymbols[microdyne], 0.75, true, 8, getRandomInt(20000, 25000)); + var microdyneStk = new Stock(microdyne, StockSymbols[microdyne], getRandomInt(70, 80)/100, true, 8, getRandomInt(15e3, 30e3)); StockMarket[microdyne] = microdyneStk; var titanlabs = "Titan Laboratories"; - var titanlabsStk = new Stock(titanlabs, StockSymbols[titanlabs], 0.6, true, 11, getRandomInt(15000, 20000)); + var titanlabsStk = new Stock(titanlabs, StockSymbols[titanlabs], getRandomInt(50, 70)/100, true, 11, getRandomInt(12e3, 24e3)); StockMarket[titanlabs] = titanlabsStk; var orders = {}; diff --git a/src/Terminal.js b/src/Terminal.js index 3001c1164..45429ffc3 100644 --- a/src/Terminal.js +++ b/src/Terminal.js @@ -10,15 +10,17 @@ import {executeDarkwebTerminalCommand, import {Engine} from "./engine"; import {FconfSettings, parseFconfSettings, createFconf} from "./Fconf"; +import {calculateHackingChance, + calculateHackingExpGain, + calculatePercentMoneyHacked, + calculateHackingTime, + calculateGrowTime, + calculateWeakenTime} from "./Hacking"; import {TerminalHelpText, HelpTexts} from "./HelpText"; import {iTutorialNextStep, iTutorialSteps, - iTutorialIsRunning, - currITutorialStep} from "./InteractiveTutorial"; + ITutorial} from "./InteractiveTutorial"; import {showLiterature} from "./Literature"; import {showMessage, Message} from "./Message"; -import {scriptCalculateHackingTime, - scriptCalculateGrowTime, - scriptCalculateWeakenTime} from "./NetscriptEvaluator"; import {killWorkerScript, addWorkerScript} from "./NetscriptWorker"; import numeral from "numeral/min/numeral.min"; import {Player} from "./Player"; @@ -70,7 +72,7 @@ $(document).keydown(function(event) { "[" + (FconfSettings.ENABLE_TIMESTAMPS ? getTimestamp() + " " : "") + Player.getCurrentServer().hostname + - " ~]> " + command + " ~]>
" + command ); if (command.length > 0) { @@ -514,8 +516,10 @@ function determineAllPossibilitiesForTabCompletion(input, index=0) { let Terminal = { //Flags to determine whether the player is currently running a hack or an analyze - hackFlag: false, - analyzeFlag: false, + hackFlag: false, + analyzeFlag: false, + actionStarted: false, + actionTime: 0, commandHistory: [], commandHistoryIndex: 0, @@ -630,6 +634,32 @@ let Terminal = { } }, + startHack: function() { + Terminal.hackFlag = true; + + //Hacking through Terminal should be faster than hacking through a script + Terminal.actionTime = calculateHackingTime(Player.getCurrentServer()) / 4; + Terminal.startAction(); + }, + + startAnalyze: function() { + Terminal.analyzeFlag = true; + Terminal.actionTime = 1; + post("Analyzing system..."); + Terminal.startAction(); + }, + + startAction: function() { + Terminal.actionStarted = true; + + hackProgressPost("Time left:"); + hackProgressBarPost("["); + + //Disable terminal + document.getElementById("terminal-input-td").innerHTML = ''; + $('input[class=terminal-input]').prop('disabled', true); + }, + finishAction: function(cancelled = false) { if (Terminal.hackFlag) { Terminal.finishHack(cancelled); @@ -644,10 +674,10 @@ let Terminal = { var server = Player.getCurrentServer(); //Calculate whether hack was successful - var hackChance = Player.calculateHackingChance(); + var hackChance = calculateHackingChance(server); var rand = Math.random(); console.log("Hack success chance: " + hackChance + ", rand: " + rand); - var expGainedOnSuccess = Player.calculateExpGain(); + var expGainedOnSuccess = calculateHackingExpGain(server); var expGainedOnFailure = (expGainedOnSuccess / 4); if (rand < hackChance) { //Success! if (SpecialServerIps[SpecialServerNames.WorldDaemon] && @@ -659,7 +689,7 @@ let Terminal = { return; } server.manuallyHacked = true; - var moneyGained = Player.calculatePercentMoneyHacked(); + var moneyGained = calculatePercentMoneyHacked(server); moneyGained = Math.floor(server.moneyAvailable * moneyGained); if (moneyGained <= 0) {moneyGained = 0;} //Safety check @@ -690,43 +720,44 @@ let Terminal = { finishAnalyze: function(cancelled = false) { if (cancelled == false) { - post(Player.getCurrentServer().hostname + ": "); - post("Organization name: " + Player.getCurrentServer().organizationName); + let currServ = Player.getCurrentServer(); + post(currServ.hostname + ": "); + post("Organization name: " + currServ.organizationName); var rootAccess = ""; - if (Player.getCurrentServer().hasAdminRights) {rootAccess = "YES";} + if (currServ.hasAdminRights) {rootAccess = "YES";} else {rootAccess = "NO";} post("Root Access: " + rootAccess); - post("Required hacking skill: " + Player.getCurrentServer().requiredHackingSkill); - post("Estimated server security level: " + formatNumber(addOffset(Player.getCurrentServer().hackDifficulty, 5), 3)); - post("Estimated chance to hack: " + formatNumber(addOffset(Player.calculateHackingChance() * 100, 5), 2) + "%"); - post("Estimated time to hack: " + formatNumber(addOffset(Player.calculateHackingTime(), 5), 3) + " seconds"); - post("Estimated total money available on server: $" + formatNumber(addOffset(Player.getCurrentServer().moneyAvailable, 5), 2)); - post("Required number of open ports for NUKE: " + Player.getCurrentServer().numOpenPortsRequired); - if (Player.getCurrentServer().sshPortOpen) { + post("Required hacking skill: " + currServ.requiredHackingSkill); + post("Server security level: " + formatNumber(currServ.hackDifficulty, 3)); + post("Chance to hack: " + formatNumber(calculateHackingChance(currServ) * 100, 2) + "%"); + post("Time to hack: " + formatNumber(calculateHackingTime(currServ), 3) + " seconds"); + post("Total money available on server: $" + formatNumber(currServ.moneyAvailable, 2)); + post("Required number of open ports for NUKE: " + currServ.numOpenPortsRequired); + if (currServ.sshPortOpen) { post("SSH port: Open") } else { post("SSH port: Closed") } - if (Player.getCurrentServer().ftpPortOpen) { + if (currServ.ftpPortOpen) { post("FTP port: Open") } else { post("FTP port: Closed") } - if (Player.getCurrentServer().smtpPortOpen) { + if (currServ.smtpPortOpen) { post("SMTP port: Open") } else { post("SMTP port: Closed") } - if (Player.getCurrentServer().httpPortOpen) { + if (currServ.httpPortOpen) { post("HTTP port: Open") } else { post("HTTP port: Closed") } - if (Player.getCurrentServer().sqlPortOpen) { + if (currServ.sqlPortOpen) { post("SQL port: Open") } else { post("SQL port: Closed") @@ -821,11 +852,11 @@ let Terminal = { if (commandArray.length == 0) {return;} /****************** Interactive Tutorial Terminal Commands ******************/ - if (iTutorialIsRunning) { + if (ITutorial.isRunning) { var foodnstuffServ = GetServerByHostname("foodnstuff"); if (foodnstuffServ == null) {throw new Error("Could not get foodnstuff server"); return;} - switch(currITutorialStep) { + switch(ITutorial.currStep) { case iTutorialSteps.TerminalHelp: if (commandArray[0] == "help") { post(TerminalHelpText); @@ -875,16 +906,7 @@ let Terminal = { if (commandArray.length != 1) { post("Incorrect usage of analyze command. Usage: analyze"); return; } - //Analyze the current server for information - Terminal.analyzeFlag = true; - post("Analyzing system..."); - hackProgressPost("Time left:"); - hackProgressBarPost("["); - Player.analyze(); - - //Disable terminal - document.getElementById("terminal-input-td").innerHTML = ''; - $('input[class=terminal-input]').prop('disabled', true); + Terminal.startAnalyze(); iTutorialNextStep(); } else { post("Bad command. Please follow the tutorial"); @@ -900,14 +922,7 @@ let Terminal = { break; case iTutorialSteps.TerminalManualHack: if (commandArray.length == 1 && commandArray[0] == "hack") { - Terminal.hackFlag = true; - hackProgressPost("Time left:"); - hackProgressBarPost("["); - Player.hack(); - - //Disable terminal - document.getElementById("terminal-input-td").innerHTML = ''; - $('input[class=terminal-input]').prop('disabled', true); + Terminal.startHack(); iTutorialNextStep(); } else {post("Bad command. Please follow the tutorial");} break; @@ -978,16 +993,7 @@ let Terminal = { if (commandArray.length != 1) { post("Incorrect usage of analyze command. Usage: analyze"); return; } - //Analyze the current server for information - Terminal.analyzeFlag = true; - post("Analyzing system..."); - hackProgressPost("Time left:"); - hackProgressBarPost("["); - Player.analyze(); - - //Disable terminal - document.getElementById("terminal-input-td").innerHTML = ''; - $('input[class=terminal-input]').prop('disabled', true); + Terminal.startAnalyze(); break; case "buy": if (SpecialServerIps.hasOwnProperty("Darkweb Server")) { @@ -1140,14 +1146,7 @@ let Terminal = { } else if (Player.getCurrentServer().requiredHackingSkill > Player.hacking_skill) { post("Your hacking skill is not high enough to attempt hacking this machine. Try analyzing the machine to determine the required hacking skill"); } else { - Terminal.hackFlag = true; - hackProgressPost("Time left:"); - hackProgressBarPost("["); - Player.hack(); - - //Disable terminal - document.getElementById("terminal-input-td").innerHTML = ''; - $('input[class=terminal-input]').prop('disabled', true); + Terminal.startHack(); } break; case "help": @@ -1964,9 +1963,9 @@ let Terminal = { post("Server base security level: " + targetServer.baseDifficulty); post("Server current security level: " + targetServer.hackDifficulty); post("Server growth rate: " + targetServer.serverGrowth); - post("Netscript hack() execution time: " + formatNumber(scriptCalculateHackingTime(targetServer), 1) + "s"); - post("Netscript grow() execution time: " + formatNumber(scriptCalculateGrowTime(targetServer)/1000, 1) + "s"); - post("Netscript weaken() execution time: " + formatNumber(scriptCalculateWeakenTime(targetServer)/1000, 1) + "s"); + post("Netscript hack() execution time: " + formatNumber(calculateHackingTime(targetServer), 1) + "s"); + post("Netscript grow() execution time: " + formatNumber(calculateGrowTime(targetServer), 1) + "s"); + post("Netscript weaken() execution time: " + formatNumber(calculateWeakenTime(targetServer), 1) + "s"); }; programHandlers[Programs.AutoLink.name] = () => { post("This executable cannot be run."); @@ -2125,6 +2124,7 @@ let Terminal = { } } + post("ERROR: No such script"); } }; diff --git a/src/engine.js b/src/engine.js index 22f6c2d6a..db60b97ee 100644 --- a/src/engine.js +++ b/src/engine.js @@ -150,7 +150,7 @@ $(document).keydown(function(e) { } }); -let Engine = { +const Engine = { version: "", Debug: true, overview: new CharacterOverview(), @@ -570,7 +570,7 @@ let Engine = { var intText = ""; if (Player.intelligence > 0) { - intText = 'Intelligence: ' + (Player.intelligence).toLocaleString() + "


"; + intText = 'Intelligence: ' + (Player.intelligence).toLocaleString() + '
'; } let bitNodeTimeText = ""; @@ -584,21 +584,21 @@ let Engine = { 'Current City: ' + Player.city + '

' + 'Employer: ' + Player.companyName + '
' + 'Job Title: ' + companyPosition + '

' + - 'Money: $' + formatNumber(Player.money.toNumber(), 2)+ '


' + + 'Money: $' + formatNumber(Player.money.toNumber(), 2) + '


' + 'Stats

' + 'Hacking Level: ' + (Player.hacking_skill).toLocaleString() + - " (" + numeral(Player.hacking_exp).format('(0.000a)') + ' experience)
' + + ' (' + numeral(Player.hacking_exp).format('(0.000a)') + ' experience)
' + 'Strength: ' + (Player.strength).toLocaleString() + - " (" + numeral(Player.strength_exp).format('(0.000a)') + ' experience)
' + + ' (' + numeral(Player.strength_exp).format('(0.000a)') + ' experience)
' + 'Defense: ' + (Player.defense).toLocaleString() + - " (" + numeral(Player.defense_exp).format('(0.000a)')+ ' experience)
' + + ' (' + numeral(Player.defense_exp).format('(0.000a)')+ ' experience)
' + 'Dexterity: ' + (Player.dexterity).toLocaleString() + - " (" + numeral(Player.dexterity_exp).format('(0.000a)') + ' experience)
' + + ' (' + numeral(Player.dexterity_exp).format('(0.000a)') + ' experience)
' + 'Agility: ' + (Player.agility).toLocaleString() + - " (" + numeral(Player.agility_exp).format('(0.000a)') + ' experience)
' + + ' (' + numeral(Player.agility_exp).format('(0.000a)') + ' experience)
' + 'Charisma: ' + (Player.charisma).toLocaleString() + - " (" + numeral(Player.charisma_exp).format('(0.000a)') + ' experience)
' + - intText + + ' (' + numeral(Player.charisma_exp).format('(0.000a)') + ' experience)
' + + intText + '

' + 'Multipliers

' + 'Hacking Chance multiplier: ' + formatNumber(Player.hacking_chance_mult * 100, 2) + '%
' + 'Hacking Speed multiplier: ' + formatNumber(Player.hacking_speed_mult * 100, 2) + '%
' + @@ -923,14 +923,14 @@ let Engine = { Player.playtimeSinceLastBitnode += time; //Start Manual hack - if (Player.startAction == true) { - Engine._totalActionTime = Player.actionTime; - Engine._actionTimeLeft = Player.actionTime; + if (Terminal.actionStarted === true) { + Engine._totalActionTime = Terminal.actionTime; + Engine._actionTimeLeft = Terminal.actionTime; Engine._actionInProgress = true; Engine._actionProgressBarCount = 1; Engine._actionProgressStr = "[ ]"; Engine._actionTimeStr = "Time left: "; - Player.startAction = false; + Terminal.actionStarted = false; } //Working @@ -1324,9 +1324,9 @@ let Engine = { Player.lastUpdate = Engine._lastUpdate; Engine.start(); //Run main game loop and Scripts loop removeLoadingScreen(); - dialogBoxCreate("While you were offline, your scripts generated $" + - formatNumber(offlineProductionFromScripts, 2) + " and your Hacknet Nodes generated $" + - formatNumber(offlineProductionFromHacknetNodes, 2)); + dialogBoxCreate("While you were offline, your scripts generated $" + + formatNumber(offlineProductionFromScripts, 2) + " and your Hacknet Nodes generated $" + + formatNumber(offlineProductionFromHacknetNodes, 2) + ""); //Close main menu accordions for loaded game var visibleMenuTabs = [terminal, createScript, activeScripts, stats, hacknetnodes, city, tutorial, options, dev]; diff --git a/src/index.html b/src/index.html new file mode 100644 index 000000000..da15a0733 --- /dev/null +++ b/src/index.html @@ -0,0 +1,1024 @@ + + + + + <%= htmlWebpackPlugin.options.title %> + + + + + + + + + + + + + <% +if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %> + <% +} %> + + + + + +
+
+
Loading Bitburner...
+
+ + diff --git a/tslint.json b/tslint.json index f1ee12cc8..cd0164ccf 100644 --- a/tslint.json +++ b/tslint.json @@ -48,6 +48,7 @@ "ignore-params", "ignore-properties" ], + "no-magic-numbers": [true, -1, 0, 1, 2, 10, 100], "no-null-keyword": false, "no-unsafe-any": false, "object-literal-key-quotes": [ @@ -59,6 +60,7 @@ "allow-declarations", "allow-named-functions" ], + "triple-equals": [true, "allow-null-check", "allow-undefined-check"], "typedef": [ true, "call-signatures", @@ -73,4 +75,4 @@ ] }, "rulesDirectory": [] -} \ No newline at end of file +} diff --git a/utils/InfiltrationBox.js b/utils/InfiltrationBox.js index efd86089b..8c1290179 100644 --- a/utils/InfiltrationBox.js +++ b/utils/InfiltrationBox.js @@ -57,9 +57,9 @@ function infiltrationBoxCreate(inst) { CONSTANTS.InfiltrationRepValue * BitNodeMultipliers.InfiltrationRep; var moneyValue = totalValue * CONSTANTS.InfiltrationMoneyValue * BitNodeMultipliers.InfiltrationMoney; infiltrationSetText("You can sell the classified documents and secrets " + - "you stole from " + inst.companyName + " for $" + - formatNumber(moneyValue, 2) + " on the black market or you can give it " + - "to a faction to gain " + formatNumber(facValue, 3) + " reputation with " + + "you stole from " + inst.companyName + " for $" + + formatNumber(moneyValue, 2) + " on the black market or you can give it " + + "to a faction to gain " + formatNumber(facValue, 3) + " reputation with " + "that faction."); var selector = document.getElementById("infiltration-faction-select"); selector.innerHTML = ""; @@ -85,7 +85,7 @@ function infiltrationBoxCreate(inst) { if (!e.isTrusted) {return false;} Player.gainMoney(moneyValue); dialogBoxCreate("You sold the classified information you stole from " + inst.companyName + - " for $" + moneyValue + " on the black market!

" + + " for $" + formatNumber(moneyValue, 2) + " on the black market!

" + "You gained:
" + formatNumber(inst.hackingExpGained, 3) + " hacking exp
" + formatNumber(inst.strExpGained, 3) + " str exp
" + @@ -111,7 +111,7 @@ function infiltrationBoxCreate(inst) { } faction.playerReputation += facValue; dialogBoxCreate("You gave the classified information you stole from " + inst.companyName + - " to " + facName + " and gained " + formatNumber(facValue, 3) + " reputation with the faction.

" + + " to " + facName + " and gained " + formatNumber(facValue, 3) + " reputation with the faction.

" + "You gained:
" + formatNumber(inst.hackingExpGained, 3) + " hacking exp
" + formatNumber(inst.strExpGained, 3) + " str exp
" + diff --git a/utils/JSONReviver.d.ts b/utils/JSONReviver.d.ts index 08ff98bd4..f4405df83 100644 --- a/utils/JSONReviver.d.ts +++ b/utils/JSONReviver.d.ts @@ -7,4 +7,4 @@ export function Generic_toJSON(ctorName: string, obj: object, keys?: string[]): export function Reviver(key, value: IReviverValue); export namespace Reviver { export var constructors: any; -} \ No newline at end of file +} diff --git a/utils/helpers/createProgressBarText.ts b/utils/helpers/createProgressBarText.ts index 8cd628277..37f433a00 100644 --- a/utils/helpers/createProgressBarText.ts +++ b/utils/helpers/createProgressBarText.ts @@ -34,11 +34,14 @@ export function createProgressBarText(params: IProgressBarConfiguration) { }; // tslint:disable-next-line:prefer-object-spread - const derivedParams: IProgressBarConfigurationMaterialized = Object.assign({}, defaultParams, params); + const derived: IProgressBarConfigurationMaterialized = Object.assign({}, defaultParams, params); + // Ensure it is 0..1 + derived.progress = Math.max(Math.min(derived.progress, 1), 0); - const bars: number = Math.floor(derivedParams.progress / (1 / derivedParams.totalTicks)); - const dashes: number = derivedParams.totalTicks - bars; + // This way there is always at least one bar filled in... + const bars: number = Math.max(Math.floor(derived.progress / (1 / derived.totalTicks)), 1); + const dashes: number = Math.max(derived.totalTicks - bars, 0); // String.prototype.repeat isn't completley supported, but good enough for our purposes - return `[${"|".repeat(bars + 1)}${"-".repeat(dashes + 1)}]`; + return `[${"|".repeat(bars)}${"-".repeat(dashes)}]`; } diff --git a/utils/helpers/getTimestamp.ts b/utils/helpers/getTimestamp.ts index fd0786166..6e7ee119f 100644 --- a/utils/helpers/getTimestamp.ts +++ b/utils/helpers/getTimestamp.ts @@ -1,3 +1,6 @@ +/** + * Returns a MM/DD HH:MM timestamp for the current time + */ export function getTimestamp() { const d: Date = new Date(); // A negative slice value takes from the end of the string rather than the beginning. diff --git a/webpack.config.js b/webpack.config.js index 559733c26..666503508 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,6 +1,7 @@ var path = require('path'); var webpack = require('webpack'); var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = (env, argv) => ({ plugins: [ @@ -16,6 +17,45 @@ module.exports = (env, argv) => ({ jQuery: "jquery", $: "jquery" }), + new HtmlWebpackPlugin({ + title: "Bitburner" + (argv.mode === 'development' ? ' - development' : ""), + template: "src/index.html", + favicon: "favicon.ico", + googleAnalytics: { + trackingId: 'UA-100157497-1' + }, + meta: {}, + minify: argv.mode === 'development' ? false : { + collapseBooleanAttributes: true, + collapseInlineTagWhitespace: false, + collapseWhitespace: false, + conservativeCollapse: false, + html5: true, + includeAutoGeneratedTags: false, + keepClosingSlash: true, + minifyCSS: false, + minifyJS: false, + minifyURLs: false, + preserveLineBreaks: false, + preventAttributesEscaping: false, + processConditionalComments: false, + quoteCharacter: "\"", + removeAttributeQuotes: false, + removeComments: false, + removeEmptyAttributes: false, + removeEmptyElements: false, + removeOptionalTags: false, + removeScriptTypeAttributes: false, + removeStyleLinkTypeAttributes: false, + removeTagWhitespace: false, + sortAttributes: false, + sortClassName: false, + useShortDoctype: false + }, + excludeChunks: [ + "tests/tests" + ] + }), new MiniCssExtractPlugin({ filename: "[name].css", chunkFilename: "[id].css"