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

36
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

@ -3,8 +3,61 @@
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**
@ -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
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**
@ -118,7 +171,7 @@ v0.51.7 - 2021-04-28 n00dles
* 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**

@ -66,7 +66,7 @@ documentation_title = '{0} Documentation'.format(project)
# The short X.Y version.
version = '0.51'
# The full version, including alpha/beta/rc tags.
release = '0.51.8'
release = '0.51.9'
# The language for content autogenerated by Sphinx. Refer to documentation
# 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">
</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 -->
<fieldset>
<label for="settingsDisableHotkeys" class="tooltip">Disable Hotkeys:

86
package-lock.json generated

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

@ -10,7 +10,7 @@
"@types/numeral": "0.0.25",
"@types/react": "^16.8.6",
"@types/react-dom": "^16.8.2",
"acorn": "^6.2.0",
"acorn": "^6.4.1",
"acorn-walk": "^6.2.0",
"ajv": "^5.1.5",
"ajv-keywords": "^2.0.0",
@ -18,14 +18,14 @@
"async": "^2.6.1",
"autosize": "^4.0.2",
"brace": "^0.11.1",
"codemirror": "^5.43.0",
"codemirror": "^5.58.2",
"decimal.js": "7.2.3",
"enhanced-resolve": "^4.0.0",
"escodegen": "^1.11.0",
"escope": "^3.6.0",
"file-saver": "^1.3.8",
"interpret": "^1.0.0",
"jquery": "^3.3.1",
"jquery": "^3.5.0",
"jshint": "^2.10.2",
"json-loader": "^0.5.4",
"jsplumb": "^2.6.8",
@ -113,6 +113,7 @@
},
"scripts": {
"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:dev": "webpack --mode development",
"build:test": "webpack --config webpack.config-test.js",
@ -121,8 +122,9 @@
"lint:style": "stylelint --fix ./css/*",
"preinstall": "node ./scripts/engines-check.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: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) {
delete GlobalAliases[name];
}
Aliases[name] = value;
Aliases[name] = value.trim();
}
function addGlobalAlias(name: string, value: string): void {
if (name in Aliases){
delete Aliases[name];
}
GlobalAliases[name] = value;
GlobalAliases[name] = value.trim();
}
function getAlias(name: string): string | null {
@ -97,22 +97,29 @@ export function removeAlias(name: string): boolean {
export function substituteAliases(origCommand: string): string {
const commandArray = origCommand.split(" ");
if (commandArray.length > 0){
// For the unalias command, dont substite
if (commandArray[0] === "unalias") { return commandArray.join(" "); }
// For the alias and unalias commands, dont substite
if (commandArray[0] === "unalias" || commandArray[0] === "alias") { return commandArray.join(" "); }
const alias = getAlias(commandArray[0]);
if (alias != null) {
commandArray[0] = alias;
} else {
const alias = getGlobalAlias(commandArray[0]);
let somethingSubstituted = true;
let depth = 0;
while(somethingSubstituted && depth < 10){
depth++;
somethingSubstituted = false
const alias = getAlias(commandArray[0])?.split(" ");
if (alias != null) {
commandArray[0] = alias;
somethingSubstituted = true
commandArray.splice(0, 1, ...alias);
//commandArray[0] = alias;
}
}
for (let i = 0; i < commandArray.length; ++i) {
const alias = getGlobalAlias(commandArray[i]);
if (alias != null) {
commandArray[i] = alias;
for (let i = 0; i < commandArray.length; ++i) {
const alias = getGlobalAlias(commandArray[i])?.split(" ");
if (alias != null) {
somethingSubstituted = true
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 { saveObject } from "../SaveObject";
import { Page, routing } from "../ui/navigationTracking";
import { onExport } from "../ExportBonus";
import { dialogBoxCreate } from "../../utils/DialogBox";
import { clearObject } from "../../utils/helpers/clearObject";
@ -2077,9 +2078,14 @@ export function displayAugmentationsContent(contentEl) {
if (!routing.isOn(Page.Augmentations)) { return; }
if (!(contentEl instanceof HTMLElement)) { return; }
function backup() {
saveObject.exportGame();
onExport(Player);
}
ReactDOM.render(
<AugmentationsRoot
exportGameFn={saveObject.exportGame.bind(saveObject)}
exportGameFn={backup}
installAugmentationsFn={installAugmentations}
/>,
contentEl,

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

@ -24,6 +24,7 @@ import {
convertTimeMsToTimeElapsedString,
} from "../utils/StringHelperFunctions";
import { Settings } from "./Settings/Settings";
import { ConsoleHelpText } from "./Bladeburner/data/Help";
import { City } from "./Bladeburner/City";
import { BladeburnerConstants } from "./Bladeburner/data/Constants";
@ -158,6 +159,7 @@ function Bladeburner(params={}) {
// These times are in seconds
this.actionTimeToComplete = 0; // 0 or -1 is an infinite running action (like training)
this.actionTimeCurrent = 0;
this.actionTimeOverflow = 0;
// ActionIdentifier Object
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`
this.automateEnabled = false;
}
dialogBoxCreate(msg);
if (!Settings.SuppressBladeburnerPopup) {
dialogBoxCreate(msg);
}
}
this.resetAction();
}
@ -381,22 +385,16 @@ Bladeburner.prototype.process = function() {
this.stamina = Math.min(this.maxStamina, this.stamina);
// Count increase for contracts/operations
for (var contractName in this.contracts) {
if (this.contracts.hasOwnProperty(contractName)) {
var contract = this.contracts[contractName];
contract.count += (seconds * contract.countGrowth/BladeburnerConstants.ActionCountGrowthPeriod);
}
for (let contract of Object.values(this.contracts)) {
contract.count += (seconds * contract.countGrowth/BladeburnerConstants.ActionCountGrowthPeriod);
}
for (var operationName in this.operations) {
if (this.operations.hasOwnProperty(operationName)) {
var op = this.operations[operationName];
op.count += (seconds * op.countGrowth/BladeburnerConstants.ActionCountGrowthPeriod);
}
for (let op of Object.values(this.operations)) {
op.count += (seconds * op.countGrowth/BladeburnerConstants.ActionCountGrowthPeriod);
}
// Chaos goes down very slowly
for (var i = 0; i < BladeburnerConstants.CityNames.length; ++i) {
var city = this.cities[BladeburnerConstants.CityNames[i]];
for (let cityName of BladeburnerConstants.CityNames) {
var city = this.cities[cityName];
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 = Math.max(0, city.chaos);
@ -406,7 +404,8 @@ Bladeburner.prototype.process = function() {
this.randomEventCounter -= seconds;
if (this.randomEventCounter <= 0) {
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);
@ -664,8 +663,12 @@ Bladeburner.prototype.processAction = function(seconds) {
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) {
this.actionTimeOverflow = this.actionTimeCurrent - this.actionTimeToComplete;
return this.completeAction();
}
}
@ -1889,17 +1892,18 @@ Bladeburner.prototype.updateActionAndSkillsContent = function() {
Bladeburner.prototype.updateGeneralActionsUIElement = function(el, action) {
removeChildrenFromElement(el);
var isActive = el.classList.contains(ActiveActionCssClass);
var computedActionTimeCurrent = Math.min(this.actionTimeCurrent+this.actionTimeOverflow,this.actionTimeToComplete);
el.appendChild(createElement("h2", { // Header
innerText:isActive ? action.name + " (IN PROGRESS - " +
formatNumber(this.actionTimeCurrent, 0) + " / " +
formatNumber(computedActionTimeCurrent, 0) + " / " +
formatNumber(this.actionTimeToComplete, 0) + ")"
: action.name,
display:"inline-block",
}));
if (isActive) { // Progress bar if its active
var progress = this.actionTimeCurrent / this.actionTimeToComplete;
var progress = computedActionTimeCurrent / this.actionTimeToComplete;
el.appendChild(createElement("p", {
display:"block",
innerText:createProgressBarText({progress:progress}),
@ -1931,17 +1935,18 @@ Bladeburner.prototype.updateContractsUIElement = function(el, action) {
removeChildrenFromElement(el);
var isActive = el.classList.contains(ActiveActionCssClass);
var estimatedSuccessChance = action.getSuccessChance(this, {est:true});
var computedActionTimeCurrent = Math.min(this.actionTimeCurrent+this.actionTimeOverflow,this.actionTimeToComplete);
el.appendChild(createElement("h2", { // Header
innerText:isActive ? action.name + " (IN PROGRESS - " +
formatNumber(this.actionTimeCurrent, 0) + " / " +
formatNumber(computedActionTimeCurrent, 0) + " / " +
formatNumber(this.actionTimeToComplete, 0) + ")"
: action.name,
display:"inline-block",
}));
if (isActive) { // Progress bar if its active
var progress = this.actionTimeCurrent / this.actionTimeToComplete;
var progress = computedActionTimeCurrent / this.actionTimeToComplete;
el.appendChild(createElement("p", {
display:"block",
innerText:createProgressBarText({progress:progress}),
@ -2030,16 +2035,18 @@ Bladeburner.prototype.updateOperationsUIElement = function(el, action) {
removeChildrenFromElement(el);
var isActive = el.classList.contains(ActiveActionCssClass);
var estimatedSuccessChance = action.getSuccessChance(this, {est:true});
var computedActionTimeCurrent = Math.min(this.actionTimeCurrent+this.actionTimeOverflow,this.actionTimeToComplete);
el.appendChild(createElement("h2", { // Header
innerText:isActive ? action.name + " (IN PROGRESS - " +
formatNumber(this.actionTimeCurrent, 0) + " / " +
formatNumber(computedActionTimeCurrent, 0) + " / " +
formatNumber(this.actionTimeToComplete, 0) + ")"
: action.name,
display:"inline-block",
}));
if (isActive) { // Progress bar if its active
var progress = this.actionTimeCurrent / this.actionTimeToComplete;
var progress = computedActionTimeCurrent / this.actionTimeToComplete;
el.appendChild(createElement("p", {
display:"block",
innerText:createProgressBarText({progress:progress}),
@ -2093,6 +2100,7 @@ Bladeburner.prototype.updateOperationsUIElement = function(el, action) {
},
});
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 actionTime = action.getActionTime(this);
var hasReqdRank = this.rank >= action.reqdRank;
var computedActionTimeCurrent = Math.min(this.actionTimeCurrent+this.actionTimeOverflow,this.actionTimeToComplete);
// UI for Completed Black Op
if (isCompleted) {
@ -2182,14 +2191,14 @@ Bladeburner.prototype.updateBlackOpsUIElement = function(el, action) {
el.appendChild(createElement("h2", { // Header
innerText:isActive ? action.name + " (IN PROGRESS - " +
formatNumber(this.actionTimeCurrent, 0) + " / " +
formatNumber(computedActionTimeCurrent, 0) + " / " +
formatNumber(this.actionTimeToComplete, 0) + ")"
: action.name,
display:"inline-block",
}));
if (isActive) { // Progress bar if its active
var progress = this.actionTimeCurrent / this.actionTimeToComplete;
var progress = computedActionTimeCurrent / this.actionTimeToComplete;
el.appendChild(createElement("p", {
display:"block",
innerText:createProgressBarText({progress:progress}),
@ -2243,6 +2252,7 @@ Bladeburner.prototype.updateBlackOpsUIElement = function(el, action) {
},
});
createPopup(popupId, [txt, input, setBtn, cancelBtn]);
input.focus();
},
}));
}

@ -6,7 +6,7 @@
import { IMap } from "./types";
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
* 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:
`
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'
* Fix 'setActionAutolevel' logging.
* Fix 'setActionLevel' not working at all.
* Add 'installBackdoor' singularity function.
Export
* Exporting now gives +1 favor to all joined factions every 24h.
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.
* Fix documentation for maxNumNodes (@ModdedGamers)
* 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)
Terminal
* 'scan' now works for servers that are more than 21 character long.
Misc.
* Fix negative money being displayed in full.
* Fix Hacking Missions not working.
* Fix Corporation tree not rendering.
* Fix script being needlessly recompiled. This should save real ram (not game ram)
* w0r1d_d43m0n can be backdoored
* Coding Contracts title is click-to-copy (@Rodeth)
* Covenant memory upgrade works better.
* Fix Neuroflux not being correctly calculated when entering BN with SF12.
* Delete Active Script now delete all active scripts, not just home.
* Now you can 'cd' in directories that only contain '.txt' files.
* Fix 'analyze' always saying players had root access
* Passive faction rep no longer builds for special factions.
* Donation option no longer appears for special factions.
* Rephrased some milestones.
* 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.
* 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
`,
}

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

@ -15,6 +15,8 @@ import { GetServerByHostname } from "./Server/ServerHelpers";
import { hackWorldDaemon } from "./RedPill";
import { StockMarket } from "./StockMarket/StockMarket";
import { Stock } from "./StockMarket/Stock";
import { Engine } from "./engine";
import { saveObject } from "./SaveObject";
import { dialogBoxCreate } from "../utils/DialogBox";
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() {
let factions = [];
for (const i in Factions) {
@ -1212,6 +1223,19 @@ class DevMenuComponent extends Component {
</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>
);
}

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();
});
let content = (<div dangerouslySetInnerHTML={{__html: aug.info}}></div>);
if(typeof aug.info !== 'string') {
content = <div>{aug.info}</div>
}
yesNoBoxCreate(<>
<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;
{Money(aug.baseCost * factionInfo.augmentationPriceMult)}?
</>);

@ -19,7 +19,6 @@ export function getHospitalizationCost(p: IPlayer): number {
export function calculateHospitalizationCost(p: IPlayer, damage: number): number {
const oldhp = p.hp;
p.hp -= damage
if (p.hp < 0) p.hp = 0;
const cost = getHospitalizationCost(p);
p.hp = oldhp;
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");
return false;
}
p.loseMoney(150e9);
const companyName = nameInput.value;
if (companyName == null || companyName == "") {
@ -149,6 +148,7 @@ export function createStartCorporationPopup(p: IPlayer): void {
}
p.startCorporation(companyName);
p.loseMoney(150e9);
const worldHeader = document.getElementById("world-menu-header");
if (worldHeader instanceof HTMLElement) {

@ -108,7 +108,11 @@ export class SpecialLocation extends React.Component<IProps, IState> {
}
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 (
<AutoupdatingStdButton
disabled={!this.props.p.canAccessCorporation() || this.props.p.hasCorporation()}

@ -166,10 +166,7 @@ import { numeralWrapper } from "./ui/numeralFormat";
import { post } from "./ui/postToTerminal";
import { setTimeoutRef } from "./utils/SetTimeoutRef";
import { is2DArray } from "./utils/helpers/is2DArray";
import {
formatNumber,
convertTimeMsToTimeElapsedString,
} from "../utils/StringHelperFunctions";
import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
import { logBoxCreate } from "../utils/LogBox";
import { arrayToString } from "../utils/helpers/arrayToString";
@ -179,77 +176,6 @@ import { createElement } from "../utils/uiHelpers/createElement";
import { createPopup } from "../utils/uiHelpers/createPopup";
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);
// 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 +
". This is probably because you somehow circumvented the static RAM " +
"calculation.<br><br>Please don't do that :(<br><br>" +
"Dynamic RAM Usage: " + workerScript.dynamicRamUsage + "<br>" +
"Static RAM Usage: " + workerScript.ramUsage);
"Dynamic RAM Usage: " + numeralWrapper.formatRAM(workerScript.dynamicRamUsage) + "<br>" +
"Static RAM Usage: " + numeralWrapper.formatRAM(workerScript.ramUsage));
}
};
@ -739,7 +665,7 @@ function NetscriptFunctions(workerScript) {
return out;
}
return {
const functions = {
hacknet : {
numNodes : function() {
return Player.hacknetNodes.length;
@ -950,7 +876,7 @@ function NetscriptFunctions(workerScript) {
expGain = 0;
}
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;
Player.gainHackingExp(expGain);
if (stock) {
@ -1194,7 +1120,7 @@ function NetscriptFunctions(workerScript) {
throw makeRuntimeErrorMsg("run", "Usage: run(scriptname, [numThreads], [arg1], [arg2]...)");
}
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 = [];
for (var i = 2; i < arguments.length; ++i) {
@ -1663,28 +1589,28 @@ function NetscriptFunctions(workerScript) {
updateDynamicRam("getServerSecurityLevel", getRamCost("getServerSecurityLevel"));
const server = safeGetServer(ip, "getServerSecurityLevel");
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;
},
getServerBaseSecurityLevel: function(ip) {
updateDynamicRam("getServerBaseSecurityLevel", getRamCost("getServerBaseSecurityLevel"));
const server = safeGetServer(ip, "getServerBaseSecurityLevel");
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;
},
getServerMinSecurityLevel: function(ip) {
updateDynamicRam("getServerMinSecurityLevel", getRamCost("getServerMinSecurityLevel"));
const server = safeGetServer(ip, "getServerMinSecurityLevel");
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;
},
getServerRequiredHackingLevel: function(ip) {
updateDynamicRam("getServerRequiredHackingLevel", getRamCost("getServerRequiredHackingLevel"));
const server = safeGetServer(ip, "getServerRequiredHackingLevel");
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;
},
getServerMaxMoney: function(ip) {
@ -1698,32 +1624,32 @@ function NetscriptFunctions(workerScript) {
updateDynamicRam("getServerGrowth", getRamCost("getServerGrowth"));
const server = safeGetServer(ip, "getServerGrowth");
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;
},
getServerNumPortsRequired: function(ip) {
updateDynamicRam("getServerNumPortsRequired", getRamCost("getServerNumPortsRequired"));
const server = safeGetServer(ip, "getServerNumPortsRequired");
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;
},
getServerRam: function(ip) {
updateDynamicRam("getServerRam", getRamCost("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];
},
getServerMaxRam: function(ip) {
updateDynamicRam("getServerMaxRam", getRamCost("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;
},
getServerUsedRam: function(ip) {
updateDynamicRam("getServerUsedRam", getRamCost("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;
},
serverExists: function(ip) {
@ -4544,7 +4470,23 @@ function NetscriptFunctions(workerScript) {
}
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()
export { NetscriptFunctions };

@ -564,7 +564,6 @@ export function updateOnlineScriptTimes(numCycles = 1) {
* into worker scripts so that they will start running
*/
export function loadAllRunningScripts() {
var total = 0;
let skipScriptLoad = (window.location.href.toLowerCase().indexOf("?noscripts") !== -1);
if (skipScriptLoad) { console.info("Skipping the load of any scripts during startup"); }
for (const property in AllServers) {
@ -587,13 +586,11 @@ export function loadAllRunningScripts() {
createAndAddWorkerScript(server.runningScripts[j], server);
// 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.earningsForPlayer.money += gain;
p.gainMoney(gain);
p.recordMoneySource(gain, 'sleeves');
}
/**

@ -8,16 +8,10 @@ import { Companies, loadCompanies } from "./Company/Companies";
import { CONSTANTS } from "./Constants";
import { Engine } from "./engine";
import { Factions, loadFactions } from "./Faction/Factions";
import { processPassiveFactionRepGain } from "./Faction/FactionHelpers";
import { loadFconf } from "./Fconf/Fconf";
import { FconfSettings } from "./Fconf/FconfSettings";
import { loadAllGangs, AllGangs } from "./Gang";
import {
hasHacknetServers,
processHacknetEarnings,
} from "./Hacknet/HacknetHelpers";
import { loadMessages, initMessages, Messages } from "./Message/MessageHelpers";
import { loadAllRunningScripts } from "./NetscriptWorker";
import { Player, loadPlayer } from "./Player";
import { AllServers, loadAllServers } from "./Server/AllServers";
import { Settings } from "./Settings/Settings";
@ -31,19 +25,15 @@ import { loadStockMarket, StockMarket } from "./StockMarket/StockMarket";
import { createStatusText } from "./ui/createStatusText";
import { setTimeoutRef } from "./utils/SetTimeoutRef";
import * as ExportBonus from "./ExportBonus";
import { dialogBoxCreate } from "../utils/DialogBox";
import { gameOptionsBoxClose } from "../utils/GameOptions";
import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners";
import {
Reviver,
Generic_toJSON,
Generic_fromJSON,
} 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";
@ -66,6 +56,7 @@ function BitburnerSaveObject() {
this.FconfSettingsSave = "";
this.VersionSave = "";
this.AllGangsSave = "";
this.LastExportBonus = "";
}
BitburnerSaveObject.prototype.getSaveString = function() {
@ -94,6 +85,7 @@ BitburnerSaveObject.prototype.getSaveString = function() {
this.SettingsSave = JSON.stringify(Settings);
this.FconfSettingsSave = JSON.stringify(FconfSettings);
this.VersionSave = JSON.stringify(CONSTANTS.Version);
this.LastExportBonus = JSON.stringify(ExportBonus.LastExportBonus);
if (Player.inGang()) {
this.AllGangsSave = JSON.stringify(AllGangs);
}
@ -254,6 +246,16 @@ function loadGame(saveString) {
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")) {
try {
var ver = JSON.parse(saveObj.VersionSave, Reviver);
@ -285,22 +287,11 @@ function loadGame(saveString) {
function loadImportedGame(saveObj, saveString) {
var tempSaveObj = 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
// errors are caught it will fail
try {
var decodedSaveString = decodeURIComponent(escape(atob(saveString)));
tempSaveObj = new BitburnerSaveObject();
tempSaveObj = JSON.parse(decodedSaveString, Reviver);
tempPlayer = JSON.parse(tempSaveObj.PlayerSave, Reviver);
@ -308,33 +299,27 @@ function loadImportedGame(saveObj, saveString) {
// Parse Decimal.js objects
tempPlayer.money = new Decimal(tempPlayer.money);
tempAllServers = JSON.parse(tempSaveObj.AllServersSave, Reviver);
tempCompanies = JSON.parse(tempSaveObj.CompaniesSave, Reviver);
tempFactions = JSON.parse(tempSaveObj.FactionsSave, Reviver);
tempSpecialServerIps = JSON.parse(tempSaveObj.SpecialServerIpsSave, Reviver);
JSON.parse(tempSaveObj.AllServersSave, Reviver);
JSON.parse(tempSaveObj.CompaniesSave, Reviver);
JSON.parse(tempSaveObj.FactionsSave, Reviver);
JSON.parse(tempSaveObj.SpecialServerIpsSave, Reviver);
if (tempSaveObj.hasOwnProperty("AliasesSave")) {
try {
tempAliases = JSON.parse(tempSaveObj.AliasesSave, Reviver);
JSON.parse(tempSaveObj.AliasesSave, Reviver);
} catch(e) {
console.error(`Parsing Aliases save failed: ${e}`);
tempAliases = {};
}
} else {
tempAliases = {};
}
if (tempSaveObj.hasOwnProperty("GlobalAliases")) {
try {
tempGlobalAliases = JSON.parse(tempSaveObj.AliasesSave, Reviver);
JSON.parse(tempSaveObj.AliasesSave, Reviver);
} catch(e) {
console.error(`Parsing Global Aliases save failed: ${e}`);
tempGlobalAliases = {};
}
} else {
tempGlobalAliases = {};
}
if (tempSaveObj.hasOwnProperty("MessagesSave")) {
try {
tempMessages = JSON.parse(tempSaveObj.MessagesSave, Reviver);
JSON.parse(tempSaveObj.MessagesSave, Reviver);
} catch(e) {
console.error(`Parsing Messages save failed: ${e}`);
initMessages();
@ -344,13 +329,18 @@ function loadImportedGame(saveObj, saveString) {
}
if (saveObj.hasOwnProperty("StockMarketSave")) {
try {
tempStockMarket = JSON.parse(tempSaveObj.StockMarketSave, Reviver);
JSON.parse(tempSaveObj.StockMarketSave, Reviver);
} catch(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")) {
try {
@ -359,7 +349,6 @@ function loadImportedGame(saveObj, saveString) {
} catch(e) {
console.error("Parsing Version save failed: " + e);
}
} else {
}
if (tempPlayer.inGang() && tempSaveObj.hasOwnProperty("AllGangsSave")) {
try {
@ -457,75 +446,8 @@ function loadImportedGame(saveObj, saveString) {
console.error("ERROR: Failed to parse AllGangsSave: " + e);
}
}
var popupId = "import-game-restart-game-notice";
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</>);
saveObject.saveGame(Engine.indexedDb);
location.reload();
return true;
}

@ -311,90 +311,51 @@ function saveAndCloseScriptEditor() {
export function scriptCalculateOfflineProduction(runningScriptObj) {
//The Player object stores the last update time from when we were online
var thisUpdate = new Date().getTime();
var lastUpdate = Player.lastUpdate;
var timePassed = (thisUpdate - lastUpdate) / 1000; //Seconds
const thisUpdate = new Date().getTime();
const lastUpdate = Player.lastUpdate;
const timePassed = (thisUpdate - lastUpdate) / 1000; //Seconds
//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
//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;}
//Data map: [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken]
// Grow
for (var ip in runningScriptObj.dataMap) {
for (const ip in runningScriptObj.dataMap) {
if (runningScriptObj.dataMap.hasOwnProperty(ip)) {
if (runningScriptObj.dataMap[ip][2] == 0 || runningScriptObj.dataMap[ip][2] == null) {continue;}
var serv = AllServers[ip];
const serv = AllServers[ip];
if (serv == null) {continue;}
var timesGrown = Math.round(0.5 * runningScriptObj.dataMap[ip][2] / runningScriptObj.onlineRunningTime * timePassed);
runningScriptObj.log("Called grow() on " + serv.hostname + " " + timesGrown + " times while offline");
var growth = processSingleServerGrowth(serv, timesGrown, Player);
runningScriptObj.log(serv.hostname + " grown by " + numeralWrapper.format(growth * 100 - 100, '0.000000%') + " from grow() calls made 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;}
const timesGrown = Math.round(0.5 * runningScriptObj.dataMap[ip][2] / runningScriptObj.onlineRunningTime * timePassed);
runningScriptObj.log(`Called on ${serv.hostname} ${timesGrown} times while offline`);
const growth = processSingleServerGrowth(serv, timesGrown, Player);
runningScriptObj.log(`'${serv.hostname}' grown by ${numeralWrapper.format(growth * 100 - 100, '0.000000%')} while offline`);
}
}
// Offline EXP gain
// A script's offline production will always be at most half of its online production.
var expGain = 0.5 * (runningScriptObj.onlineExpGained / runningScriptObj.onlineRunningTime) * timePassed;
expGain *= confidence;
const expGain = confidence * (runningScriptObj.onlineExpGained / runningScriptObj.onlineRunningTime) * timePassed;
Player.gainHackingExp(expGain);
// Update script stats
runningScriptObj.offlineMoneyMade += totalOfflineProduction;
runningScriptObj.offlineRunningTime += timePassed;
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
for (var ip in runningScriptObj.dataMap) {
for (const ip in runningScriptObj.dataMap) {
if (runningScriptObj.dataMap.hasOwnProperty(ip)) {
if (runningScriptObj.dataMap[ip][3] == 0 || runningScriptObj.dataMap[ip][3] == null) {continue;}
var serv = AllServers[ip];
const serv = AllServers[ip];
if (serv == null) {continue;}
var timesWeakened = Math.round(0.5 * runningScriptObj.dataMap[ip][3] / runningScriptObj.onlineRunningTime * timePassed);
runningScriptObj.log("Called weaken() on " + serv.hostname + " " + timesWeakened + " times while offline");
const timesWeakened = Math.round(0.5 * runningScriptObj.dataMap[ip][3] / runningScriptObj.onlineRunningTime * timePassed);
runningScriptObj.log(`Called weaken() on ${serv.hostname} ${timesWeakened} times while offline`);
serv.weaken(CONSTANTS.ServerWeakenAmount * timesWeakened);
}
}
return totalOfflineProduction;
}
//Returns a RunningScript object matching the filename and arguments on the

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

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

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

@ -74,6 +74,11 @@ interface IDefaultSettings {
* Whether the user should be asked to confirm travelling between cities.
*/
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
*/
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 = {
@ -122,6 +157,7 @@ const defaultSettings: IDefaultSettings = {
SuppressHospitalizationPopup: false,
SuppressMessages: false,
SuppressTravelConfirmation: false,
SuppressBladeburnerPopup: false,
};
/**
@ -147,6 +183,13 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
SuppressHospitalizationPopup: defaultSettings.SuppressHospitalizationPopup,
SuppressMessages: defaultSettings.SuppressMessages,
SuppressTravelConfirmation: defaultSettings.SuppressTravelConfirmation,
SuppressBladeburnerPopup: defaultSettings.SuppressBladeburnerPopup,
EditorHighlightActiveLine: true,
EditorShowInvisibles: false,
EditorUseSoftTab: true,
EditorAutoCloseBrackets: true,
EditorEnableLinting: true,
EditorContinueComments: true,
init() {
Object.assign(Settings, defaultSettings);
},

@ -10,6 +10,7 @@ import {
import { determineAllPossibilitiesForTabCompletion } from "./Terminal/determineAllPossibilitiesForTabCompletion";
import { TerminalHelpText, HelpTexts } from "./Terminal/HelpText";
import { tabCompletion } from "./Terminal/tabCompletion";
import { createFconf } from "./Fconf/Fconf";
import {
parseAliasDeclaration,
@ -625,7 +626,11 @@ let Terminal = {
Terminal.commandHistoryIndex = Terminal.commandHistory.length;
// 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++) {
if(commands[i].match(/^\s*$/)) { continue; } // Don't run commands that only have whitespace
Terminal.executeCommand(commands[i].trim());
@ -727,9 +732,6 @@ let Terminal = {
return;
}
// Process any aliases
command = substituteAliases(command);
// Allow usage of ./
if (command.startsWith("./")) {
command = "run " + command.slice(2);
@ -873,7 +875,7 @@ let Terminal = {
if (commandArray.length === 3) {
if (commandArray[1] === "-g") {
if (parseAliasDeclaration(commandArray[2], true)) {
post(`Set global alias ${commandArray[1]}`);
post(`Set global alias ${commandArray[2]}`);
return;
}
}
@ -1311,9 +1313,17 @@ let Terminal = {
}
// 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) {
postError(status.msg);
}
@ -1771,19 +1781,22 @@ let Terminal = {
i--;
postContent(row, config);
}
if(segments.length > 0) {
postElement(<br />);
}
}
const config = { color: "#0000FF" };
postSegments(folders, config);
postSegments(allMessages);
postSegments(allTextFiles);
postSegments(allPrograms);
postSegments(allContracts);
postSegments(allScripts);
const groups = [
{segments: folders, config: config},
{segments: allMessages},
{segments: allTextFiles},
{segments: allPrograms},
{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) {
@ -1868,30 +1881,27 @@ let Terminal = {
// Displays available network connections using TCP
const currServ = Player.getCurrentServer();
post("Hostname IP Root Access");
for (let i = 0; i < currServ.serversOnNetwork.length; i++) {
// Add hostname
let entry = getServerOnNetwork(currServ, i);
if (entry == null) { continue; }
entry = entry.hostname;
// 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';
const servers = currServ.serversOnNetwork.map((_, i) => {
const server = getServerOnNetwork(currServ, i);
return {
hostname: server.hostname,
ip: server.ip,
hasRoot: server.hasAdminRights ? "Y" : "N"
}
numSpaces = 21 - getServerOnNetwork(currServ, i).ip.length;
spaces = Array(numSpaces+1).join(" ");
entry += spaces;
entry += hasRoot;
});
servers.unshift({
hostname: "Hostname",
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);
}
},

@ -32,6 +32,11 @@ import {
processPassiveFactionRepGain,
inviteToFaction,
} from "./Faction/FactionHelpers";
import {
getHackingWorkRepGain,
getFactionSecurityWorkRepGain,
getFactionFieldWorkRepGain,
} from "./PersonObjects/formulas/reputation";
import { FconfSettings } from "./Fconf/FconfSettings";
import {
hasHacknetServers,
@ -91,6 +96,7 @@ import { Page, routing } from "./ui/navigationTracking";
import { setSettingsLabels } from "./ui/setSettingsLabels";
import { Money } from "./ui/React/Money";
import { Hashes } from "./ui/React/Hashes";
import { Reputation } from "./ui/React/Reputation";
import { ActiveScriptsRoot } from "./ui/ActiveScripts/Root";
import { initializeMainMenuHeaders } from "./ui/MainMenu/Headers";
@ -230,6 +236,8 @@ const Engine = {
characterInfo: null,
},
indexedDb: undefined,
// Time variables (milliseconds unix epoch time)
_lastUpdate: new Date().getTime(),
_idleSpeed: 200, // Speed (in ms) at which the main loop is updated
@ -809,7 +817,7 @@ const Engine = {
Engine.Counters.autoSaveCounter = Infinity;
} else {
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
Engine._lastUpdate = new Date().getTime();
var lastUpdate = Player.lastUpdate;
var numCyclesOffline = Math.floor((Engine._lastUpdate - lastUpdate) / Engine._idleSpeed);
const lastUpdate = Player.lastUpdate;
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
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.workType == CONSTANTS.WorkTypeFaction) {
Player.workForFaction(numCyclesOffline);
@ -1098,6 +1110,31 @@ const Engine = {
} else {
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
@ -1157,7 +1194,10 @@ const Engine = {
removeLoadingScreen();
const timeOfflineString = convertTimeMsToTimeElapsedString(time);
dialogBoxCreate(<>
Offline for {timeOfflineString}. While you were offline, your scripts generated {Money(offlineProductionFromScripts)} and your Hacknet Nodes generated {hacknetProdInfo}.
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
var visibleMenuTabs = [terminal, createScript, activeScripts, stats,
@ -1430,13 +1470,13 @@ const Engine = {
// Save, Delete, Import/Export buttons
Engine.Clickables.saveMainMenuButton = document.getElementById("save-game-link");
Engine.Clickables.saveMainMenuButton.addEventListener("click", function() {
saveObject.saveGame(indexedDb);
saveObject.saveGame(Engine.indexedDb);
return false;
});
Engine.Clickables.deleteMainMenuButton = document.getElementById("delete-game-link");
Engine.Clickables.deleteMainMenuButton.addEventListener("click", function() {
saveObject.deleteGame(indexedDb);
saveObject.deleteGame(Engine.indexedDb);
return false;
});
@ -1447,7 +1487,7 @@ const Engine = {
// Character Overview buttons
document.getElementById("character-overview-save-button").addEventListener("click", function() {
saveObject.saveGame(indexedDb);
saveObject.saveGame(Engine.indexedDb);
return false;
});
@ -1559,7 +1599,7 @@ const Engine = {
},
};
var indexedDb, indexedDbRequest;
var indexedDbRequest;
window.onload = function() {
if (!window.indexedDB) {
return Engine.load(null); // Will try to load from localstorage
@ -1579,8 +1619,8 @@ window.onload = function() {
};
indexedDbRequest.onsuccess = function(e) {
indexedDb = e.target.result;
var transaction = indexedDb.transaction(["savestring"]);
Engine.indexedDb = e.target.result;
var transaction = Engine.indexedDb.transaction(["savestring"]);
var objectStore = transaction.objectStore("savestring");
var request = objectStore.get("save");
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">
</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 -->
<fieldset>
<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.stock) { parts.push([`Stock Market:`, Money(src.stock)]) }
if (src.casino) { parts.push([`Casino:`, Money(src.casino)]) }
if (src.sleeves) { parts.push([`Sleeves:`, Money(src.sleeves)]) }
return StatsTable(parts, "");
}

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

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

@ -1,4 +1,5 @@
/* GameOptions.js */
import { Player } from "../src/Player";
//Close box when clicking outside
$(document).click(function(event) {
@ -36,6 +37,11 @@ function gameOptionsBoxClose() {
function gameOptionsBoxOpen() {
var box = document.getElementById("game-options-container");
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() {
gameOptionsOpened = true;
}, 500);

@ -5,6 +5,7 @@ var HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = (env, argv) => {
const isDevServer = (env || {}).devServer === true;
const runInContainer = (env || {}).runInContainer === true;
const isDevelopment = argv.mode === 'development';
const outputDirectory = isDevServer ? "dist-dev" : "dist";
const entries = {};
@ -22,6 +23,22 @@ module.exports = (env, argv) => {
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 {
plugins: [
new webpack.DefinePlugin({
@ -131,11 +148,7 @@ module.exports = (env, argv) => {
},
},
},
devServer: {
port: 8000,
publicPath: `/`,
stats: statsConfig,
},
devServer: devServerSettings,
resolve: {
extensions: [
".tsx",