Merge pull request #961 from danielyxie/dev

v0.51.9
This commit is contained in:
hydroflame 2021-05-18 00:01:35 -04:00 committed by GitHub
commit a7389f63b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 645 additions and 502 deletions

14
.dockerignore Normal file

@ -0,0 +1,14 @@
node_modules/
.git
.gitattributes
.gitignore
.editorconfig
.dockerignore
Dockerfile
docker-compose.yml
*.md
Quotes.txt
netscript_tests/

35
Dockerfile Normal file

@ -0,0 +1,35 @@
FROM node:15.14.0 AS base
WORKDIR /app
# Scripts used in the npm preinstall hook
COPY scripts/engines-check.js scripts/semver.js scripts/
# Adding our dependencies and install before adding the rest of the files
# This prevents reinstallation of npm packages for every subsequent code modification
ENV npm_config_update_notifier=false
COPY package.json package-lock.json ./
RUN npm ci --loglevel=error --no-audit --no-fund && npm rebuild node-sass
# Adding all the remaining source files
COPY . .
# We need more than the default 512MB otherwise webpack will throw 'heap out of memory' exceptions
# https://nodejs.org/api/cli.html#cli_max_old_space_size_size_in_megabytes
ENV NODE_OPTIONS=--max-old-space-size=1536
FROM base AS dev
# This is the main development build using the file watcher if you mount volumes
USER node
EXPOSE 8000
CMD npm run start:container
FROM base AS prod-dist
# We'll simply build the production dist files here to later reuse in a simple webserver
RUN npm run build
FROM nginx:1.20.0-alpine AS prod
WORKDIR /usr/share/nginx/html
COPY --from=prod-dist /app/dist ./dist
COPY --from=prod-dist /app/index.html /app/favicon.ico /app/license.txt ./
EXPOSE 80

File diff suppressed because one or more lines are too long

@ -1,2 +1,2 @@
!function(n){function t(t){for(var e,i,f=t[0],c=t[1],l=t[2],p=0,s=[];p<f.length;p++)i=f[p],u[i]&&s.push(u[i][0]),u[i]=0;for(e in c)Object.prototype.hasOwnProperty.call(c,e)&&(n[e]=c[e]);for(a&&a(t);s.length;)s.shift()();return r.push.apply(r,l||[]),o()}function o(){for(var n,t=0;t<r.length;t++){for(var o=r[t],e=!0,f=1;f<o.length;f++){var c=o[f];0!==u[c]&&(e=!1)}e&&(r.splice(t--,1),n=i(i.s=o[0]))}return n}var e={},u={1:0},r=[];function i(t){if(e[t])return e[t].exports;var o=e[t]={i:t,l:!1,exports:{}};return n[t].call(o.exports,o,o.exports,i),o.l=!0,o.exports}i.m=n,i.c=e,i.d=function(n,t,o){i.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:o})},i.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},i.t=function(n,t){if(1&t&&(n=i(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var o=Object.create(null);if(i.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var e in n)i.d(o,e,function(t){return n[t]}.bind(null,e));return o},i.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return i.d(t,"a",t),t},i.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},i.p="";var f=window.webpackJsonp=window.webpackJsonp||[],c=f.push.bind(f);f.push=t,f=f.slice();for(var l=0;l<f.length;l++)t(f[l]);var a=c;r.push([841,0]),o()}({780:function(n,t,o){},782:function(n,t,o){},784:function(n,t,o){},786:function(n,t,o){},788:function(n,t,o){},790:function(n,t,o){},792:function(n,t,o){},794:function(n,t,o){},796:function(n,t,o){},798:function(n,t,o){},800:function(n,t,o){},802:function(n,t,o){},804:function(n,t,o){},806:function(n,t,o){},808:function(n,t,o){},810:function(n,t,o){},812:function(n,t,o){},814:function(n,t,o){},816:function(n,t,o){},818:function(n,t,o){},820:function(n,t,o){},822:function(n,t,o){},824:function(n,t,o){},826:function(n,t,o){},828:function(n,t,o){},830:function(n,t,o){},832:function(n,t,o){},834:function(n,t,o){},836:function(n,t,o){},838:function(n,t,o){},841:function(n,t,o){"use strict";o.r(t);o(840),o(838),o(836),o(834),o(832),o(830),o(828),o(826),o(824),o(822),o(820),o(818),o(816),o(814),o(812),o(810),o(808),o(806),o(804),o(802),o(800),o(798),o(796),o(794),o(792),o(790),o(788),o(786),o(784),o(782),o(780)}}); !function(n){function t(t){for(var e,i,f=t[0],c=t[1],l=t[2],p=0,s=[];p<f.length;p++)i=f[p],u[i]&&s.push(u[i][0]),u[i]=0;for(e in c)Object.prototype.hasOwnProperty.call(c,e)&&(n[e]=c[e]);for(a&&a(t);s.length;)s.shift()();return r.push.apply(r,l||[]),o()}function o(){for(var n,t=0;t<r.length;t++){for(var o=r[t],e=!0,f=1;f<o.length;f++){var c=o[f];0!==u[c]&&(e=!1)}e&&(r.splice(t--,1),n=i(i.s=o[0]))}return n}var e={},u={1:0},r=[];function i(t){if(e[t])return e[t].exports;var o=e[t]={i:t,l:!1,exports:{}};return n[t].call(o.exports,o,o.exports,i),o.l=!0,o.exports}i.m=n,i.c=e,i.d=function(n,t,o){i.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:o})},i.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},i.t=function(n,t){if(1&t&&(n=i(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var o=Object.create(null);if(i.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var e in n)i.d(o,e,function(t){return n[t]}.bind(null,e));return o},i.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return i.d(t,"a",t),t},i.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},i.p="";var f=window.webpackJsonp=window.webpackJsonp||[],c=f.push.bind(f);f.push=t,f=f.slice();for(var l=0;l<f.length;l++)t(f[l]);var a=c;r.push([842,0]),o()}({781:function(n,t,o){},783:function(n,t,o){},785:function(n,t,o){},787:function(n,t,o){},789:function(n,t,o){},791:function(n,t,o){},793:function(n,t,o){},795:function(n,t,o){},797:function(n,t,o){},799:function(n,t,o){},801:function(n,t,o){},803:function(n,t,o){},805:function(n,t,o){},807:function(n,t,o){},809:function(n,t,o){},811:function(n,t,o){},813:function(n,t,o){},815:function(n,t,o){},817:function(n,t,o){},819:function(n,t,o){},821:function(n,t,o){},823:function(n,t,o){},825:function(n,t,o){},827:function(n,t,o){},829:function(n,t,o){},831:function(n,t,o){},833:function(n,t,o){},835:function(n,t,o){},837:function(n,t,o){},839:function(n,t,o){},842:function(n,t,o){"use strict";o.r(t);o(841),o(839),o(837),o(835),o(833),o(831),o(829),o(827),o(825),o(823),o(821),o(819),o(817),o(815),o(813),o(811),o(809),o(807),o(805),o(803),o(801),o(799),o(797),o(795),o(793),o(791),o(789),o(787),o(785),o(783),o(781)}});
//# sourceMappingURL=engineStyle.bundle.js.map //# sourceMappingURL=engineStyle.bundle.js.map

36
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

@ -3,8 +3,61 @@
Changelog Changelog
========= =========
v0.51.9 - 2021-05-17 offline progress and exports! (hydroflame & community)
---------------------------------------------------------------
v0.51.8 - 2021-05-07 It was there all along (hydroflame) **Alias**
* several commands can be included in 1 alias. Recursive alias now work to
a depth of 10. (@Dawe)
**Offline**
* Offline money gain has been reworked (it is more generous)
* If you're not working anywhere and go offline the game will work for you
at all your factions evenly.
**Export**
* Exporting now gives +1 favor to all joined factions every 24h.
**Corp**
* Self-fund with an invalid name no longer takes away 150b anyway.
* Can no longer export negative amount
**Bladeburner**
* No longer waste overflowing time.
**Text Editors**
* All settings will now be saved and loaded correctly.
**Terminal**
* 'scan' now works for servers that are more than 21 character long.
**Misc.**
* ls now correctly lists all files.
* importing auto save+reloads (@Dawe)
* Fix a bug where .fconf could not be created
* Fix formatting inconsistencies for some logs of netscript functions.
* Fix a bug where Cashroot starter kit would appear as [object Object] in
confirmation dialog.
* Fix some ram not displayed as 0.00GB
* Fix error message throw undefined variable error
* City hall now has some generic text if you can't create a corp yet.
* Deleting a file without extension now returns an appropriate error message.
* Fixed an issue where bladeburner would miscalculate the cost of hospitalization.
* It is now possible to suppress bladeburner "action stopped" popup.
* Updated several dependencies (big who cares, I know)
* ls no longer prints lingering newline.
* Money earned/spent by sleeves is now tracked under Character>Money
v0.51.8 - 2021-05-07 It was there all along (hydroflame & community)
-------------------------------------------------------- --------------------------------------------------------
**Servers** **Servers**
@ -60,8 +113,8 @@ v0.51.8 - 2021-05-07 It was there all along (hydroflame)
* script income transfers to parent on death. This helps keep track of * script income transfers to parent on death. This helps keep track of
income for scripts that spawn short lived scripts. income for scripts that spawn short lived scripts.
v0.51.7 - 2021-04-28 n00dles v0.51.7 - 2021-04-28 n00dles (hydroflame & community)
---------------------------- -----------------------------------------
**Tutorial servers** **Tutorial servers**
@ -118,7 +171,7 @@ v0.51.7 - 2021-04-28 n00dles
* Faction invite text says "Decide later"/"Join!" instead of "No"/"Yes" * Faction invite text says "Decide later"/"Join!" instead of "No"/"Yes"
v0.51.6 - 2021-04-28 Backdoor! (Community) v0.51.6 - 2021-04-28 Backdoor! (hydroflame & community)
------------------------------------------ ------------------------------------------
**Backdoor** **Backdoor**

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

15
docker-compose.yml Normal file

@ -0,0 +1,15 @@
version: "3.4"
services:
web:
image: bitburner:dev
build:
context: .
dockerfile: Dockerfile
target: dev
ports:
- "8000:8000"
volumes:
- ./src:/app/src
- ./css:/app/css
- ./utils:/app/utils
- ./test:/app/test

@ -506,6 +506,16 @@
<input class="optionCheckbox" type="checkbox" name="settingsSuppressHospitalizationPopup" id="settingsSuppressHospitalizationPopup"> <input class="optionCheckbox" type="checkbox" name="settingsSuppressHospitalizationPopup" id="settingsSuppressHospitalizationPopup">
</fieldset> </fieldset>
<!-- Suppress Bladeburner popups -->
<fieldset>
<label for="settingsSuppressBladeburnerPopup" class="tooltip">Suppress Bladeburner Popup:
<span class="tooltiptext">
If this is set, then having your Bladeburner actions interrupted by being busy with something else will not display a popup message.
</span>
</label>
<input class="optionCheckbox" type="checkbox" name="settingsSuppressBladeburnerPopup" id="settingsSuppressBladeburnerPopup">
</fieldset>
<!-- Disable Terminal and Navigation Shortcuts --> <!-- Disable Terminal and Navigation Shortcuts -->
<fieldset> <fieldset>
<label for="settingsDisableHotkeys" class="tooltip">Disable Hotkeys: <label for="settingsDisableHotkeys" class="tooltip">Disable Hotkeys:

86
package-lock.json generated

@ -1,11 +1,11 @@
{ {
"name": "bitburner", "name": "bitburner",
"version": "0.51.5", "version": "0.51.8",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"version": "0.51.5", "version": "0.51.8",
"hasInstallScript": true, "hasInstallScript": true,
"license": "SEE LICENSE IN license.txt", "license": "SEE LICENSE IN license.txt",
"dependencies": { "dependencies": {
@ -13,7 +13,7 @@
"@types/numeral": "0.0.25", "@types/numeral": "0.0.25",
"@types/react": "^16.8.6", "@types/react": "^16.8.6",
"@types/react-dom": "^16.8.2", "@types/react-dom": "^16.8.2",
"acorn": "^6.2.0", "acorn": "^6.4.1",
"acorn-walk": "^6.2.0", "acorn-walk": "^6.2.0",
"ajv": "^5.1.5", "ajv": "^5.1.5",
"ajv-keywords": "^2.0.0", "ajv-keywords": "^2.0.0",
@ -21,14 +21,14 @@
"async": "^2.6.1", "async": "^2.6.1",
"autosize": "^4.0.2", "autosize": "^4.0.2",
"brace": "^0.11.1", "brace": "^0.11.1",
"codemirror": "^5.43.0", "codemirror": "^5.58.2",
"decimal.js": "7.2.3", "decimal.js": "7.2.3",
"enhanced-resolve": "^4.0.0", "enhanced-resolve": "^4.0.0",
"escodegen": "^1.11.0", "escodegen": "^1.11.0",
"escope": "^3.6.0", "escope": "^3.6.0",
"file-saver": "^1.3.8", "file-saver": "^1.3.8",
"interpret": "^1.0.0", "interpret": "^1.0.0",
"jquery": "^3.3.1", "jquery": "^3.5.0",
"jshint": "^2.10.2", "jshint": "^2.10.2",
"json-loader": "^0.5.4", "json-loader": "^0.5.4",
"jsplumb": "^2.6.8", "jsplumb": "^2.6.8",
@ -1545,9 +1545,9 @@
} }
}, },
"node_modules/acorn": { "node_modules/acorn": {
"version": "6.2.0", "version": "6.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.2.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
"integrity": "sha512-8oe72N3WPMjA+2zVG71Ia0nXZ8DpQH+QyyHO+p06jT8eg8FGG3FbcUIi8KziHlAfheJQZeoqbvq1mQSQHXKYLw==", "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@ -1565,18 +1565,6 @@
"acorn-walk": "^6.0.1" "acorn-walk": "^6.0.1"
} }
}, },
"node_modules/acorn-globals/node_modules/acorn": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz",
"integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/acorn-jsx": { "node_modules/acorn-jsx": {
"version": "5.3.1", "version": "5.3.1",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz",
@ -3516,9 +3504,9 @@
} }
}, },
"node_modules/codemirror": { "node_modules/codemirror": {
"version": "5.43.0", "version": "5.58.2",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.43.0.tgz", "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.58.2.tgz",
"integrity": "sha512-mljwQWUaWIf85I7QwTBryF2ASaIvmYAL4s5UCanCJFfKeXOKhrqdHWdHiZWAMNT+hjLTCnVx2S/SYTORIgxsgA==" "integrity": "sha512-K/hOh24cCwRutd1Mk3uLtjWzNISOkm4fvXiMO7LucCrqbh6aJDdtqUziim3MZUI6wOY0rvY1SlL1Ork01uMy6w=="
}, },
"node_modules/collapse-white-space": { "node_modules/collapse-white-space": {
"version": "1.0.4", "version": "1.0.4",
@ -8542,9 +8530,9 @@
"dev": true "dev": true
}, },
"node_modules/jquery": { "node_modules/jquery": {
"version": "3.3.1", "version": "3.5.0",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.0.tgz",
"integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" "integrity": "sha512-Xb7SVYMvygPxbFMpTFQiHh1J7HClEaThguL15N/Gg37Lri/qKyhRGZYzHRyLH8Stq3Aow0LsHO2O2ci86fCrNQ=="
}, },
"node_modules/js-base64": { "node_modules/js-base64": {
"version": "2.4.3", "version": "2.4.3",
@ -8637,18 +8625,6 @@
"integrity": "sha1-a9KZwTsMRiay2iwDk81DhdYGrLk=", "integrity": "sha1-a9KZwTsMRiay2iwDk81DhdYGrLk=",
"dev": true "dev": true
}, },
"node_modules/jsdom/node_modules/acorn": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz",
"integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/jsesc": { "node_modules/jsesc": {
"version": "0.5.0", "version": "0.5.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
@ -20259,9 +20235,9 @@
} }
}, },
"acorn": { "acorn": {
"version": "6.2.0", "version": "6.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.2.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
"integrity": "sha512-8oe72N3WPMjA+2zVG71Ia0nXZ8DpQH+QyyHO+p06jT8eg8FGG3FbcUIi8KziHlAfheJQZeoqbvq1mQSQHXKYLw==" "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA=="
}, },
"acorn-globals": { "acorn-globals": {
"version": "4.3.2", "version": "4.3.2",
@ -20271,14 +20247,6 @@
"requires": { "requires": {
"acorn": "^6.0.1", "acorn": "^6.0.1",
"acorn-walk": "^6.0.1" "acorn-walk": "^6.0.1"
},
"dependencies": {
"acorn": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz",
"integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==",
"dev": true
}
} }
}, },
"acorn-jsx": { "acorn-jsx": {
@ -21942,9 +21910,9 @@
"dev": true "dev": true
}, },
"codemirror": { "codemirror": {
"version": "5.43.0", "version": "5.58.2",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.43.0.tgz", "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.58.2.tgz",
"integrity": "sha512-mljwQWUaWIf85I7QwTBryF2ASaIvmYAL4s5UCanCJFfKeXOKhrqdHWdHiZWAMNT+hjLTCnVx2S/SYTORIgxsgA==" "integrity": "sha512-K/hOh24cCwRutd1Mk3uLtjWzNISOkm4fvXiMO7LucCrqbh6aJDdtqUziim3MZUI6wOY0rvY1SlL1Ork01uMy6w=="
}, },
"collapse-white-space": { "collapse-white-space": {
"version": "1.0.4", "version": "1.0.4",
@ -26149,9 +26117,9 @@
} }
}, },
"jquery": { "jquery": {
"version": "3.3.1", "version": "3.5.0",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.0.tgz",
"integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" "integrity": "sha512-Xb7SVYMvygPxbFMpTFQiHh1J7HClEaThguL15N/Gg37Lri/qKyhRGZYzHRyLH8Stq3Aow0LsHO2O2ci86fCrNQ=="
}, },
"js-base64": { "js-base64": {
"version": "2.4.3", "version": "2.4.3",
@ -26225,14 +26193,6 @@
"whatwg-url": "^7.0.0", "whatwg-url": "^7.0.0",
"ws": "^6.1.2", "ws": "^6.1.2",
"xml-name-validator": "^3.0.0" "xml-name-validator": "^3.0.0"
},
"dependencies": {
"acorn": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz",
"integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==",
"dev": true
}
} }
}, },
"jsdom-global": { "jsdom-global": {

@ -10,7 +10,7 @@
"@types/numeral": "0.0.25", "@types/numeral": "0.0.25",
"@types/react": "^16.8.6", "@types/react": "^16.8.6",
"@types/react-dom": "^16.8.2", "@types/react-dom": "^16.8.2",
"acorn": "^6.2.0", "acorn": "^6.4.1",
"acorn-walk": "^6.2.0", "acorn-walk": "^6.2.0",
"ajv": "^5.1.5", "ajv": "^5.1.5",
"ajv-keywords": "^2.0.0", "ajv-keywords": "^2.0.0",
@ -18,14 +18,14 @@
"async": "^2.6.1", "async": "^2.6.1",
"autosize": "^4.0.2", "autosize": "^4.0.2",
"brace": "^0.11.1", "brace": "^0.11.1",
"codemirror": "^5.43.0", "codemirror": "^5.58.2",
"decimal.js": "7.2.3", "decimal.js": "7.2.3",
"enhanced-resolve": "^4.0.0", "enhanced-resolve": "^4.0.0",
"escodegen": "^1.11.0", "escodegen": "^1.11.0",
"escope": "^3.6.0", "escope": "^3.6.0",
"file-saver": "^1.3.8", "file-saver": "^1.3.8",
"interpret": "^1.0.0", "interpret": "^1.0.0",
"jquery": "^3.3.1", "jquery": "^3.5.0",
"jshint": "^2.10.2", "jshint": "^2.10.2",
"json-loader": "^0.5.4", "json-loader": "^0.5.4",
"jsplumb": "^2.6.8", "jsplumb": "^2.6.8",
@ -113,6 +113,7 @@
}, },
"scripts": { "scripts": {
"start:dev": "webpack-dev-server --progress --env.devServer --mode development", "start:dev": "webpack-dev-server --progress --env.devServer --mode development",
"start:container": "webpack-dev-server --progress --env.devServer --mode development --env.runInContainer",
"build": "webpack --mode production", "build": "webpack --mode production",
"build:dev": "webpack --mode development", "build:dev": "webpack --mode development",
"build:test": "webpack --config webpack.config-test.js", "build:test": "webpack --config webpack.config-test.js",
@ -121,8 +122,9 @@
"lint:style": "stylelint --fix ./css/*", "lint:style": "stylelint --fix ./css/*",
"preinstall": "node ./scripts/engines-check.js", "preinstall": "node ./scripts/engines-check.js",
"test": "mochapack --webpack-config webpack.config-test.js -r jsdom-global/register ./test/index.js", "test": "mochapack --webpack-config webpack.config-test.js -r jsdom-global/register ./test/index.js",
"test:container": "mochapack --webpack-config webpack.config-test.js --slow 2000 --timeout 10000 -r jsdom-global/register ./test/index.js",
"watch": "webpack --watch --mode production", "watch": "webpack --watch --mode production",
"watch:dev": "webpack --watch --mode development" "watch:dev": "webpack --watch --mode development"
}, },
"version": "0.51.8" "version": "0.51.9"
} }

@ -51,14 +51,14 @@ function addAlias(name: string, value: string): void {
if (name in GlobalAliases) { if (name in GlobalAliases) {
delete GlobalAliases[name]; delete GlobalAliases[name];
} }
Aliases[name] = value; Aliases[name] = value.trim();
} }
function addGlobalAlias(name: string, value: string): void { function addGlobalAlias(name: string, value: string): void {
if (name in Aliases){ if (name in Aliases){
delete Aliases[name]; delete Aliases[name];
} }
GlobalAliases[name] = value; GlobalAliases[name] = value.trim();
} }
function getAlias(name: string): string | null { function getAlias(name: string): string | null {
@ -97,22 +97,29 @@ export function removeAlias(name: string): boolean {
export function substituteAliases(origCommand: string): string { export function substituteAliases(origCommand: string): string {
const commandArray = origCommand.split(" "); const commandArray = origCommand.split(" ");
if (commandArray.length > 0){ if (commandArray.length > 0){
// For the unalias command, dont substite // For the alias and unalias commands, dont substite
if (commandArray[0] === "unalias") { return commandArray.join(" "); } if (commandArray[0] === "unalias" || commandArray[0] === "alias") { return commandArray.join(" "); }
const alias = getAlias(commandArray[0]); let somethingSubstituted = true;
if (alias != null) { let depth = 0;
commandArray[0] = alias;
} else { while(somethingSubstituted && depth < 10){
const alias = getGlobalAlias(commandArray[0]); depth++;
somethingSubstituted = false
const alias = getAlias(commandArray[0])?.split(" ");
if (alias != null) { if (alias != null) {
commandArray[0] = alias; somethingSubstituted = true
commandArray.splice(0, 1, ...alias);
//commandArray[0] = alias;
} }
} for (let i = 0; i < commandArray.length; ++i) {
for (let i = 0; i < commandArray.length; ++i) { const alias = getGlobalAlias(commandArray[i])?.split(" ");
const alias = getGlobalAlias(commandArray[i]); if (alias != null) {
if (alias != null) { somethingSubstituted = true
commandArray[i] = alias; commandArray.splice(i, 1, ...alias);
i += alias.length - 1;
//commandArray[i] = alias;
}
} }
} }
} }

@ -12,6 +12,7 @@ import { Player } from "../Player";
import { prestigeAugmentation } from "../Prestige"; import { prestigeAugmentation } from "../Prestige";
import { saveObject } from "../SaveObject"; import { saveObject } from "../SaveObject";
import { Page, routing } from "../ui/navigationTracking"; import { Page, routing } from "../ui/navigationTracking";
import { onExport } from "../ExportBonus";
import { dialogBoxCreate } from "../../utils/DialogBox"; import { dialogBoxCreate } from "../../utils/DialogBox";
import { clearObject } from "../../utils/helpers/clearObject"; import { clearObject } from "../../utils/helpers/clearObject";
@ -2077,9 +2078,14 @@ export function displayAugmentationsContent(contentEl) {
if (!routing.isOn(Page.Augmentations)) { return; } if (!routing.isOn(Page.Augmentations)) { return; }
if (!(contentEl instanceof HTMLElement)) { return; } if (!(contentEl instanceof HTMLElement)) { return; }
function backup() {
saveObject.exportGame();
onExport(Player);
}
ReactDOM.render( ReactDOM.render(
<AugmentationsRoot <AugmentationsRoot
exportGameFn={saveObject.exportGame.bind(saveObject)} exportGameFn={backup}
installAugmentationsFn={installAugmentations} installAugmentationsFn={installAugmentations}
/>, />,
contentEl, contentEl,

@ -10,6 +10,8 @@ import { PurchasedAugmentations } from "./PurchasedAugmentations";
import { Player } from "../../Player"; import { Player } from "../../Player";
import { StdButton } from "../../ui/React/StdButton"; import { StdButton } from "../../ui/React/StdButton";
import { LastExportBonus, canGetBonus } from "../../ExportBonus";
import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions";
type IProps = { type IProps = {
exportGameFn: () => void; exportGameFn: () => void;
@ -17,15 +19,31 @@ type IProps = {
} }
type IState = { type IState = {
rerender: boolean;
} }
export class AugmentationsRoot extends React.Component<IProps, IState> { export class AugmentationsRoot extends React.Component<IProps, IState> {
constructor(props: IProps) { constructor(props: IProps) {
super(props); super(props);
this.state = {
rerender: false,
};
this.export = this.export.bind(this);
}
export() {
this.props.exportGameFn();
this.setState({
rerender: !this.state.rerender,
});
} }
render(): React.ReactNode { render(): React.ReactNode {
function exportBonusStr(): string {
if(canGetBonus()) return "(+1 favor to all factions)";
return "";
}
return ( return (
<div id="augmentations-content"> <div id="augmentations-content">
<h1>Purchased Augmentations</h1> <h1>Purchased Augmentations</h1>
@ -60,8 +78,8 @@ export class AugmentationsRoot extends React.Component<IProps, IState> {
<StdButton <StdButton
addClasses="flashing-button" addClasses="flashing-button"
onClick={this.props.exportGameFn} onClick={this.export}
text="Backup Save (Export)" text={`Backup Save ${exportBonusStr()}`}
tooltip="It's always a good idea to backup/export your save!" tooltip="It's always a good idea to backup/export your save!"
/> />
<PurchasedAugmentations /> <PurchasedAugmentations />

@ -24,6 +24,7 @@ import {
convertTimeMsToTimeElapsedString, convertTimeMsToTimeElapsedString,
} from "../utils/StringHelperFunctions"; } from "../utils/StringHelperFunctions";
import { Settings } from "./Settings/Settings";
import { ConsoleHelpText } from "./Bladeburner/data/Help"; import { ConsoleHelpText } from "./Bladeburner/data/Help";
import { City } from "./Bladeburner/City"; import { City } from "./Bladeburner/City";
import { BladeburnerConstants } from "./Bladeburner/data/Constants"; import { BladeburnerConstants } from "./Bladeburner/data/Constants";
@ -158,6 +159,7 @@ function Bladeburner(params={}) {
// These times are in seconds // These times are in seconds
this.actionTimeToComplete = 0; // 0 or -1 is an infinite running action (like training) this.actionTimeToComplete = 0; // 0 or -1 is an infinite running action (like training)
this.actionTimeCurrent = 0; this.actionTimeCurrent = 0;
this.actionTimeOverflow = 0;
// ActionIdentifier Object // ActionIdentifier Object
var idleActionType = ActionTypes["Idle"]; var idleActionType = ActionTypes["Idle"];
@ -358,7 +360,9 @@ Bladeburner.prototype.process = function() {
msg += `<br><br>Your automation was disabled as well. You will have to re-enable it through the Bladeburner console` msg += `<br><br>Your automation was disabled as well. You will have to re-enable it through the Bladeburner console`
this.automateEnabled = false; this.automateEnabled = false;
} }
dialogBoxCreate(msg); if (!Settings.SuppressBladeburnerPopup) {
dialogBoxCreate(msg);
}
} }
this.resetAction(); this.resetAction();
} }
@ -381,22 +385,16 @@ Bladeburner.prototype.process = function() {
this.stamina = Math.min(this.maxStamina, this.stamina); this.stamina = Math.min(this.maxStamina, this.stamina);
// Count increase for contracts/operations // Count increase for contracts/operations
for (var contractName in this.contracts) { for (let contract of Object.values(this.contracts)) {
if (this.contracts.hasOwnProperty(contractName)) { contract.count += (seconds * contract.countGrowth/BladeburnerConstants.ActionCountGrowthPeriod);
var contract = this.contracts[contractName];
contract.count += (seconds * contract.countGrowth/BladeburnerConstants.ActionCountGrowthPeriod);
}
} }
for (var operationName in this.operations) { for (let op of Object.values(this.operations)) {
if (this.operations.hasOwnProperty(operationName)) { op.count += (seconds * op.countGrowth/BladeburnerConstants.ActionCountGrowthPeriod);
var op = this.operations[operationName];
op.count += (seconds * op.countGrowth/BladeburnerConstants.ActionCountGrowthPeriod);
}
} }
// Chaos goes down very slowly // Chaos goes down very slowly
for (var i = 0; i < BladeburnerConstants.CityNames.length; ++i) { for (let cityName of BladeburnerConstants.CityNames) {
var city = this.cities[BladeburnerConstants.CityNames[i]]; var city = this.cities[cityName];
if (!(city instanceof City)) {throw new Error("Invalid City object when processing passive chaos reduction in Bladeburner.process");} if (!(city instanceof City)) {throw new Error("Invalid City object when processing passive chaos reduction in Bladeburner.process");}
city.chaos -= (0.0001 * seconds); city.chaos -= (0.0001 * seconds);
city.chaos = Math.max(0, city.chaos); city.chaos = Math.max(0, city.chaos);
@ -406,7 +404,8 @@ Bladeburner.prototype.process = function() {
this.randomEventCounter -= seconds; this.randomEventCounter -= seconds;
if (this.randomEventCounter <= 0) { if (this.randomEventCounter <= 0) {
this.randomEvent(); this.randomEvent();
this.randomEventCounter = getRandomInt(240, 600); // Add instead of setting because we might have gone over the required time for the event
this.randomEventCounter += getRandomInt(240, 600);
} }
this.processAction(seconds); this.processAction(seconds);
@ -664,8 +663,12 @@ Bladeburner.prototype.processAction = function(seconds) {
throw new Error("Bladeburner.action is not an ActionIdentifier Object"); throw new Error("Bladeburner.action is not an ActionIdentifier Object");
} }
this.actionTimeCurrent += seconds; // If the previous action went past its completion time, add to the next action
// This is not added inmediatly in case the automation changes the action
this.actionTimeCurrent += seconds + this.actionTimeOverflow;
this.actionTimeOverflow = 0;
if (this.actionTimeCurrent >= this.actionTimeToComplete) { if (this.actionTimeCurrent >= this.actionTimeToComplete) {
this.actionTimeOverflow = this.actionTimeCurrent - this.actionTimeToComplete;
return this.completeAction(); return this.completeAction();
} }
} }
@ -1889,17 +1892,18 @@ Bladeburner.prototype.updateActionAndSkillsContent = function() {
Bladeburner.prototype.updateGeneralActionsUIElement = function(el, action) { Bladeburner.prototype.updateGeneralActionsUIElement = function(el, action) {
removeChildrenFromElement(el); removeChildrenFromElement(el);
var isActive = el.classList.contains(ActiveActionCssClass); var isActive = el.classList.contains(ActiveActionCssClass);
var computedActionTimeCurrent = Math.min(this.actionTimeCurrent+this.actionTimeOverflow,this.actionTimeToComplete);
el.appendChild(createElement("h2", { // Header el.appendChild(createElement("h2", { // Header
innerText:isActive ? action.name + " (IN PROGRESS - " + innerText:isActive ? action.name + " (IN PROGRESS - " +
formatNumber(this.actionTimeCurrent, 0) + " / " + formatNumber(computedActionTimeCurrent, 0) + " / " +
formatNumber(this.actionTimeToComplete, 0) + ")" formatNumber(this.actionTimeToComplete, 0) + ")"
: action.name, : action.name,
display:"inline-block", display:"inline-block",
})); }));
if (isActive) { // Progress bar if its active if (isActive) { // Progress bar if its active
var progress = this.actionTimeCurrent / this.actionTimeToComplete; var progress = computedActionTimeCurrent / this.actionTimeToComplete;
el.appendChild(createElement("p", { el.appendChild(createElement("p", {
display:"block", display:"block",
innerText:createProgressBarText({progress:progress}), innerText:createProgressBarText({progress:progress}),
@ -1931,17 +1935,18 @@ Bladeburner.prototype.updateContractsUIElement = function(el, action) {
removeChildrenFromElement(el); removeChildrenFromElement(el);
var isActive = el.classList.contains(ActiveActionCssClass); var isActive = el.classList.contains(ActiveActionCssClass);
var estimatedSuccessChance = action.getSuccessChance(this, {est:true}); var estimatedSuccessChance = action.getSuccessChance(this, {est:true});
var computedActionTimeCurrent = Math.min(this.actionTimeCurrent+this.actionTimeOverflow,this.actionTimeToComplete);
el.appendChild(createElement("h2", { // Header el.appendChild(createElement("h2", { // Header
innerText:isActive ? action.name + " (IN PROGRESS - " + innerText:isActive ? action.name + " (IN PROGRESS - " +
formatNumber(this.actionTimeCurrent, 0) + " / " + formatNumber(computedActionTimeCurrent, 0) + " / " +
formatNumber(this.actionTimeToComplete, 0) + ")" formatNumber(this.actionTimeToComplete, 0) + ")"
: action.name, : action.name,
display:"inline-block", display:"inline-block",
})); }));
if (isActive) { // Progress bar if its active if (isActive) { // Progress bar if its active
var progress = this.actionTimeCurrent / this.actionTimeToComplete; var progress = computedActionTimeCurrent / this.actionTimeToComplete;
el.appendChild(createElement("p", { el.appendChild(createElement("p", {
display:"block", display:"block",
innerText:createProgressBarText({progress:progress}), innerText:createProgressBarText({progress:progress}),
@ -2030,16 +2035,18 @@ Bladeburner.prototype.updateOperationsUIElement = function(el, action) {
removeChildrenFromElement(el); removeChildrenFromElement(el);
var isActive = el.classList.contains(ActiveActionCssClass); var isActive = el.classList.contains(ActiveActionCssClass);
var estimatedSuccessChance = action.getSuccessChance(this, {est:true}); var estimatedSuccessChance = action.getSuccessChance(this, {est:true});
var computedActionTimeCurrent = Math.min(this.actionTimeCurrent+this.actionTimeOverflow,this.actionTimeToComplete);
el.appendChild(createElement("h2", { // Header el.appendChild(createElement("h2", { // Header
innerText:isActive ? action.name + " (IN PROGRESS - " + innerText:isActive ? action.name + " (IN PROGRESS - " +
formatNumber(this.actionTimeCurrent, 0) + " / " + formatNumber(computedActionTimeCurrent, 0) + " / " +
formatNumber(this.actionTimeToComplete, 0) + ")" formatNumber(this.actionTimeToComplete, 0) + ")"
: action.name, : action.name,
display:"inline-block", display:"inline-block",
})); }));
if (isActive) { // Progress bar if its active if (isActive) { // Progress bar if its active
var progress = this.actionTimeCurrent / this.actionTimeToComplete; var progress = computedActionTimeCurrent / this.actionTimeToComplete;
el.appendChild(createElement("p", { el.appendChild(createElement("p", {
display:"block", display:"block",
innerText:createProgressBarText({progress:progress}), innerText:createProgressBarText({progress:progress}),
@ -2093,6 +2100,7 @@ Bladeburner.prototype.updateOperationsUIElement = function(el, action) {
}, },
}); });
createPopup(popupId, [txt, input, setBtn, cancelBtn]); createPopup(popupId, [txt, input, setBtn, cancelBtn]);
input.focus();
}, },
})); }));
} }
@ -2171,6 +2179,7 @@ Bladeburner.prototype.updateBlackOpsUIElement = function(el, action) {
var estimatedSuccessChance = action.getSuccessChance(this, {est:true}); var estimatedSuccessChance = action.getSuccessChance(this, {est:true});
var actionTime = action.getActionTime(this); var actionTime = action.getActionTime(this);
var hasReqdRank = this.rank >= action.reqdRank; var hasReqdRank = this.rank >= action.reqdRank;
var computedActionTimeCurrent = Math.min(this.actionTimeCurrent+this.actionTimeOverflow,this.actionTimeToComplete);
// UI for Completed Black Op // UI for Completed Black Op
if (isCompleted) { if (isCompleted) {
@ -2182,14 +2191,14 @@ Bladeburner.prototype.updateBlackOpsUIElement = function(el, action) {
el.appendChild(createElement("h2", { // Header el.appendChild(createElement("h2", { // Header
innerText:isActive ? action.name + " (IN PROGRESS - " + innerText:isActive ? action.name + " (IN PROGRESS - " +
formatNumber(this.actionTimeCurrent, 0) + " / " + formatNumber(computedActionTimeCurrent, 0) + " / " +
formatNumber(this.actionTimeToComplete, 0) + ")" formatNumber(this.actionTimeToComplete, 0) + ")"
: action.name, : action.name,
display:"inline-block", display:"inline-block",
})); }));
if (isActive) { // Progress bar if its active if (isActive) { // Progress bar if its active
var progress = this.actionTimeCurrent / this.actionTimeToComplete; var progress = computedActionTimeCurrent / this.actionTimeToComplete;
el.appendChild(createElement("p", { el.appendChild(createElement("p", {
display:"block", display:"block",
innerText:createProgressBarText({progress:progress}), innerText:createProgressBarText({progress:progress}),
@ -2243,6 +2252,7 @@ Bladeburner.prototype.updateBlackOpsUIElement = function(el, action) {
}, },
}); });
createPopup(popupId, [txt, input, setBtn, cancelBtn]); createPopup(popupId, [txt, input, setBtn, cancelBtn]);
input.focus();
}, },
})); }));
} }

@ -6,7 +6,7 @@
import { IMap } from "./types"; import { IMap } from "./types";
export const CONSTANTS: IMap<any> = { export const CONSTANTS: IMap<any> = {
Version: "0.51.8", Version: "0.51.9",
/** Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience /** Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience
* and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then * and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then
@ -228,60 +228,50 @@ export const CONSTANTS: IMap<any> = {
LatestUpdate: LatestUpdate:
` `
v0.51.8 - 2021-05-07 It was there all along (hydroflame) v0.51.9 - 2021-05-17 offline progress and exports!
------- -------
Servers Alias
* several commands can be included in 1 alias. Recursive alias now work to
a depth of 10. (@Dawe)
* Update n00dles metadata Offline
Netscript * Offline money gain has been reworked (it is more generous)
* If you're not working anywhere and go offline the game will work for you
at all your factions evenly.
* 'hashGainRate' use the correct 'usedRam' and 'maxRam' Export
* Fix 'setActionAutolevel' logging. * Exporting now gives +1 favor to all joined factions every 24h.
* Fix 'setActionLevel' not working at all.
* Add 'installBackdoor' singularity function.
Hacknet Corp
* Self-fund with an invalid name no longer takes away 150b anyway.
* Can no longer export negative amount
* Fix Hacknet Servers total production always displaying 0 Bladeburner
* No longer waste overflowing time.
Documentation Text Editors
* All settings will now be saved and loaded correctly.
* Updated guide to no longer recommend BN12. Terminal
* Fix documentation for maxNumNodes (@ModdedGamers) * 'scan' now works for servers that are more than 21 character long.
* Fix typo in 'sourcefiles.rst'
* Fix typo in 'recommendedbitnodeorder.rst'
* Fix 'getServer' documentation missing 'server' argument.
* Fix missing ram cost in 'getData.rst'
* Fix basic formulas examples.
* Fix typo in BN11 description.
* Fix formatting issue in Bladeburner (@Pimgd)
Misc. Misc.
* ls now correctly lists all files.
* Fix negative money being displayed in full. * importing auto save+reloads (@Dawe)
* Fix Hacking Missions not working. * Fix a bug where .fconf could not be created
* Fix Corporation tree not rendering. * Fix formatting inconsistencies for some logs of netscript functions.
* Fix script being needlessly recompiled. This should save real ram (not game ram) * Fix a bug where Cashroot starter kit would appear as [object Object] in
* w0r1d_d43m0n can be backdoored confirmation dialog.
* Coding Contracts title is click-to-copy (@Rodeth) * Fix some ram not displayed as 0.00GB
* Covenant memory upgrade works better. * Fix error message throw undefined variable error
* Fix Neuroflux not being correctly calculated when entering BN with SF12. * City hall now has some generic text if you can't create a corp yet.
* Delete Active Script now delete all active scripts, not just home. * Deleting a file without extension now returns an appropriate error message.
* Now you can 'cd' in directories that only contain '.txt' files. * Fixed an issue where bladeburner would miscalculate the cost of hospitalization.
* Fix 'analyze' always saying players had root access * It is now possible to suppress bladeburner "action stopped" popup.
* Passive faction rep no longer builds for special factions. * Updated several dependencies (big who cares, I know)
* Donation option no longer appears for special factions. * ls no longer prints lingering newline.
* Rephrased some milestones. * Money earned/spent by sleeves is now tracked under Character>Money
* donation textbox now accepts money in the format '1b' and the like (@Dawe)
* Fix being able to join hated factions simultaneously. (@Dawe)
* 'ls' now displays files in multiple column. (Helps players with many files)
* Bladeburner multiplers now appear under Character>Stats and
Character>Augmentation when they are relevant.
* Fix missing functions syntax highlight in codemirror.
* Fix infiltration number formatting.
* script income transfers to parent on death. This helps keep track of
income for scripts that spawn short lived scripts.
`, `,
} }

@ -320,7 +320,7 @@ export class CorporationEventHandler {
return false; return false;
} }
if (temp == null || isNaN(temp)) { if (temp == null || isNaN(temp) || temp < 0) {
dialogBoxCreate("Invalid amount entered for export"); dialogBoxCreate("Invalid amount entered for export");
return; return;
} }

@ -15,6 +15,8 @@ import { GetServerByHostname } from "./Server/ServerHelpers";
import { hackWorldDaemon } from "./RedPill"; import { hackWorldDaemon } from "./RedPill";
import { StockMarket } from "./StockMarket/StockMarket"; import { StockMarket } from "./StockMarket/StockMarket";
import { Stock } from "./StockMarket/Stock"; import { Stock } from "./StockMarket/Stock";
import { Engine } from "./engine";
import { saveObject } from "./SaveObject";
import { dialogBoxCreate } from "../utils/DialogBox"; import { dialogBoxCreate } from "../utils/DialogBox";
import { createElement } from "../utils/uiHelpers/createElement"; import { createElement } from "../utils/uiHelpers/createElement";
@ -641,6 +643,15 @@ class DevMenuComponent extends Component {
} }
} }
timeskip(time) {
return () => {
Player.lastUpdate -= time;
Engine._lastUpdate -= time;
saveObject.saveGame(Engine.indexedDb);
setTimeout(() => location.reload(), 1000);
};
}
render() { render() {
let factions = []; let factions = [];
for (const i in Factions) { for (const i in Factions) {
@ -1212,6 +1223,19 @@ class DevMenuComponent extends Component {
</div> </div>
</div> </div>
<div className="row">
<div className="col">
<div className="row">
<h2>Offline time skip:</h2>
</div>
<div className="row">
<button className="std-button" onClick={this.timeskip(60*1000)}>1 minute</button>
<button className="std-button" onClick={this.timeskip(60*60*1000)}>1 hour</button>
<button className="std-button" onClick={this.timeskip(24*60*60*1000)}>1 day</button>
</div>
</div>
</div>
</div> </div>
); );
} }

22
src/ExportBonus.tsx Normal file

@ -0,0 +1,22 @@
import { Factions } from "./Faction/Factions";
import { IPlayer } from "./PersonObjects/IPlayer";
export let LastExportBonus: number = 0;
const bonusTimer = 24*60*60*1000; // 24h
export function canGetBonus(): boolean {
const now = (new Date()).getTime()
console.log(now);
console.log(LastExportBonus);
console.log(now - LastExportBonus);
if(now - LastExportBonus > bonusTimer) return true;
return false;
}
export function onExport(p: IPlayer): void {
if(!canGetBonus()) return;
for (const facName of p.factions) {
Factions[facName].favor++;
}
LastExportBonus = (new Date()).getTime();
}

@ -114,9 +114,13 @@ export function purchaseAugmentationBoxCreate(aug, fac) {
yesNoBoxClose(); yesNoBoxClose();
}); });
let content = (<div dangerouslySetInnerHTML={{__html: aug.info}}></div>);
if(typeof aug.info !== 'string') {
content = <div>{aug.info}</div>
}
yesNoBoxCreate(<> yesNoBoxCreate(<>
<h2>{aug.name}</h2><br /> <h2>{aug.name}</h2><br />
<div dangerouslySetInnerHTML={{__html: aug.info}}></div><br /><br /> {content}<br /><br />
<br />Would you like to purchase the {aug.name} Augmentation for&nbsp; <br />Would you like to purchase the {aug.name} Augmentation for&nbsp;
{Money(aug.baseCost * factionInfo.augmentationPriceMult)}? {Money(aug.baseCost * factionInfo.augmentationPriceMult)}?
</>); </>);

@ -19,7 +19,6 @@ export function getHospitalizationCost(p: IPlayer): number {
export function calculateHospitalizationCost(p: IPlayer, damage: number): number { export function calculateHospitalizationCost(p: IPlayer, damage: number): number {
const oldhp = p.hp; const oldhp = p.hp;
p.hp -= damage p.hp -= damage
if (p.hp < 0) p.hp = 0;
const cost = getHospitalizationCost(p); const cost = getHospitalizationCost(p);
p.hp = oldhp; p.hp = oldhp;
return cost; return cost;

@ -140,7 +140,6 @@ export function createStartCorporationPopup(p: IPlayer): void {
dialogBoxCreate("You don't have enough money to create a corporation! You need $150b"); dialogBoxCreate("You don't have enough money to create a corporation! You need $150b");
return false; return false;
} }
p.loseMoney(150e9);
const companyName = nameInput.value; const companyName = nameInput.value;
if (companyName == null || companyName == "") { if (companyName == null || companyName == "") {
@ -149,6 +148,7 @@ export function createStartCorporationPopup(p: IPlayer): void {
} }
p.startCorporation(companyName); p.startCorporation(companyName);
p.loseMoney(150e9);
const worldHeader = document.getElementById("world-menu-header"); const worldHeader = document.getElementById("world-menu-header");
if (worldHeader instanceof HTMLElement) { if (worldHeader instanceof HTMLElement) {

@ -108,7 +108,11 @@ export class SpecialLocation extends React.Component<IProps, IState> {
} }
renderCreateCorporation(): React.ReactNode { renderCreateCorporation(): React.ReactNode {
if (!this.props.p.canAccessCorporation()) { return null; } if (!this.props.p.canAccessCorporation()) {
return <>
<p><i>A business man is yelling at a clerk. You should come back later.</i></p>
</>;
}
return ( return (
<AutoupdatingStdButton <AutoupdatingStdButton
disabled={!this.props.p.canAccessCorporation() || this.props.p.hasCorporation()} disabled={!this.props.p.canAccessCorporation() || this.props.p.hasCorporation()}

@ -166,10 +166,7 @@ import { numeralWrapper } from "./ui/numeralFormat";
import { post } from "./ui/postToTerminal"; import { post } from "./ui/postToTerminal";
import { setTimeoutRef } from "./utils/SetTimeoutRef"; import { setTimeoutRef } from "./utils/SetTimeoutRef";
import { is2DArray } from "./utils/helpers/is2DArray"; import { is2DArray } from "./utils/helpers/is2DArray";
import { import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
formatNumber,
convertTimeMsToTimeElapsedString,
} from "../utils/StringHelperFunctions";
import { logBoxCreate } from "../utils/LogBox"; import { logBoxCreate } from "../utils/LogBox";
import { arrayToString } from "../utils/helpers/arrayToString"; import { arrayToString } from "../utils/helpers/arrayToString";
@ -179,77 +176,6 @@ import { createElement } from "../utils/uiHelpers/createElement";
import { createPopup } from "../utils/uiHelpers/createPopup"; import { createPopup } from "../utils/uiHelpers/createPopup";
import { removeElementById } from "../utils/uiHelpers/removeElementById"; import { removeElementById } from "../utils/uiHelpers/removeElementById";
const possibleLogs = {
ALL: true,
scan: true,
hack: true,
sleep: true,
disableLog: true,
enableLog: true,
grow: true,
weaken: true,
nuke: true,
brutessh: true,
ftpcrack: true,
relaysmtp: true,
httpworm: true,
sqlinject: true,
run:true,
exec:true,
spawn: true,
kill: true,
killall: true,
scp: true,
getHackingLevel: true,
getServerMoneyAvailable: true,
getServerSecurityLevel: true,
getServerBaseSecurityLevel: true,
getServerMinSecurityLevel: true,
getServerRequiredHackingLevel: true,
getServerMaxMoney: true,
getServerGrowth: true,
getServerNumPortsRequired: true,
getServerRam: true,
// TIX API
buyStock: true,
sellStock: true,
shortStock: true,
sellShort: true,
purchase4SMarketData: true,
purchase4SMarketDataTixApi: true,
// Singularity Functions
purchaseServer: true,
deleteServer: true,
universityCourse: true,
gymWorkout: true,
travelToCity: true,
purchaseTor: true,
purchaseProgram: true,
stopAction: true,
upgradeHomeRam: true,
workForCompany: true,
applyToCompany: true,
joinFaction: true,
workForFaction: true,
donateToFaction: true,
createProgram: true,
commitCrime: true,
// Bladeburner API
startAction: true,
upgradeSkill: true,
setTeamSize: true,
joinBladeburnerFaction: true,
// Gang API
recruitMember: true,
setMemberTask: true,
purchaseEquipment: true,
setTerritoryWarfare: true,
}
const defaultInterpreter = new Interpreter('', () => undefined); const defaultInterpreter = new Interpreter('', () => undefined);
// the acorn interpreter has a bug where it doesn't convert arrays correctly. // the acorn interpreter has a bug where it doesn't convert arrays correctly.
@ -300,8 +226,8 @@ function NetscriptFunctions(workerScript) {
"Dynamic RAM usage calculated to be greater than initial RAM usage on fn: " + fnName + "Dynamic RAM usage calculated to be greater than initial RAM usage on fn: " + fnName +
". This is probably because you somehow circumvented the static RAM " + ". This is probably because you somehow circumvented the static RAM " +
"calculation.<br><br>Please don't do that :(<br><br>" + "calculation.<br><br>Please don't do that :(<br><br>" +
"Dynamic RAM Usage: " + workerScript.dynamicRamUsage + "<br>" + "Dynamic RAM Usage: " + numeralWrapper.formatRAM(workerScript.dynamicRamUsage) + "<br>" +
"Static RAM Usage: " + workerScript.ramUsage); "Static RAM Usage: " + numeralWrapper.formatRAM(workerScript.ramUsage));
} }
}; };
@ -739,7 +665,7 @@ function NetscriptFunctions(workerScript) {
return out; return out;
} }
return { const functions = {
hacknet : { hacknet : {
numNodes : function() { numNodes : function() {
return Player.hacknetNodes.length; return Player.hacknetNodes.length;
@ -950,7 +876,7 @@ function NetscriptFunctions(workerScript) {
expGain = 0; expGain = 0;
} }
const logGrowPercent = (moneyAfter/moneyBefore)*100 - 100; const logGrowPercent = (moneyAfter/moneyBefore)*100 - 100;
workerScript.log("grow", `Available money on '${server.hostname}' grown by ${formatNumber(logGrowPercent, 6)}%. Gained ${numeralWrapper.formatExp(expGain)} hacking exp (t=${numeralWrapper.formatThreads(threads)}).`); workerScript.log("grow", `Available money on '${server.hostname}' grown by ${numeralWrapper.formatPercentage(logGrowPercent, 6)}. Gained ${numeralWrapper.formatExp(expGain)} hacking exp (t=${numeralWrapper.formatThreads(threads)}).`);
workerScript.scriptRef.onlineExpGained += expGain; workerScript.scriptRef.onlineExpGained += expGain;
Player.gainHackingExp(expGain); Player.gainHackingExp(expGain);
if (stock) { if (stock) {
@ -1194,7 +1120,7 @@ function NetscriptFunctions(workerScript) {
throw makeRuntimeErrorMsg("run", "Usage: run(scriptname, [numThreads], [arg1], [arg2]...)"); throw makeRuntimeErrorMsg("run", "Usage: run(scriptname, [numThreads], [arg1], [arg2]...)");
} }
if (isNaN(threads) || threads <= 0) { if (isNaN(threads) || threads <= 0) {
throw makeRuntimeErrorMsg("run", `Invalid thread count. Must be numeric and > 0, is ${thread}`); throw makeRuntimeErrorMsg("run", `Invalid thread count. Must be numeric and > 0, is ${threads}`);
} }
var argsForNewScript = []; var argsForNewScript = [];
for (var i = 2; i < arguments.length; ++i) { for (var i = 2; i < arguments.length; ++i) {
@ -1663,28 +1589,28 @@ function NetscriptFunctions(workerScript) {
updateDynamicRam("getServerSecurityLevel", getRamCost("getServerSecurityLevel")); updateDynamicRam("getServerSecurityLevel", getRamCost("getServerSecurityLevel"));
const server = safeGetServer(ip, "getServerSecurityLevel"); const server = safeGetServer(ip, "getServerSecurityLevel");
if (failOnHacknetServer(server, "getServerSecurityLevel")) { return 1; } if (failOnHacknetServer(server, "getServerSecurityLevel")) { return 1; }
workerScript.log("getServerSecurityLevel", `returned ${formatNumber(server.hackDifficulty, 3)} for '${server.hostname}'`); workerScript.log("getServerSecurityLevel", `returned ${numeralWrapper.formatServerSecurity(server.hackDifficulty, 3)} for '${server.hostname}'`);
return server.hackDifficulty; return server.hackDifficulty;
}, },
getServerBaseSecurityLevel: function(ip) { getServerBaseSecurityLevel: function(ip) {
updateDynamicRam("getServerBaseSecurityLevel", getRamCost("getServerBaseSecurityLevel")); updateDynamicRam("getServerBaseSecurityLevel", getRamCost("getServerBaseSecurityLevel"));
const server = safeGetServer(ip, "getServerBaseSecurityLevel"); const server = safeGetServer(ip, "getServerBaseSecurityLevel");
if (failOnHacknetServer(server, "getServerBaseSecurityLevel")) { return 1; } if (failOnHacknetServer(server, "getServerBaseSecurityLevel")) { return 1; }
workerScript.log("getServerBaseSecurityLevel", `returned ${formatNumber(server.baseDifficulty, 3)} for '${server.hostname}'`); workerScript.log("getServerBaseSecurityLevel", `returned ${numeralWrapper.formatServerSecurity(server.baseDifficulty, 3)} for '${server.hostname}'`);
return server.baseDifficulty; return server.baseDifficulty;
}, },
getServerMinSecurityLevel: function(ip) { getServerMinSecurityLevel: function(ip) {
updateDynamicRam("getServerMinSecurityLevel", getRamCost("getServerMinSecurityLevel")); updateDynamicRam("getServerMinSecurityLevel", getRamCost("getServerMinSecurityLevel"));
const server = safeGetServer(ip, "getServerMinSecurityLevel"); const server = safeGetServer(ip, "getServerMinSecurityLevel");
if (failOnHacknetServer(server, "getServerMinSecurityLevel")) { return 1; } if (failOnHacknetServer(server, "getServerMinSecurityLevel")) { return 1; }
workerScript.log("getServerMinSecurityLevel", `returned ${formatNumber(server.minDifficulty, 3)} for ${server.hostname}`); workerScript.log("getServerMinSecurityLevel", `returned ${numeralWrapper.formatServerSecurity(server.minDifficulty, 3)} for ${server.hostname}`);
return server.minDifficulty; return server.minDifficulty;
}, },
getServerRequiredHackingLevel: function(ip) { getServerRequiredHackingLevel: function(ip) {
updateDynamicRam("getServerRequiredHackingLevel", getRamCost("getServerRequiredHackingLevel")); updateDynamicRam("getServerRequiredHackingLevel", getRamCost("getServerRequiredHackingLevel"));
const server = safeGetServer(ip, "getServerRequiredHackingLevel"); const server = safeGetServer(ip, "getServerRequiredHackingLevel");
if (failOnHacknetServer(server, "getServerRequiredHackingLevel")) { return 1; } if (failOnHacknetServer(server, "getServerRequiredHackingLevel")) { return 1; }
workerScript.log("getServerRequiredHackingLevel", `returned ${formatNumber(server.requiredHackingSkill, 0)} for '${server.hostname}'`); workerScript.log("getServerRequiredHackingLevel", `returned ${numeralWrapper.formatSkill(server.requiredHackingSkill, 0)} for '${server.hostname}'`);
return server.requiredHackingSkill; return server.requiredHackingSkill;
}, },
getServerMaxMoney: function(ip) { getServerMaxMoney: function(ip) {
@ -1698,32 +1624,32 @@ function NetscriptFunctions(workerScript) {
updateDynamicRam("getServerGrowth", getRamCost("getServerGrowth")); updateDynamicRam("getServerGrowth", getRamCost("getServerGrowth"));
const server = safeGetServer(ip, "getServerGrowth"); const server = safeGetServer(ip, "getServerGrowth");
if (failOnHacknetServer(server, "getServerGrowth")) { return 1; } if (failOnHacknetServer(server, "getServerGrowth")) { return 1; }
workerScript.log("getServerGrowth", `returned ${formatNumber(server.serverGrowth, 0)} for '${server.hostname}'`); workerScript.log("getServerGrowth", `returned ${server.serverGrowth} for '${server.hostname}'`);
return server.serverGrowth; return server.serverGrowth;
}, },
getServerNumPortsRequired: function(ip) { getServerNumPortsRequired: function(ip) {
updateDynamicRam("getServerNumPortsRequired", getRamCost("getServerNumPortsRequired")); updateDynamicRam("getServerNumPortsRequired", getRamCost("getServerNumPortsRequired"));
const server = safeGetServer(ip, "getServerNumPortsRequired"); const server = safeGetServer(ip, "getServerNumPortsRequired");
if (failOnHacknetServer(server, "getServerNumPortsRequired")) { return 5; } if (failOnHacknetServer(server, "getServerNumPortsRequired")) { return 5; }
workerScript.log("getServerNumPortsRequired", `returned ${formatNumber(server.numOpenPortsRequired, 0)} for '${server.hostname}'`); workerScript.log("getServerNumPortsRequired", `returned ${server.numOpenPortsRequired} for '${server.hostname}'`);
return server.numOpenPortsRequired; return server.numOpenPortsRequired;
}, },
getServerRam: function(ip) { getServerRam: function(ip) {
updateDynamicRam("getServerRam", getRamCost("getServerRam")); updateDynamicRam("getServerRam", getRamCost("getServerRam"));
const server = safeGetServer(ip, "getServerRam"); const server = safeGetServer(ip, "getServerRam");
workerScript.log("getServerRam", `returned [${formatNumber(server.maxRam, 2)}GB, ${formatNumber(server.ramUsed, 2)}GB]`); workerScript.log("getServerRam", `returned [${numeralWrapper.formatRAM(server.maxRam, 2)}, ${numeralWrapper.formatRAM(server.ramUsed, 2)}]`);
return [server.maxRam, server.ramUsed]; return [server.maxRam, server.ramUsed];
}, },
getServerMaxRam: function(ip) { getServerMaxRam: function(ip) {
updateDynamicRam("getServerMaxRam", getRamCost("getServerMaxRam")); updateDynamicRam("getServerMaxRam", getRamCost("getServerMaxRam"));
const server = safeGetServer(ip, "getServerMaxRam"); const server = safeGetServer(ip, "getServerMaxRam");
workerScript.log("getServerMaxRam", `returned ${formatNumber(server.maxRam, 2)}GB`); workerScript.log("getServerMaxRam", `returned ${numeralWrapper.formatRAM(server.maxRam, 2)}`);
return server.maxRam; return server.maxRam;
}, },
getServerUsedRam: function(ip) { getServerUsedRam: function(ip) {
updateDynamicRam("getServerUsedRam", getRamCost("getServerUsedRam")); updateDynamicRam("getServerUsedRam", getRamCost("getServerUsedRam"));
const server = safeGetServer(ip, "getServerUsedRam"); const server = safeGetServer(ip, "getServerUsedRam");
workerScript.log("getServerUsedRam", `returned ${formatNumber(server.ramUsed, 2)}GB`); workerScript.log("getServerUsedRam", `returned ${numeralWrapper.formatRAM(server.ramUsed, 2)}`);
return server.ramUsed; return server.ramUsed;
}, },
serverExists: function(ip) { serverExists: function(ip) {
@ -4544,7 +4470,23 @@ function NetscriptFunctions(workerScript) {
} }
return ret; return ret;
}, },
} // End return }
function getFunctionNames(obj) {
const functionNames = [];
for(const [key, value] of Object.entries(obj)){
if(typeof(value)=="function"){
functionNames.push(key);
}else if(typeof(value)=="object"){
functionNames.push(...getFunctionNames(value));
}
}
return functionNames;
}
const possibleLogs = Object.fromEntries(["ALL", ...getFunctionNames(functions)].map(a => [a, true]))
return functions;
} // End NetscriptFunction() } // End NetscriptFunction()
export { NetscriptFunctions }; export { NetscriptFunctions };

@ -564,7 +564,6 @@ export function updateOnlineScriptTimes(numCycles = 1) {
* into worker scripts so that they will start running * into worker scripts so that they will start running
*/ */
export function loadAllRunningScripts() { export function loadAllRunningScripts() {
var total = 0;
let skipScriptLoad = (window.location.href.toLowerCase().indexOf("?noscripts") !== -1); let skipScriptLoad = (window.location.href.toLowerCase().indexOf("?noscripts") !== -1);
if (skipScriptLoad) { console.info("Skipping the load of any scripts during startup"); } if (skipScriptLoad) { console.info("Skipping the load of any scripts during startup"); }
for (const property in AllServers) { for (const property in AllServers) {
@ -587,13 +586,11 @@ export function loadAllRunningScripts() {
createAndAddWorkerScript(server.runningScripts[j], server); createAndAddWorkerScript(server.runningScripts[j], server);
// Offline production // Offline production
total += scriptCalculateOfflineProduction(server.runningScripts[j]); scriptCalculateOfflineProduction(server.runningScripts[j]);
} }
} }
} }
} }
return total;
} }
/** /**

@ -334,6 +334,7 @@ export class Sleeve extends Person {
this.earningsForTask.money += gain; this.earningsForTask.money += gain;
this.earningsForPlayer.money += gain; this.earningsForPlayer.money += gain;
p.gainMoney(gain); p.gainMoney(gain);
p.recordMoneySource(gain, 'sleeves');
} }
/** /**

@ -8,16 +8,10 @@ import { Companies, loadCompanies } from "./Company/Companies";
import { CONSTANTS } from "./Constants"; import { CONSTANTS } from "./Constants";
import { Engine } from "./engine"; import { Engine } from "./engine";
import { Factions, loadFactions } from "./Faction/Factions"; import { Factions, loadFactions } from "./Faction/Factions";
import { processPassiveFactionRepGain } from "./Faction/FactionHelpers";
import { loadFconf } from "./Fconf/Fconf"; import { loadFconf } from "./Fconf/Fconf";
import { FconfSettings } from "./Fconf/FconfSettings"; import { FconfSettings } from "./Fconf/FconfSettings";
import { loadAllGangs, AllGangs } from "./Gang"; import { loadAllGangs, AllGangs } from "./Gang";
import {
hasHacknetServers,
processHacknetEarnings,
} from "./Hacknet/HacknetHelpers";
import { loadMessages, initMessages, Messages } from "./Message/MessageHelpers"; import { loadMessages, initMessages, Messages } from "./Message/MessageHelpers";
import { loadAllRunningScripts } from "./NetscriptWorker";
import { Player, loadPlayer } from "./Player"; import { Player, loadPlayer } from "./Player";
import { AllServers, loadAllServers } from "./Server/AllServers"; import { AllServers, loadAllServers } from "./Server/AllServers";
import { Settings } from "./Settings/Settings"; import { Settings } from "./Settings/Settings";
@ -31,19 +25,15 @@ import { loadStockMarket, StockMarket } from "./StockMarket/StockMarket";
import { createStatusText } from "./ui/createStatusText"; import { createStatusText } from "./ui/createStatusText";
import { setTimeoutRef } from "./utils/SetTimeoutRef"; import { setTimeoutRef } from "./utils/SetTimeoutRef";
import * as ExportBonus from "./ExportBonus";
import { dialogBoxCreate } from "../utils/DialogBox"; import { dialogBoxCreate } from "../utils/DialogBox";
import { gameOptionsBoxClose } from "../utils/GameOptions";
import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners"; import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners";
import { import {
Reviver, Reviver,
Generic_toJSON, Generic_toJSON,
Generic_fromJSON, Generic_fromJSON,
} from "../utils/JSONReviver"; } from "../utils/JSONReviver";
import { createElement } from "../utils/uiHelpers/createElement";
import { createPopup } from "../utils/uiHelpers/createPopup";
import { removeElementById } from "../utils/uiHelpers/removeElementById";
import Decimal from "decimal.js"; import Decimal from "decimal.js";
@ -66,6 +56,7 @@ function BitburnerSaveObject() {
this.FconfSettingsSave = ""; this.FconfSettingsSave = "";
this.VersionSave = ""; this.VersionSave = "";
this.AllGangsSave = ""; this.AllGangsSave = "";
this.LastExportBonus = "";
} }
BitburnerSaveObject.prototype.getSaveString = function() { BitburnerSaveObject.prototype.getSaveString = function() {
@ -94,6 +85,7 @@ BitburnerSaveObject.prototype.getSaveString = function() {
this.SettingsSave = JSON.stringify(Settings); this.SettingsSave = JSON.stringify(Settings);
this.FconfSettingsSave = JSON.stringify(FconfSettings); this.FconfSettingsSave = JSON.stringify(FconfSettings);
this.VersionSave = JSON.stringify(CONSTANTS.Version); this.VersionSave = JSON.stringify(CONSTANTS.Version);
this.LastExportBonus = JSON.stringify(ExportBonus.LastExportBonus);
if (Player.inGang()) { if (Player.inGang()) {
this.AllGangsSave = JSON.stringify(AllGangs); this.AllGangsSave = JSON.stringify(AllGangs);
} }
@ -254,6 +246,16 @@ function loadGame(saveString) {
console.error("ERROR: Failed to parse .fconf Settings."); console.error("ERROR: Failed to parse .fconf Settings.");
} }
} }
if (saveObj.hasOwnProperty("LastExportBonus")) {
try {
ExportBonus.LastExportBonus = JSON.parse(saveObj.LastExportBonus);
} catch(err) {
console.log(saveObj.LastExportBonus);
console.log(ExportBonus.LastExportBonus);
ExportBonus.LastExportBonus = (new Date()).getTime();
console.error("ERROR: Failed to parse .fconf Settings "+ err);
}
}
if (saveObj.hasOwnProperty("VersionSave")) { if (saveObj.hasOwnProperty("VersionSave")) {
try { try {
var ver = JSON.parse(saveObj.VersionSave, Reviver); var ver = JSON.parse(saveObj.VersionSave, Reviver);
@ -285,22 +287,11 @@ function loadGame(saveString) {
function loadImportedGame(saveObj, saveString) { function loadImportedGame(saveObj, saveString) {
var tempSaveObj = null; var tempSaveObj = null;
var tempPlayer = null; var tempPlayer = null;
var tempAllServers = null;
var tempCompanies = null;
var tempFactions = null;
var tempSpecialServerIps = null;
var tempAliases = null;
var tempGlobalAliases = null;
var tempMessages = null;
var tempStockMarket = null;
var tempAllGangs = null;
let tempCorporationResearchTrees = null;
// Check to see if the imported save file can be parsed. If any // Check to see if the imported save file can be parsed. If any
// errors are caught it will fail // errors are caught it will fail
try { try {
var decodedSaveString = decodeURIComponent(escape(atob(saveString))); var decodedSaveString = decodeURIComponent(escape(atob(saveString)));
tempSaveObj = new BitburnerSaveObject();
tempSaveObj = JSON.parse(decodedSaveString, Reviver); tempSaveObj = JSON.parse(decodedSaveString, Reviver);
tempPlayer = JSON.parse(tempSaveObj.PlayerSave, Reviver); tempPlayer = JSON.parse(tempSaveObj.PlayerSave, Reviver);
@ -308,33 +299,27 @@ function loadImportedGame(saveObj, saveString) {
// Parse Decimal.js objects // Parse Decimal.js objects
tempPlayer.money = new Decimal(tempPlayer.money); tempPlayer.money = new Decimal(tempPlayer.money);
tempAllServers = JSON.parse(tempSaveObj.AllServersSave, Reviver); JSON.parse(tempSaveObj.AllServersSave, Reviver);
tempCompanies = JSON.parse(tempSaveObj.CompaniesSave, Reviver); JSON.parse(tempSaveObj.CompaniesSave, Reviver);
tempFactions = JSON.parse(tempSaveObj.FactionsSave, Reviver); JSON.parse(tempSaveObj.FactionsSave, Reviver);
tempSpecialServerIps = JSON.parse(tempSaveObj.SpecialServerIpsSave, Reviver); JSON.parse(tempSaveObj.SpecialServerIpsSave, Reviver);
if (tempSaveObj.hasOwnProperty("AliasesSave")) { if (tempSaveObj.hasOwnProperty("AliasesSave")) {
try { try {
tempAliases = JSON.parse(tempSaveObj.AliasesSave, Reviver); JSON.parse(tempSaveObj.AliasesSave, Reviver);
} catch(e) { } catch(e) {
console.error(`Parsing Aliases save failed: ${e}`); console.error(`Parsing Aliases save failed: ${e}`);
tempAliases = {};
} }
} else {
tempAliases = {};
} }
if (tempSaveObj.hasOwnProperty("GlobalAliases")) { if (tempSaveObj.hasOwnProperty("GlobalAliases")) {
try { try {
tempGlobalAliases = JSON.parse(tempSaveObj.AliasesSave, Reviver); JSON.parse(tempSaveObj.AliasesSave, Reviver);
} catch(e) { } catch(e) {
console.error(`Parsing Global Aliases save failed: ${e}`); console.error(`Parsing Global Aliases save failed: ${e}`);
tempGlobalAliases = {};
} }
} else {
tempGlobalAliases = {};
} }
if (tempSaveObj.hasOwnProperty("MessagesSave")) { if (tempSaveObj.hasOwnProperty("MessagesSave")) {
try { try {
tempMessages = JSON.parse(tempSaveObj.MessagesSave, Reviver); JSON.parse(tempSaveObj.MessagesSave, Reviver);
} catch(e) { } catch(e) {
console.error(`Parsing Messages save failed: ${e}`); console.error(`Parsing Messages save failed: ${e}`);
initMessages(); initMessages();
@ -344,13 +329,18 @@ function loadImportedGame(saveObj, saveString) {
} }
if (saveObj.hasOwnProperty("StockMarketSave")) { if (saveObj.hasOwnProperty("StockMarketSave")) {
try { try {
tempStockMarket = JSON.parse(tempSaveObj.StockMarketSave, Reviver); JSON.parse(tempSaveObj.StockMarketSave, Reviver);
} catch(e) { } catch(e) {
console.error(`Parsing StockMarket save failed: ${e}`); console.error(`Parsing StockMarket save failed: ${e}`);
tempStockMarket = {};
} }
} else { }
tempStockMarket = {}; if (saveObj.hasOwnProperty("LastExportBonus")) {
try {
ExportBonus.LastExportBonus = JSON.parse(saveObj.LastExportBonus);
} catch(err) {
ExportBonus.LastExportBonus = (new Date()).getTime();
console.error("ERROR: Failed to parse .fconf Settings "+ err);
}
} }
if (tempSaveObj.hasOwnProperty("VersionSave")) { if (tempSaveObj.hasOwnProperty("VersionSave")) {
try { try {
@ -359,7 +349,6 @@ function loadImportedGame(saveObj, saveString) {
} catch(e) { } catch(e) {
console.error("Parsing Version save failed: " + e); console.error("Parsing Version save failed: " + e);
} }
} else {
} }
if (tempPlayer.inGang() && tempSaveObj.hasOwnProperty("AllGangsSave")) { if (tempPlayer.inGang() && tempSaveObj.hasOwnProperty("AllGangsSave")) {
try { try {
@ -457,75 +446,8 @@ function loadImportedGame(saveObj, saveString) {
console.error("ERROR: Failed to parse AllGangsSave: " + e); console.error("ERROR: Failed to parse AllGangsSave: " + e);
} }
} }
saveObject.saveGame(Engine.indexedDb);
var popupId = "import-game-restart-game-notice"; location.reload();
var txt = createElement("p", {
innerText:"Imported game! You need to SAVE the game and then RELOAD the page " +
"to make sure everything runs smoothly",
});
var gotitBtn = createElement("a", {
class:"a-link-button", float:"right", padding:"6px", innerText:"Got it!",
clickListener:() => {
removeElementById(popupId);
},
});
createPopup(popupId, [txt, gotitBtn]);
gameOptionsBoxClose();
// Re-start game
Engine.setDisplayElements(); // Sets variables for important DOM elements
Engine.init(); // Initialize buttons, work, etc.
// Calculate the number of cycles have elapsed while offline
Engine._lastUpdate = new Date().getTime();
var lastUpdate = Player.lastUpdate;
var numCyclesOffline = Math.floor((Engine._lastUpdate - lastUpdate) / Engine._idleSpeed);
// Process offline progress
var offlineProductionFromScripts = loadAllRunningScripts(); // This also takes care of offline production for those scripts
if (Player.isWorking) {
if (Player.workType == CONSTANTS.WorkTypeFaction) {
Player.workForFaction(numCyclesOffline);
} else if (Player.workType == CONSTANTS.WorkTypeCreateProgram) {
Player.createProgramWork(numCyclesOffline);
} else if (Player.workType == CONSTANTS.WorkTypeStudyClass) {
Player.takeClass(numCyclesOffline);
} else if (Player.workType == CONSTANTS.WorkTypeCrime) {
Player.commitCrime(numCyclesOffline);
} else if (Player.workType == CONSTANTS.WorkTypeCompanyPartTime) {
Player.workPartTime(numCyclesOffline);
} else {
Player.work(numCyclesOffline);
}
}
// Hacknet Nodes offline progress
var offlineProductionFromHacknetNodes = processHacknetEarnings(numCyclesOffline);
// Passive faction rep gain offline
processPassiveFactionRepGain(numCyclesOffline);
// Update total playtime
var time = numCyclesOffline * Engine._idleSpeed;
if (Player.totalPlaytime == null) {Player.totalPlaytime = 0;}
if (Player.playtimeSinceLastAug == null) {Player.playtimeSinceLastAug = 0;}
if (Player.playtimeSinceLastBitnode == null) {Player.playtimeSinceLastBitnode = 0;}
Player.totalPlaytime += time;
Player.playtimeSinceLastAug += time;
Player.playtimeSinceLastBitnode += time;
// Re-apply augmentations
Player.reapplyAllAugmentations();
// Clear terminal
$("#terminal tr:not(:last)").remove();
Player.lastUpdate = Engine._lastUpdate;
Engine.start(); // Run main game loop and Scripts loop
const timeOfflineString = convertTimeMsToTimeElapsedString(time);
dialogBoxCreate(<>Offline for {timeOfflineString}. While you were offline, your scripts
generated {Money(offlineProductionFromScripts)}
and your Hacknet Nodes generated hacknetProdInfo</>);
return true; return true;
} }

@ -311,90 +311,51 @@ function saveAndCloseScriptEditor() {
export function scriptCalculateOfflineProduction(runningScriptObj) { export function scriptCalculateOfflineProduction(runningScriptObj) {
//The Player object stores the last update time from when we were online //The Player object stores the last update time from when we were online
var thisUpdate = new Date().getTime(); const thisUpdate = new Date().getTime();
var lastUpdate = Player.lastUpdate; const lastUpdate = Player.lastUpdate;
var timePassed = (thisUpdate - lastUpdate) / 1000; //Seconds const timePassed = (thisUpdate - lastUpdate) / 1000; //Seconds
//Calculate the "confidence" rating of the script's true production. This is based //Calculate the "confidence" rating of the script's true production. This is based
//entirely off of time. We will arbitrarily say that if a script has been running for //entirely off of time. We will arbitrarily say that if a script has been running for
//4 hours (14400 sec) then we are completely confident in its ability //4 hours (14400 sec) then we are completely confident in its ability
var confidence = (runningScriptObj.onlineRunningTime) / 14400; let confidence = (runningScriptObj.onlineRunningTime) / 14400;
if (confidence >= 1) {confidence = 1;} if (confidence >= 1) {confidence = 1;}
//Data map: [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken] //Data map: [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken]
// Grow // Grow
for (var ip in runningScriptObj.dataMap) { for (const ip in runningScriptObj.dataMap) {
if (runningScriptObj.dataMap.hasOwnProperty(ip)) { if (runningScriptObj.dataMap.hasOwnProperty(ip)) {
if (runningScriptObj.dataMap[ip][2] == 0 || runningScriptObj.dataMap[ip][2] == null) {continue;} if (runningScriptObj.dataMap[ip][2] == 0 || runningScriptObj.dataMap[ip][2] == null) {continue;}
var serv = AllServers[ip]; const serv = AllServers[ip];
if (serv == null) {continue;} if (serv == null) {continue;}
var timesGrown = Math.round(0.5 * runningScriptObj.dataMap[ip][2] / runningScriptObj.onlineRunningTime * timePassed); const timesGrown = Math.round(0.5 * runningScriptObj.dataMap[ip][2] / runningScriptObj.onlineRunningTime * timePassed);
runningScriptObj.log("Called grow() on " + serv.hostname + " " + timesGrown + " times while offline"); runningScriptObj.log(`Called on ${serv.hostname} ${timesGrown} times while offline`);
var growth = processSingleServerGrowth(serv, timesGrown, Player); const growth = processSingleServerGrowth(serv, timesGrown, Player);
runningScriptObj.log(serv.hostname + " grown by " + numeralWrapper.format(growth * 100 - 100, '0.000000%') + " from grow() calls made while offline"); runningScriptObj.log(`'${serv.hostname}' grown by ${numeralWrapper.format(growth * 100 - 100, '0.000000%')} while offline`);
}
}
// Money from hacking
var totalOfflineProduction = 0;
for (var ip in runningScriptObj.dataMap) {
if (runningScriptObj.dataMap.hasOwnProperty(ip)) {
if (runningScriptObj.dataMap[ip][0] == 0 || runningScriptObj.dataMap[ip][0] == null) {continue;}
var serv = AllServers[ip];
if (serv == null) {continue;}
var production = 0.5 * runningScriptObj.dataMap[ip][0] / runningScriptObj.onlineRunningTime * timePassed;
production *= confidence;
if (production > serv.moneyAvailable) {
production = serv.moneyAvailable;
}
totalOfflineProduction += production;
Player.gainMoney(production);
Player.recordMoneySource(production, "hacking");
runningScriptObj.log(runningScriptObj.filename + " generated $" + production + " while offline by hacking " + serv.hostname);
serv.moneyAvailable -= production;
if (serv.moneyAvailable < 0) {serv.moneyAvailable = 0;}
if (isNaN(serv.moneyAvailable)) {serv.moneyAvailable = 0;}
} }
} }
// Offline EXP gain // Offline EXP gain
// A script's offline production will always be at most half of its online production. // A script's offline production will always be at most half of its online production.
var expGain = 0.5 * (runningScriptObj.onlineExpGained / runningScriptObj.onlineRunningTime) * timePassed; const expGain = confidence * (runningScriptObj.onlineExpGained / runningScriptObj.onlineRunningTime) * timePassed;
expGain *= confidence;
Player.gainHackingExp(expGain); Player.gainHackingExp(expGain);
// Update script stats // Update script stats
runningScriptObj.offlineMoneyMade += totalOfflineProduction;
runningScriptObj.offlineRunningTime += timePassed; runningScriptObj.offlineRunningTime += timePassed;
runningScriptObj.offlineExpGained += expGain; runningScriptObj.offlineExpGained += expGain;
// Fortify a server's security based on how many times it was hacked
for (var ip in runningScriptObj.dataMap) {
if (runningScriptObj.dataMap.hasOwnProperty(ip)) {
if (runningScriptObj.dataMap[ip][1] == 0 || runningScriptObj.dataMap[ip][1] == null) {continue;}
var serv = AllServers[ip];
if (serv == null) {continue;}
var timesHacked = Math.round(0.5 * runningScriptObj.dataMap[ip][1] / runningScriptObj.onlineRunningTime * timePassed);
runningScriptObj.log("Hacked " + serv.hostname + " " + timesHacked + " times while offline");
serv.fortify(CONSTANTS.ServerFortifyAmount * timesHacked);
}
}
// Weaken // Weaken
for (var ip in runningScriptObj.dataMap) { for (const ip in runningScriptObj.dataMap) {
if (runningScriptObj.dataMap.hasOwnProperty(ip)) { if (runningScriptObj.dataMap.hasOwnProperty(ip)) {
if (runningScriptObj.dataMap[ip][3] == 0 || runningScriptObj.dataMap[ip][3] == null) {continue;} if (runningScriptObj.dataMap[ip][3] == 0 || runningScriptObj.dataMap[ip][3] == null) {continue;}
var serv = AllServers[ip]; const serv = AllServers[ip];
if (serv == null) {continue;} if (serv == null) {continue;}
var timesWeakened = Math.round(0.5 * runningScriptObj.dataMap[ip][3] / runningScriptObj.onlineRunningTime * timePassed); const timesWeakened = Math.round(0.5 * runningScriptObj.dataMap[ip][3] / runningScriptObj.onlineRunningTime * timePassed);
runningScriptObj.log("Called weaken() on " + serv.hostname + " " + timesWeakened + " times while offline"); runningScriptObj.log(`Called weaken() on ${serv.hostname} ${timesWeakened} times while offline`);
serv.weaken(CONSTANTS.ServerWeakenAmount * timesWeakened); serv.weaken(CONSTANTS.ServerWeakenAmount * timesWeakened);
} }
} }
return totalOfflineProduction;
} }
//Returns a RunningScript object matching the filename and arguments on the //Returns a RunningScript object matching the filename and arguments on the

@ -233,19 +233,25 @@ class AceEditorWrapper extends ScriptEditor {
// Highlight Active line // Highlight Active line
const highlightActiveChkBox = safeClearEventListeners("script-editor-option-highlightactiveline", "Active Line Checkbox"); const highlightActiveChkBox = safeClearEventListeners("script-editor-option-highlightactiveline", "Active Line Checkbox");
highlightActiveChkBox.checked = Settings.EditorHighlightActiveLine;
highlightActiveChkBox.onchange = () => { highlightActiveChkBox.onchange = () => {
Settings.EditorHighlightActiveLine = highlightActiveChkBox.checked;
this.editor.setHighlightActiveLine(highlightActiveChkBox.checked); this.editor.setHighlightActiveLine(highlightActiveChkBox.checked);
}; };
// Show Invisibles // Show Invisibles
const showInvisiblesChkBox = safeClearEventListeners("script-editor-option-showinvisibles", "Show Invisible Checkbox"); const showInvisiblesChkBox = safeClearEventListeners("script-editor-option-showinvisibles", "Show Invisible Checkbox");
showInvisiblesChkBox.checked = Settings.EditorShowInvisibles;
showInvisiblesChkBox.onchange = () => { showInvisiblesChkBox.onchange = () => {
Settings.EditorShowInvisibles = showInvisiblesChkBox.checked;
this.editor.setShowInvisibles(showInvisiblesChkBox.checked); this.editor.setShowInvisibles(showInvisiblesChkBox.checked);
}; };
// Use Soft Tab // Use Soft Tab
const softTabChkBox = safeClearEventListeners("script-editor-option-usesofttab", "Soft Tab Checkbox"); const softTabChkBox = safeClearEventListeners("script-editor-option-usesofttab", "Soft Tab Checkbox");
softTabChkBox.checked = Settings.EditorUseSoftTab;
softTabChkBox.onchange = () => { softTabChkBox.onchange = () => {
Settings.EditorUseSoftTab = softTabChkBox.checked;
this.editor.getSession().setUseSoftTabs(softTabChkBox.checked); this.editor.getSession().setUseSoftTabs(softTabChkBox.checked);
}; };

@ -375,14 +375,18 @@ class CodeMirrorEditorWrapper extends ScriptEditor {
// Highlight Active line // Highlight Active line
const highlightActiveChkBox = safeClearEventListeners("script-editor-option-highlightactiveline", "Active Line Checkbox"); const highlightActiveChkBox = safeClearEventListeners("script-editor-option-highlightactiveline", "Active Line Checkbox");
highlightActiveChkBox.checked = Settings.EditorHighlightActiveLine;
highlightActiveChkBox.onchange = () => { highlightActiveChkBox.onchange = () => {
Settings.EditorHighlightActiveLine = highlightActiveChkBox.checked;
this.editor.setOption("styleActiveLine", highlightActiveChkBox.checked); this.editor.setOption("styleActiveLine", highlightActiveChkBox.checked);
}; };
highlightActiveChkBox.onchange(); highlightActiveChkBox.onchange();
// Show Invisibles // Show Invisibles
const showInvisiblesChkBox = safeClearEventListeners("script-editor-option-showinvisibles", "Show Invisible Checkbox"); const showInvisiblesChkBox = safeClearEventListeners("script-editor-option-showinvisibles", "Show Invisible Checkbox");
showInvisiblesChkBox.checked = Settings.EditorShowInvisibles;
showInvisiblesChkBox.onchange = () => { showInvisiblesChkBox.onchange = () => {
Settings.EditorShowInvisibles = showInvisiblesChkBox.checked;
const overlayMode = { const overlayMode = {
name: 'invisibles', name: 'invisibles',
token: function(stream) { token: function(stream) {
@ -428,7 +432,9 @@ class CodeMirrorEditorWrapper extends ScriptEditor {
//Use Soft Tab //Use Soft Tab
const softTabChkBox = safeClearEventListeners("script-editor-option-usesofttab", "Soft Tab Checkbox"); const softTabChkBox = safeClearEventListeners("script-editor-option-usesofttab", "Soft Tab Checkbox");
softTabChkBox.checked = Settings.EditorUseSoftTab;
softTabChkBox.onchange = () => { softTabChkBox.onchange = () => {
Settings.EditorUseSoftTab = softTabChkBox.checked;
this.editor.setOption("indentWithTabs", !softTabChkBox.checked); this.editor.setOption("indentWithTabs", !softTabChkBox.checked);
if (softTabChkBox.checked) { if (softTabChkBox.checked) {
this.editor.addKeyMap({ this.editor.addKeyMap({
@ -482,13 +488,14 @@ class CodeMirrorEditorWrapper extends ScriptEditor {
})); }));
const flex1Checkbox = createElement("input", { const flex1Checkbox = createElement("input", {
checked: true, checked: Settings.EditorAutoCloseBrackets,
id: flex1Id, id: flex1Id,
name: flex1Id, name: flex1Id,
type: "checkbox", type: "checkbox",
}); });
flex1Fieldset.appendChild(flex1Checkbox); flex1Fieldset.appendChild(flex1Checkbox);
flex1Checkbox.onchange = () => { flex1Checkbox.onchange = () => {
Settings.EditorAutoCloseBrackets = flex1Checkbox.checked;
this.editor.setOption("autoCloseBrackets", flex1Checkbox.checked); this.editor.setOption("autoCloseBrackets", flex1Checkbox.checked);
}; };
flex1Checkbox.onchange(); flex1Checkbox.onchange();
@ -502,7 +509,7 @@ class CodeMirrorEditorWrapper extends ScriptEditor {
})); }));
const flex2Checkbox = createElement("input", { const flex2Checkbox = createElement("input", {
checked: true, checked: Settings.EditorEnableLinting,
id: flex2Id, id: flex2Id,
name: flex2Id, name: flex2Id,
type: "checkbox", type: "checkbox",
@ -510,8 +517,10 @@ class CodeMirrorEditorWrapper extends ScriptEditor {
flex2Fieldset.appendChild(flex2Checkbox); flex2Fieldset.appendChild(flex2Checkbox);
flex2Checkbox.onchange = () => { flex2Checkbox.onchange = () => {
if (flex2Checkbox.checked) { if (flex2Checkbox.checked) {
Settings.EditorEnableLinting = true;
this.editor.setOption("lint", CodeMirror.lint.netscript); this.editor.setOption("lint", CodeMirror.lint.netscript);
} else { } else {
Settings.EditorEnableLinting = false;
this.editor.setOption("lint", false); this.editor.setOption("lint", false);
} }
} }
@ -526,13 +535,14 @@ class CodeMirrorEditorWrapper extends ScriptEditor {
})); }));
const flex3Checkbox = createElement("input", { const flex3Checkbox = createElement("input", {
checked: true, checked: Settings.EditorContinueComments,
id: flex3Id, id: flex3Id,
name: flex3Id, name: flex3Id,
type: "checkbox", type: "checkbox",
}); });
flex3Fieldset.appendChild(flex3Checkbox); flex3Fieldset.appendChild(flex3Checkbox);
flex3Checkbox.onchange = () => { flex3Checkbox.onchange = () => {
Settings.EditorContinueComments = flex3Checkbox.checked;
this.editor.setOption("continueComments", flex3Checkbox.checked); this.editor.setOption("continueComments", flex3Checkbox.checked);
} }
flex3Checkbox.onchange(); flex3Checkbox.onchange();

@ -181,6 +181,7 @@ export class BaseServer {
* @returns {IReturnStatus} Return status object indicating whether or not file was deleted * @returns {IReturnStatus} Return status object indicating whether or not file was deleted
*/ */
removeFile(fn: string): IReturnStatus { removeFile(fn: string): IReturnStatus {
console.log(`removing ${fn}`);
if (fn.endsWith(".exe") || fn.match(/^.+\.exe-\d+(?:\.\d*)?%-INC$/) != null) { if (fn.endsWith(".exe") || fn.match(/^.+\.exe-\d+(?:\.\d*)?%-INC$/) != null) {
for (let i = 0; i < this.programs.length; ++i) { for (let i = 0; i < this.programs.length; ++i) {
if (this.programs[i] === fn) { if (this.programs[i] === fn) {

@ -74,6 +74,11 @@ interface IDefaultSettings {
* Whether the user should be asked to confirm travelling between cities. * Whether the user should be asked to confirm travelling between cities.
*/ */
SuppressTravelConfirmation: boolean; SuppressTravelConfirmation: boolean;
/**
* Whether the user should be displayed a popup message when his Bladeburner actions are cancelled.
*/
SuppressBladeburnerPopup: boolean;
} }
/** /**
@ -106,6 +111,36 @@ interface ISettings extends IDefaultSettings {
* What order the Augmentations should be displayed in when purchasing from a Faction * What order the Augmentations should be displayed in when purchasing from a Faction
*/ */
PurchaseAugmentationsOrder: PurchaseAugmentationsOrderSetting; PurchaseAugmentationsOrder: PurchaseAugmentationsOrderSetting;
/**
* Editor settings to highlight active line.
*/
EditorHighlightActiveLine: boolean;
/**
* Editor settings to show spaces and tabs.
*/
EditorShowInvisibles: boolean;
/**
* Editor settings to use tabs or 4 spaces.
*/
EditorUseSoftTab: boolean;
/**
* Editor settings to add matching bracket.
*/
EditorAutoCloseBrackets: boolean;
/**
* Editor settings to show linting (like missing semicolons)
*/
EditorEnableLinting: boolean;
/**
* Editor settings to add extra * when entering new line inside a /* comment.
*/
EditorContinueComments: boolean;
} }
const defaultSettings: IDefaultSettings = { const defaultSettings: IDefaultSettings = {
@ -122,6 +157,7 @@ const defaultSettings: IDefaultSettings = {
SuppressHospitalizationPopup: false, SuppressHospitalizationPopup: false,
SuppressMessages: false, SuppressMessages: false,
SuppressTravelConfirmation: false, SuppressTravelConfirmation: false,
SuppressBladeburnerPopup: false,
}; };
/** /**
@ -147,6 +183,13 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
SuppressHospitalizationPopup: defaultSettings.SuppressHospitalizationPopup, SuppressHospitalizationPopup: defaultSettings.SuppressHospitalizationPopup,
SuppressMessages: defaultSettings.SuppressMessages, SuppressMessages: defaultSettings.SuppressMessages,
SuppressTravelConfirmation: defaultSettings.SuppressTravelConfirmation, SuppressTravelConfirmation: defaultSettings.SuppressTravelConfirmation,
SuppressBladeburnerPopup: defaultSettings.SuppressBladeburnerPopup,
EditorHighlightActiveLine: true,
EditorShowInvisibles: false,
EditorUseSoftTab: true,
EditorAutoCloseBrackets: true,
EditorEnableLinting: true,
EditorContinueComments: true,
init() { init() {
Object.assign(Settings, defaultSettings); Object.assign(Settings, defaultSettings);
}, },

@ -10,6 +10,7 @@ import {
import { determineAllPossibilitiesForTabCompletion } from "./Terminal/determineAllPossibilitiesForTabCompletion"; import { determineAllPossibilitiesForTabCompletion } from "./Terminal/determineAllPossibilitiesForTabCompletion";
import { TerminalHelpText, HelpTexts } from "./Terminal/HelpText"; import { TerminalHelpText, HelpTexts } from "./Terminal/HelpText";
import { tabCompletion } from "./Terminal/tabCompletion"; import { tabCompletion } from "./Terminal/tabCompletion";
import { createFconf } from "./Fconf/Fconf";
import { import {
parseAliasDeclaration, parseAliasDeclaration,
@ -625,7 +626,11 @@ let Terminal = {
Terminal.commandHistoryIndex = Terminal.commandHistory.length; Terminal.commandHistoryIndex = Terminal.commandHistory.length;
// Split commands and execute sequentially // Split commands and execute sequentially
commands = commands.split(";"); commands = commands
.match(/(?:'[^']*'|"[^"]*"|[^;"])*/g)
.map(substituteAliases)
.map(c => c.match(/(?:'[^']*'|"[^"]*"|[^;"])*/g))
.flat();
for (let i = 0; i < commands.length; i++) { for (let i = 0; i < commands.length; i++) {
if(commands[i].match(/^\s*$/)) { continue; } // Don't run commands that only have whitespace if(commands[i].match(/^\s*$/)) { continue; } // Don't run commands that only have whitespace
Terminal.executeCommand(commands[i].trim()); Terminal.executeCommand(commands[i].trim());
@ -727,9 +732,6 @@ let Terminal = {
return; return;
} }
// Process any aliases
command = substituteAliases(command);
// Allow usage of ./ // Allow usage of ./
if (command.startsWith("./")) { if (command.startsWith("./")) {
command = "run " + command.slice(2); command = "run " + command.slice(2);
@ -873,7 +875,7 @@ let Terminal = {
if (commandArray.length === 3) { if (commandArray.length === 3) {
if (commandArray[1] === "-g") { if (commandArray[1] === "-g") {
if (parseAliasDeclaration(commandArray[2], true)) { if (parseAliasDeclaration(commandArray[2], true)) {
post(`Set global alias ${commandArray[1]}`); post(`Set global alias ${commandArray[2]}`);
return; return;
} }
} }
@ -1311,9 +1313,17 @@ let Terminal = {
} }
// Check programs // Check programs
let delTarget = Terminal.getFilepath(commandArray[1]); let delTarget, status;
try {
delTarget = Terminal.getFilepath(commandArray[1]);
status = s.removeFile(delTarget);
} catch(err) {
status = {
res: false,
msg: 'No such file exists'
};
}
const status = s.removeFile(delTarget);
if (!status.res) { if (!status.res) {
postError(status.msg); postError(status.msg);
} }
@ -1771,19 +1781,22 @@ let Terminal = {
i--; i--;
postContent(row, config); postContent(row, config);
} }
if(segments.length > 0) {
postElement(<br />);
}
} }
const config = { color: "#0000FF" }; const config = { color: "#0000FF" };
postSegments(folders, config); const groups = [
postSegments(allMessages); {segments: folders, config: config},
postSegments(allTextFiles); {segments: allMessages},
postSegments(allPrograms); {segments: allTextFiles},
postSegments(allContracts); {segments: allPrograms},
postSegments(allScripts); {segments: allContracts},
{segments: allScripts},
].filter((g) => g.segments.length > 0)
for(let i = 0; i < groups.length; i++) {
if(i !== 0) postElement(<br />);
postSegments(groups[i].segments, groups[i].config);
}
}, },
executeMemCommand: function(commandArray) { executeMemCommand: function(commandArray) {
@ -1868,30 +1881,27 @@ let Terminal = {
// Displays available network connections using TCP // Displays available network connections using TCP
const currServ = Player.getCurrentServer(); const currServ = Player.getCurrentServer();
post("Hostname IP Root Access"); const servers = currServ.serversOnNetwork.map((_, i) => {
for (let i = 0; i < currServ.serversOnNetwork.length; i++) { const server = getServerOnNetwork(currServ, i);
// Add hostname return {
let entry = getServerOnNetwork(currServ, i); hostname: server.hostname,
if (entry == null) { continue; } ip: server.ip,
entry = entry.hostname; hasRoot: server.hasAdminRights ? "Y" : "N"
// Calculate padding and add IP
let numSpaces = 21 - entry.length;
let spaces = Array(numSpaces+1).join(" ");
entry += spaces;
entry += getServerOnNetwork(currServ, i).ip;
// Calculate padding and add root access info
let hasRoot;
if (getServerOnNetwork(currServ, i).hasAdminRights) {
hasRoot = 'Y';
} else {
hasRoot = 'N';
} }
numSpaces = 21 - getServerOnNetwork(currServ, i).ip.length; });
spaces = Array(numSpaces+1).join(" "); servers.unshift({
entry += spaces; hostname: "Hostname",
entry += hasRoot; ip: "IP",
hasRoot: "Root Access",
})
const maxHostname = Math.max(...servers.map(s => s.hostname.length));
const maxIP = Math.max(...servers.map(s => s.ip.length));
for(const server of servers) {
let entry = server.hostname;
entry += " ".repeat(maxHostname-server.hostname.length+1);
entry += server.ip;
entry += " ".repeat(maxIP-server.ip.length+1);
entry += server.hasRoot;
post(entry); post(entry);
} }
}, },

@ -32,6 +32,11 @@ import {
processPassiveFactionRepGain, processPassiveFactionRepGain,
inviteToFaction, inviteToFaction,
} from "./Faction/FactionHelpers"; } from "./Faction/FactionHelpers";
import {
getHackingWorkRepGain,
getFactionSecurityWorkRepGain,
getFactionFieldWorkRepGain,
} from "./PersonObjects/formulas/reputation";
import { FconfSettings } from "./Fconf/FconfSettings"; import { FconfSettings } from "./Fconf/FconfSettings";
import { import {
hasHacknetServers, hasHacknetServers,
@ -91,6 +96,7 @@ import { Page, routing } from "./ui/navigationTracking";
import { setSettingsLabels } from "./ui/setSettingsLabels"; import { setSettingsLabels } from "./ui/setSettingsLabels";
import { Money } from "./ui/React/Money"; import { Money } from "./ui/React/Money";
import { Hashes } from "./ui/React/Hashes"; import { Hashes } from "./ui/React/Hashes";
import { Reputation } from "./ui/React/Reputation";
import { ActiveScriptsRoot } from "./ui/ActiveScripts/Root"; import { ActiveScriptsRoot } from "./ui/ActiveScripts/Root";
import { initializeMainMenuHeaders } from "./ui/MainMenu/Headers"; import { initializeMainMenuHeaders } from "./ui/MainMenu/Headers";
@ -230,6 +236,8 @@ const Engine = {
characterInfo: null, characterInfo: null,
}, },
indexedDb: undefined,
// Time variables (milliseconds unix epoch time) // Time variables (milliseconds unix epoch time)
_lastUpdate: new Date().getTime(), _lastUpdate: new Date().getTime(),
_idleSpeed: 200, // Speed (in ms) at which the main loop is updated _idleSpeed: 200, // Speed (in ms) at which the main loop is updated
@ -809,7 +817,7 @@ const Engine = {
Engine.Counters.autoSaveCounter = Infinity; Engine.Counters.autoSaveCounter = Infinity;
} else { } else {
Engine.Counters.autoSaveCounter = Settings.AutosaveInterval * 5; Engine.Counters.autoSaveCounter = Settings.AutosaveInterval * 5;
saveObject.saveGame(indexedDb); saveObject.saveGame(Engine.indexedDb);
} }
} }
@ -1079,11 +1087,15 @@ const Engine = {
// Calculate the number of cycles have elapsed while offline // Calculate the number of cycles have elapsed while offline
Engine._lastUpdate = new Date().getTime(); Engine._lastUpdate = new Date().getTime();
var lastUpdate = Player.lastUpdate; const lastUpdate = Player.lastUpdate;
var numCyclesOffline = Math.floor((Engine._lastUpdate - lastUpdate) / Engine._idleSpeed); const timeOffline = Engine._lastUpdate - lastUpdate;
const numCyclesOffline = Math.floor(timeOffline / Engine._idleSpeed);
let offlineReputation = 0
const offlineHackingIncome = Player.moneySourceA.hacking/(Player.playtimeSinceLastAug)*timeOffline*0.75;
Player.gainMoney(offlineHackingIncome);
// Process offline progress // Process offline progress
var offlineProductionFromScripts = loadAllRunningScripts(); // This also takes care of offline production for those scripts loadAllRunningScripts(); // This also takes care of offline production for those scripts
if (Player.isWorking) { if (Player.isWorking) {
if (Player.workType == CONSTANTS.WorkTypeFaction) { if (Player.workType == CONSTANTS.WorkTypeFaction) {
Player.workForFaction(numCyclesOffline); Player.workForFaction(numCyclesOffline);
@ -1098,6 +1110,31 @@ const Engine = {
} else { } else {
Player.work(numCyclesOffline); Player.work(numCyclesOffline);
} }
} else {
for(let i = 0; i < Player.factions.length; i++) {
const facName = Player.factions[i];
if (!Factions.hasOwnProperty(facName)) continue;
const faction = Factions[facName];
if (!faction.isMember) continue;
// No rep for special factions.
const info = faction.getInfo();
if(!info.offersWork()) continue;
// No rep for gangs.
if(Player.getGangName() === facName) continue;
const hRep = getHackingWorkRepGain(Player, faction);
const sRep = getFactionSecurityWorkRepGain(Player, faction);
const fRep = getFactionFieldWorkRepGain(Player, faction);
// can be infinite, doesn't matter.
const reputationRate = Math.max(hRep, sRep, fRep) / Player.factions.length;
const rep = reputationRate *
(numCyclesOffline);
faction.playerReputation += rep
offlineReputation += rep;
}
} }
// Hacknet Nodes offline progress // Hacknet Nodes offline progress
@ -1157,7 +1194,10 @@ const Engine = {
removeLoadingScreen(); removeLoadingScreen();
const timeOfflineString = convertTimeMsToTimeElapsedString(time); const timeOfflineString = convertTimeMsToTimeElapsedString(time);
dialogBoxCreate(<> dialogBoxCreate(<>
Offline for {timeOfflineString}. While you were offline, your scripts generated {Money(offlineProductionFromScripts)} and your Hacknet Nodes generated {hacknetProdInfo}. Offline for {timeOfflineString}. While you were offline, your scripts
generated {Money(offlineHackingIncome)}, your Hacknet Nodes
generated {hacknetProdInfo} and you
gained {Reputation(offlineReputation)} divided amongst your factions.
</>); </>);
// Close main menu accordions for loaded game // Close main menu accordions for loaded game
var visibleMenuTabs = [terminal, createScript, activeScripts, stats, var visibleMenuTabs = [terminal, createScript, activeScripts, stats,
@ -1430,13 +1470,13 @@ const Engine = {
// Save, Delete, Import/Export buttons // Save, Delete, Import/Export buttons
Engine.Clickables.saveMainMenuButton = document.getElementById("save-game-link"); Engine.Clickables.saveMainMenuButton = document.getElementById("save-game-link");
Engine.Clickables.saveMainMenuButton.addEventListener("click", function() { Engine.Clickables.saveMainMenuButton.addEventListener("click", function() {
saveObject.saveGame(indexedDb); saveObject.saveGame(Engine.indexedDb);
return false; return false;
}); });
Engine.Clickables.deleteMainMenuButton = document.getElementById("delete-game-link"); Engine.Clickables.deleteMainMenuButton = document.getElementById("delete-game-link");
Engine.Clickables.deleteMainMenuButton.addEventListener("click", function() { Engine.Clickables.deleteMainMenuButton.addEventListener("click", function() {
saveObject.deleteGame(indexedDb); saveObject.deleteGame(Engine.indexedDb);
return false; return false;
}); });
@ -1447,7 +1487,7 @@ const Engine = {
// Character Overview buttons // Character Overview buttons
document.getElementById("character-overview-save-button").addEventListener("click", function() { document.getElementById("character-overview-save-button").addEventListener("click", function() {
saveObject.saveGame(indexedDb); saveObject.saveGame(Engine.indexedDb);
return false; return false;
}); });
@ -1559,7 +1599,7 @@ const Engine = {
}, },
}; };
var indexedDb, indexedDbRequest; var indexedDbRequest;
window.onload = function() { window.onload = function() {
if (!window.indexedDB) { if (!window.indexedDB) {
return Engine.load(null); // Will try to load from localstorage return Engine.load(null); // Will try to load from localstorage
@ -1579,8 +1619,8 @@ window.onload = function() {
}; };
indexedDbRequest.onsuccess = function(e) { indexedDbRequest.onsuccess = function(e) {
indexedDb = e.target.result; Engine.indexedDb = e.target.result;
var transaction = indexedDb.transaction(["savestring"]); var transaction = Engine.indexedDb.transaction(["savestring"]);
var objectStore = transaction.objectStore("savestring"); var objectStore = transaction.objectStore("savestring");
var request = objectStore.get("save"); var request = objectStore.get("save");
request.onerror = function(e) { request.onerror = function(e) {
@ -1599,4 +1639,4 @@ window.onload = function() {
} }
}; };
export {Engine}; export {Engine, indexedDb};

@ -519,6 +519,16 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
<input class="optionCheckbox" type="checkbox" name="settingsSuppressHospitalizationPopup" id="settingsSuppressHospitalizationPopup"> <input class="optionCheckbox" type="checkbox" name="settingsSuppressHospitalizationPopup" id="settingsSuppressHospitalizationPopup">
</fieldset> </fieldset>
<!-- Suppress Bladeburner popups -->
<fieldset>
<label for="settingsSuppressBladeburnerPopup" class="tooltip">Suppress Bladeburner Popup:
<span class="tooltiptext">
If this is set, then having your Bladeburner actions interrupted by being busy with something else will not display a popup message.
</span>
</label>
<input class="optionCheckbox" type="checkbox" name="settingsSuppressBladeburnerPopup" id="settingsSuppressBladeburnerPopup">
</fieldset>
<!-- Disable Terminal and Navigation Shortcuts --> <!-- Disable Terminal and Navigation Shortcuts -->
<fieldset> <fieldset>
<label for="settingsDisableHotkeys" class="tooltip">Disable Hotkeys: <label for="settingsDisableHotkeys" class="tooltip">Disable Hotkeys:

@ -61,6 +61,7 @@ export function CharacterInfo(p: IPlayer): React.ReactElement {
if (src.infiltration) { parts.push([`Infiltration:`, Money(src.infiltration)]) } if (src.infiltration) { parts.push([`Infiltration:`, Money(src.infiltration)]) }
if (src.stock) { parts.push([`Stock Market:`, Money(src.stock)]) } if (src.stock) { parts.push([`Stock Market:`, Money(src.stock)]) }
if (src.casino) { parts.push([`Casino:`, Money(src.casino)]) } if (src.casino) { parts.push([`Casino:`, Money(src.casino)]) }
if (src.sleeves) { parts.push([`Sleeves:`, Money(src.sleeves)]) }
return StatsTable(parts, ""); return StatsTable(parts, "");
} }

@ -1,6 +1,6 @@
import {Engine} from "../engine"; import {Engine} from "../engine";
import {Settings} from "../Settings/Settings"; import {Settings} from "../Settings/Settings";
import {Player} from "../Player";
import {numeralWrapper} from "./numeralFormat"; import {numeralWrapper} from "./numeralFormat";
@ -21,6 +21,7 @@ function setSettingsLabels() {
const suppressTravelConfirmation = document.getElementById("settingsSuppressTravelConfirmation"); const suppressTravelConfirmation = document.getElementById("settingsSuppressTravelConfirmation");
const suppressBuyAugmentationConfirmation = document.getElementById("settingsSuppressBuyAugmentationConfirmation"); const suppressBuyAugmentationConfirmation = document.getElementById("settingsSuppressBuyAugmentationConfirmation");
const suppressHospitalizationPopup = document.getElementById("settingsSuppressHospitalizationPopup"); const suppressHospitalizationPopup = document.getElementById("settingsSuppressHospitalizationPopup");
const suppressBladeburnerPopup = document.getElementById("settingsSuppressBladeburnerPopup");
const autosaveInterval = document.getElementById("settingsAutosaveIntervalValLabel"); const autosaveInterval = document.getElementById("settingsAutosaveIntervalValLabel");
const disableHotkeys = document.getElementById("settingsDisableHotkeys"); const disableHotkeys = document.getElementById("settingsDisableHotkeys");
const disableASCIIArt = document.getElementById("settingsDisableASCIIArt"); const disableASCIIArt = document.getElementById("settingsDisableASCIIArt");
@ -36,6 +37,7 @@ function setSettingsLabels() {
suppressTravelConfirmation.checked = Settings.SuppressTravelConfirmation; suppressTravelConfirmation.checked = Settings.SuppressTravelConfirmation;
suppressBuyAugmentationConfirmation.checked = Settings.SuppressBuyAugmentationConfirmation; suppressBuyAugmentationConfirmation.checked = Settings.SuppressBuyAugmentationConfirmation;
suppressHospitalizationPopup.checked = Settings.SuppressHospitalizationPopup; suppressHospitalizationPopup.checked = Settings.SuppressHospitalizationPopup;
suppressBladeburnerPopup.checked = Settings.SuppressBladeburnerPopup;
setAutosaveLabel(autosaveInterval); setAutosaveLabel(autosaveInterval);
disableHotkeys.checked = Settings.DisableHotkeys; disableHotkeys.checked = Settings.DisableHotkeys;
disableASCIIArt.checked = Settings.CityListView; disableASCIIArt.checked = Settings.CityListView;
@ -99,6 +101,10 @@ function setSettingsLabels() {
Settings.SuppressHospitalizationPopup = this.checked; Settings.SuppressHospitalizationPopup = this.checked;
} }
suppressBladeburnerPopup.onclick = function() {
Settings.SuppressBladeburnerPopup = this.checked;
}
disableHotkeys.onclick = function() { disableHotkeys.onclick = function() {
Settings.DisableHotkeys = this.checked; Settings.DisableHotkeys = this.checked;
} }

@ -19,6 +19,7 @@ export class MoneySourceTracker {
hacknetnode = 0; hacknetnode = 0;
hospitalization = 0; hospitalization = 0;
infiltration = 0; infiltration = 0;
sleeves = 0;
stock = 0; stock = 0;
total = 0; total = 0;
work = 0; work = 0;

@ -1,4 +1,5 @@
/* GameOptions.js */ /* GameOptions.js */
import { Player } from "../src/Player";
//Close box when clicking outside //Close box when clicking outside
$(document).click(function(event) { $(document).click(function(event) {
@ -36,6 +37,11 @@ function gameOptionsBoxClose() {
function gameOptionsBoxOpen() { function gameOptionsBoxOpen() {
var box = document.getElementById("game-options-container"); var box = document.getElementById("game-options-container");
box.style.display = "flex"; box.style.display = "flex";
// special exception for bladeburner popup because it's only visible later.
document.getElementById("settingsSuppressBladeburnerPopup").
closest('fieldset').style.display =
Player.canAccessBladeburner() ? 'block' : 'none';
setTimeout(function() { setTimeout(function() {
gameOptionsOpened = true; gameOptionsOpened = true;
}, 500); }, 500);

@ -5,6 +5,7 @@ var HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = (env, argv) => { module.exports = (env, argv) => {
const isDevServer = (env || {}).devServer === true; const isDevServer = (env || {}).devServer === true;
const runInContainer = (env || {}).runInContainer === true;
const isDevelopment = argv.mode === 'development'; const isDevelopment = argv.mode === 'development';
const outputDirectory = isDevServer ? "dist-dev" : "dist"; const outputDirectory = isDevServer ? "dist-dev" : "dist";
const entries = {}; const entries = {};
@ -22,6 +23,22 @@ module.exports = (env, argv) => {
entrypoints: true, entrypoints: true,
} }
const devServerSettings = {
port: 8000,
publicPath: `/`,
stats: statsConfig,
};
// By default, the webpack-dev-server is not exposed outside of localhost.
// When running in a container we need it accessible externally.
if (runInContainer) {
devServerSettings.disableHostCheck = true;
devServerSettings.host = '0.0.0.0';
devServerSettings.watchOptions = {
poll: true,
}
}
return { return {
plugins: [ plugins: [
new webpack.DefinePlugin({ new webpack.DefinePlugin({
@ -131,11 +148,7 @@ module.exports = (env, argv) => {
}, },
}, },
}, },
devServer: { devServer: devServerSettings,
port: 8000,
publicPath: `/`,
stats: statsConfig,
},
resolve: { resolve: {
extensions: [ extensions: [
".tsx", ".tsx",