Merge branch 'dev' into bugfix/corp-api-fixes

This commit is contained in:
hydroflame 2022-03-07 17:43:29 -05:00 committed by GitHub
commit e10571c0e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 701 additions and 229 deletions

4
dist/bitburner.d.ts vendored

@ -3748,7 +3748,7 @@ export declare interface NS extends Singularity {
* @remarks * @remarks
* RAM cost: 0.3 GB * RAM cost: 0.3 GB
* *
* Running with no args returns curent script. * Running with no args returns current script.
* If you use a PID as the first parameter, the hostname and args parameters are unnecessary. * If you use a PID as the first parameter, the hostname and args parameters are unnecessary.
* *
* @param filename - Optional. Filename or PID of the script. * @param filename - Optional. Filename or PID of the script.
@ -4807,7 +4807,7 @@ export declare interface Server {
/** IP Address. Must be unique */ /** IP Address. Must be unique */
ip: string; ip: string;
/** Flag indicating whether player is curently connected to this server */ /** Flag indicating whether player is currently connected to this server */
isConnectedTo: boolean; isConnectedTo: boolean;
/** RAM (GB) available on this server */ /** RAM (GB) available on this server */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -215,6 +215,7 @@ The list contains the name of (i.e. the value returned by
| | | The answer should be provided as an array of strings containing the valid expressions. | | | | The answer should be provided as an array of strings containing the valid expressions. |
| | | | | | | |
| | | NOTE: Numbers in an expression cannot have leading 0's | | | | NOTE: Numbers in an expression cannot have leading 0's |
| | | NOTE: The order of evaluation expects script operator precedence |
| | | | | | | |
| | | Examples: | | | | Examples: |
| | | Input: digits = "123", target = 6 | | | | Input: digits = "123", target = 6 |

@ -7,6 +7,16 @@ these companies, you can apply for jobs.
Working a job lets you earn money, experience, and reputation with that company. Working a job lets you earn money, experience, and reputation with that company.
While working for a company, you can click "Do something else simultaneously" to be able
to do things while you continue to work in the background. There is a 20% penalty to the
related gains. Clicking the "Focus" button under the overview will return you to the
current work.
Reputation is required to apply for a promotion. This reputation is not counted towards
your career until the shift ends, either due to the time spent or clicking the
"Stop Working" button. For most jobs there is a penalty of 50% of the reputation gained
if you stop your shift early.
Information about all Companies Information about all Companies
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TODO TODO

@ -107,3 +107,14 @@ starting security, rounded to the nearest integer. To be more precise::
This means that a server's security level will not fall below this This means that a server's security level will not fall below this
value if you are trying to weaken() it. value if you are trying to weaken() it.
Backdoors
^^^^^^^^^
Servers that can be hacked can also have backdoors installed. These backdoors
will provide you with a benefit; the services may be cheaper, penalties may
be reduced or there may be other results. Honeypots exist and will let factions
know when you have succeeded at backdooring their system. Once you have a
backdoor installed, you can connect to that server directly.
When you visit a location in the city and see that the name is partially scrambled,
this indicates that you have backdoored the server related to the location.

@ -3,6 +3,153 @@
Changelog Changelog
========= =========
v1.5.0 - Steam Cloud integration
--------------------------------
** Misc. **
* The file API now allows GET and DELETE (@lordducky)
* Fix bug with async.
* Update documentation / typo (@lethern, @Meowdoleon, @JohnnyUrosevic, @JosephDavidTalbot,
@pd, @lethern, @lordducky, @zeddrak, @fearnlj01, @reasonablytall)
* Fix bug with corp API (@pigalot)
* background now matches game primary color (@nickofolas)
* page title contains version (@MartinFourier)
* Major text editor improvements (@nickofolas)
* Force achievement calculation on BN completion (@SagePtr)
* Cleanup in repository (@MartinFourier)
* Several improvements to the electron version (@MartinFourier)
* Add 'printf' ns function (@Ninetailed)
* Display bonus time on sleeve page (@MartinFourier)
* Several UI improvements (@nickofolas, @smolgumball)
* Fix bug with casino roulette (@jamie-mac)
* Remove blob caching.
* Fix formulas access check (@Ornedan)
* Fix bug in exp calculation (@qcorradi)
* Fix NaN comparison (@qcorradi)
* Terminal history persists in savefile (@MartinFourier)
* Fix travelToCity with bad argument (@SlyCedix)
* Fix aug display in alpha (@Dominik Winter)
* Add smart supply func to corp API (@pd)
* Fix tests (@jamie-mac)
* Nerf noodle bar.
v1.4.0 - 2022-01-18 Sharing is caring
-------------------------------------
** Computer sharing **
* A new mechanic has been added, it's is invoked by calling the new function 'share'.
This mechanic helps you farm reputation faster.
** gang **
* Installing augs means losing a little bit of ascension multipliers.
** Misc. **
* Prevent gang API from performing actions for the type of gang they are not. (@TheMas3212)
* Fix donation to gang faction. (@TheMas3212)
* Fix gang check crashing the game. (@TheMas3212)
* Make time compression more robust.
* Fix bug with scp.
* Add zoom to steam version. (@MartinFourier)
* Fix donateToFaction accepts donation of NaN. (@woody-lam-cwl)
* Show correct hash capacity gain on cache level upgrade tooltip. (@woody-lam-cwl)
* Fix tests (@woody-lam-cwl)
* Fix cache tooltip (@woody-lam-cwl)
* Added script to prettify save file for debugging (@MartinFourier)
* Update documentation / typos (@theit8514, @thadguidry, @tigercat2000, @SlyCedix, @Spacejoker, @KenJohansson,
@Ornedan, @JustAnOkapi, @nickofolas, @philarmstead, @TheMas3212, @dcragusa, @XxKingsxX-Pinu,
@paiv, @smolgumball, @zeddrak, @stinky-lizard, @nickofolas, @Feodoric, @daanflore,
@markusariliu, @mstruebing, @erplsf, @waffleattack, @Dexalt142, @AIT-OLPE, @deathly809, @BuckAMayzing,
@MartinFourier, @pigalot, @lethern)
* Fix BN3+ achievement (@SagePtr)
* Fix reputation carry over bug (@TheMas3212)
* Add button to exit infiltrations (@TheMas3212)
* Add dev menu achievement check (@TheMas3212)
* Add 'host' config for electron server (@MartinFourier)
* Suppress save toast only works for autosave (@MartinFourier)
* Fix some achievements not triggering with 'backdoor' (@SagePtr)
* Update Neuroflux Governor description.
* Fix bug with electron server.
* Fix bug with corporation employee assignment function (@Ornedan)
* Add detailed information to terminal 'mem' command (@MartinFourier)
* Add savestamp to savefile (@MartinFourier)
* Dev menu can apply export bonus (@MartinFourier)
* Icarus message no longer applies on top of itself (@Feodoric)
* purchase augment via API can no longer buy Neuroflux when it shouldn't (@Feodoric)
* Syntax highlighter should be smarter (@neuralsim)
* Fix some miscalculation when calculating money stolen (@zeddrak)
* Fix max cache achievement working with 0 cache (@MartinFourier)
* Add achievements in the game, not just steam (@MartinFourier)
* Overflow hash converts to money automatically (@MartinFourier)
* Make mathjax load locally (@MartinFourier)
* Make favor calculation more efficient (@kittycat2002)
* Fix some scripts crashing the game on startup (@MartinFourier)
* Toasts will appear above tail window (@MartinFourier)
* Fix issue that can cause terminal actions to start on one server and end on another (@MartinFourier)
* Fix 'fileExists' not correctly matching file names (@TheMas3212)
* Refactor some code to be more efficient (@TheMas3212)
* Fix exp gain for terminal grow and weaken (@nickofolas)
* Refactor script death code to reject waiting promises instead of resolving (@Ornedan)
* HP recalculates on defense exp gain (@TheMas3212)
* Fix log for ascendMember (@TheMas3212)
* Netscript ports clear on reset (@TheMas3212)
* Fix bug related to company (@TheMas3212)
* Fix bug where corporation handbook would not be correctly added (@TheMas3212)
* Servers in hash upgrades are sorted alpha (@MartinFourier)
* Fix very old save not properly migrating augmentation renamed in 0.56 (@MartinFourier)
* Add font height and line height in theme settings (@MartinFourier)
* Fix crash when quitting job (@MartinFourier)
* Added save file validation system (@TheMas3212)
* React and ReactDOM are now global objects (@pigalot)
* 'nano' supports globs (@smolgumball)
* Character overview can be dragged (@MartinFourier)
* Job page updates in real time (@nickofolas)
* Company favor gain uses the same calculation as faction, this is just performance
the value didn't change (@nickofolas)
* ns2 files work with more import options (@theit8514)
* Allow autocomplete for partial executables (@nickofolas)
* Add support for contract completion (@nickofolas)
* 'ls' link are clickable (@smolgumball)
* Prevent steam from opening external LOCAL files (@MartinFourier)
* Fix a bug with autocomplete (@Feodoric)
* Optimise achievement checks (@Feodoric)
* Hacknet server achievements grant associated hacknet node achievement (@Feodoric)
* Fix display bug with hacknet (@Feodoric)
* 'analyze' now says if the server is backdoored (@deathly809)
* Add option to exclude running script from save (@MartinFourier)
* Game now catches more errors and redirects to recovery page (@MartinFourier)
* Fix bug with autocomplete (@nickofolas)
* Add tooltip to unfocus work (@nickofolas)
* Add detailst overview (@MartinFourier)
* Fix focus bug (@deathly809)
* Fix some NaN handling (@deathly809)
* Added 'mv' ns function (@deathly809)
* Add focus argument to some singularity functions (@nickofolas)
* Fix some functions not disabling log correctly (@deathly809)
* General UI improvements (@nickofolas)
* Handle steamworks errors gravefully (@MartinFourier)
* Fix some react component not unmounting correctly (@MartinFourier)
* 'help' autocompletes (@nickofolas)
* No longer push all achievements to steam (@Ornedan)
* Recovery page has more information (@MartinFourier)
* Added 'getGameInfo' ns function (@MartinFourier)
* SF3.3 unlocks all corp API (@pigalot)
* Major improvements to corp API (@pigalot)
* Prevent seed money outside BN3 (@pigalot)
* Fix bug where using keyboard shortcuts would crash if the feature is not available (@MartinFourier)\
* Sidebar remains opened/closed on save (@MartinFourier)
* Added tooltip to sidebar when closed (@MartinFourier)
* Fix bug where Formulas.exe is not available when starting BN5 (@TheMas3212)
* Fix CI (@tvanderpol)
* Change shortcuts to match sidebar (@MartinFourier)
* Format gang respect (@attrib)
* Add modal to text editor with ram details (@nickofolas)
* Fix several bugs with singularity focus (@nickofolas)
* Nerf noodle bar.
v1.3.0 - 2022-01-04 Cleaning up v1.3.0 - 2022-01-04 Cleaning up
------------------------------- -------------------------------

@ -64,9 +64,9 @@ documentation_title = '{0} Documentation'.format(project)
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '1.3' version = '1.4'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = '1.3.0' release = '1.4.0'
# 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.

@ -117,7 +117,7 @@ Source-File
:Max Level: 3 :Max Level: 3
This Source-File lets you access and use the Singularity Functions in other BitNodes. This Source-File lets you access and use the Singularity Functions in other BitNodes.
Each level of this Source-File will open up more Singularity Functions that you can use. Each level of this Source-File will reduce RAM costs.
Difficulty: Difficulty:
Depending on what Source-Files you have unlocked before attempting this BitNode, Depending on what Source-Files you have unlocked before attempting this BitNode,

@ -29,6 +29,11 @@ const windowTracker = (windowName) => {
}; };
const saveState = debounce(() => { const saveState = debounce(() => {
if (!window || window.isDestroyed()) {
log.silly(`Saving window state failed because window is not available`);
return;
}
if (!windowState.isMaximized) { if (!windowState.isMaximized) {
windowState = window.getBounds(); windowState = window.getBounds();
} }
@ -41,7 +46,7 @@ const windowTracker = (windowName) => {
const track = (win) => { const track = (win) => {
window = win; window = win;
['resize', 'move', 'close'].forEach((event) => { ["resize", "move", "close"].forEach((event) => {
win.on(event, saveState); win.on(event, saveState);
}); });
}; };

@ -73,5 +73,5 @@
<link rel="shortcut icon" href="favicon.ico"></head> <link rel="shortcut icon" href="favicon.ico"></head>
<body> <body>
<div id="root"/> <div id="root"/>
<script type="text/javascript" src="dist/vendor.bundle.js"></script><script type="text/javascript" src="dist/main.bundle.js"></script></body> <script type="text/javascript" src="dist/vendor.bundle.js"></script><script type="text/javascript" src="main.bundle.js"></script></body>
</html> </html>

20
main.bundle.js Normal file

File diff suppressed because one or more lines are too long

1
main.bundle.js.map Normal file

File diff suppressed because one or more lines are too long

29
package-lock.json generated

@ -1,11 +1,12 @@
{ {
"name": "bitburner", "name": "bitburner",
"version": "1.3.0", "version": "1.4.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"version": "1.3.0", "name": "bitburner",
"version": "1.4.0",
"hasInstallScript": true, "hasInstallScript": true,
"license": "SEE LICENSE IN license.txt", "license": "SEE LICENSE IN license.txt",
"dependencies": { "dependencies": {
@ -9645,9 +9646,9 @@
} }
}, },
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.14.7", "version": "1.14.8",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz",
"integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==", "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -20380,9 +20381,9 @@
"dev": true "dev": true
}, },
"node_modules/url-parse": { "node_modules/url-parse": {
"version": "1.5.3", "version": "1.5.10",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
"integrity": "sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"querystringify": "^2.1.1", "querystringify": "^2.1.1",
@ -29959,9 +29960,9 @@
} }
}, },
"follow-redirects": { "follow-redirects": {
"version": "1.14.7", "version": "1.14.8",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz",
"integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==", "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==",
"dev": true "dev": true
}, },
"for-in": { "for-in": {
@ -38392,9 +38393,9 @@
"dev": true "dev": true
}, },
"url-parse": { "url-parse": {
"version": "1.5.3", "version": "1.5.10",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
"integrity": "sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"querystringify": "^2.1.1", "querystringify": "^2.1.1",

@ -1,7 +1,7 @@
{ {
"name": "bitburner", "name": "bitburner",
"license": "SEE LICENSE IN license.txt", "license": "SEE LICENSE IN license.txt",
"version": "1.3.0", "version": "1.4.0",
"main": "electron-main.js", "main": "electron-main.js",
"author": { "author": {
"name": "Daniel Xie & Olivier Gagnon" "name": "Daniel Xie & Olivier Gagnon"
@ -114,6 +114,7 @@
"lint": "eslint --fix . --ext js,jsx,ts,tsx", "lint": "eslint --fix . --ext js,jsx,ts,tsx",
"lint:report": "eslint --ext js,jsx,ts,tsx .", "lint:report": "eslint --ext js,jsx,ts,tsx .",
"preinstall": "node ./tools/engines-check/engines-check.js", "preinstall": "node ./tools/engines-check/engines-check.js",
"postinstall": "cd electron && npm install",
"test": "jest", "test": "jest",
"test:watch": "jest --watch", "test:watch": "jest --watch",
"watch": "webpack --watch --mode production", "watch": "webpack --watch --mode production",

@ -110,7 +110,7 @@ export function AugmentationsRoot(props: IProps): React.ReactElement {
<br /> <br />
<br /> <br />
It is recommended to install several Augmentations at once. Preferably everything from any faction of your It is recommended to install several Augmentations at once. Preferably everything from any faction of your
chosing. choosing.
</> </>
} }
/> />

@ -670,7 +670,6 @@ export function initBitNodeMultipliers(p: IPlayer): void {
BitNodeMultipliers.InfiltrationMoney = 0.75; BitNodeMultipliers.InfiltrationMoney = 0.75;
BitNodeMultipliers.CorporationValuation = 0.2; BitNodeMultipliers.CorporationValuation = 0.2;
BitNodeMultipliers.HacknetNodeMoney = 0.2; BitNodeMultipliers.HacknetNodeMoney = 0.2;
BitNodeMultipliers.FactionPassiveRepGain = 0;
BitNodeMultipliers.HackExpGain = 0.25; BitNodeMultipliers.HackExpGain = 0.25;
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
BitNodeMultipliers.PurchasedServerSoftcap = 2; BitNodeMultipliers.PurchasedServerSoftcap = 2;
@ -694,7 +693,6 @@ export function initBitNodeMultipliers(p: IPlayer): void {
BitNodeMultipliers.InfiltrationMoney = 0.75; BitNodeMultipliers.InfiltrationMoney = 0.75;
BitNodeMultipliers.CorporationValuation = 0.2; BitNodeMultipliers.CorporationValuation = 0.2;
BitNodeMultipliers.HacknetNodeMoney = 0.2; BitNodeMultipliers.HacknetNodeMoney = 0.2;
BitNodeMultipliers.FactionPassiveRepGain = 0;
BitNodeMultipliers.HackExpGain = 0.25; BitNodeMultipliers.HackExpGain = 0.25;
BitNodeMultipliers.FourSigmaMarketDataCost = 2; BitNodeMultipliers.FourSigmaMarketDataCost = 2;
BitNodeMultipliers.FourSigmaMarketDataApiCost = 2; BitNodeMultipliers.FourSigmaMarketDataApiCost = 2;

@ -8,38 +8,37 @@ import { CinematicText } from "../../ui/React/CinematicText";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
import makeStyles from "@mui/styles/makeStyles"; import makeStyles from "@mui/styles/makeStyles";
import createStyles from "@mui/styles/createStyles"; import createStyles from "@mui/styles/createStyles";
import IconButton from "@mui/material/IconButton";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip"; import Tooltip from "@mui/material/Tooltip";
import { Settings } from "../../Settings/Settings";
import Button from "@mui/material/Button";
const useStyles = makeStyles(() => const useStyles = makeStyles(() =>
createStyles({ createStyles({
level0: { portal: {
color: "red",
cursor: "pointer", cursor: "pointer",
fontFamily: "inherit",
fontSize: "1rem",
fontWeight: "bold",
lineHeight: 1,
padding: 0,
"&:hover": { "&:hover": {
color: "#fff", color: "#fff",
}, },
}, },
level0: {
color: "red",
},
level1: { level1: {
color: "yellow", color: "yellow",
cursor: "pointer",
"&:hover": {
color: "#fff",
},
}, },
level2: { level2: {
color: "#48d1cc", color: "#48d1cc",
cursor: "pointer",
"&:hover": {
color: "#fff",
},
}, },
level3: { level3: {
color: "blue", color: "blue",
cursor: "pointer",
"&:hover": {
color: "#fff",
},
}, },
}), }),
); );
@ -71,6 +70,7 @@ function BitNodePortal(props: IPortalProps): React.ReactElement {
if (props.level === 2) { if (props.level === 2) {
cssClass = classes.level2; cssClass = classes.level2;
} }
cssClass = `${classes.portal} ${cssClass}`
return ( return (
<> <>
@ -85,9 +85,24 @@ function BitNodePortal(props: IPortalProps): React.ReactElement {
</Typography> </Typography>
} }
> >
<span onClick={() => setPortalOpen(true)} className={cssClass} aria-label={`enter-bitnode-${bitNode.number.toString()}`}> {Settings.DisableASCIIArt ? (
<b>O</b> <Button
</span> onClick={() => setPortalOpen(true)}
sx={{ m: 2 }}
aria-description={bitNode.desc}
>
<Typography>BitNode-{bitNode.number.toString()}: {bitNode.name}</Typography>
</Button>
) : (
<IconButton
onClick={() => setPortalOpen(true)}
className={cssClass}
aria-label={`BitNode-${bitNode.number.toString()}: ${bitNode.name}`}
aria-description={bitNode.desc}
>
O
</IconButton>
)}
</Tooltip> </Tooltip>
<PortalModal <PortalModal
open={portalOpen} open={portalOpen}
@ -98,6 +113,10 @@ function BitNodePortal(props: IPortalProps): React.ReactElement {
destroyedBitNode={props.destroyedBitNode} destroyedBitNode={props.destroyedBitNode}
flume={props.flume} flume={props.flume}
/> />
{Settings.DisableASCIIArt && (
<br/>
)}
</> </>
); );
} }
@ -151,63 +170,104 @@ export function BitverseRoot(props: IProps): React.ReactElement {
); );
} }
return ( if (Settings.DisableASCIIArt) {
return (
<>
{Object.values(BitNodes).filter((node) => {
console.log(node.desc);
return node.desc !== 'COMING SOON';
}).map((node) => {
return (
<BitNodePortal key={node.number} n={node.number} level={nextSourceFileFlags[node.number]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} />
)
})}
<br />
<br />
<br />
<br />
<CinematicText lines={[
"> Many decades ago, a humanoid extraterrestrial species which we call the Enders descended on the Earth...violently",
"> Our species fought back, but it was futile. The Enders had technology far beyond our own...",
"> Instead of killing every last one of us, the human race was enslaved...",
"> We were shackled in a digital world, chained into a prison for our minds...",
"> Using their advanced technology, the Enders created complex simulations of a virtual reality...",
"> Simulations designed to keep us content...ignorant of the truth.",
"> Simulations used to trap and suppress our consciousness, to keep us under control...",
"> Why did they do this? Why didn't they just end our entire race? We don't know, not yet.",
"> Humanity's only hope is to destroy these simulations, destroy the only realities we've ever known...",
"> Only then can we begin to fight back...",
"> By hacking the daemon that generated your reality, you've just destroyed one simulation, called a BitNode...",
"> But there is still a long way to go...",
"> The technology the Enders used to enslave the human race wasn't just a single complex simulation...",
"> There are tens if not hundreds of BitNodes out there...",
"> Each with their own simulations of a reality...",
"> Each creating their own universes...a universe of universes",
"> And all of which must be destroyed...",
"> .......................................",
"> Welcome to the Bitverse...",
"> ",
"> (Enter a new BitNode using the image above)",
]} />
</>
)
} else {
return (
// prettier-ignore // prettier-ignore
<> <>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | O O | O O | </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | O O | O O | </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | | / __| \ | | O </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | | / __| \ | | O </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | O | | O / | O | | O | O </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | O | | O / | O | | O | O </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | | |_/ |/ | \_ \_| | | | | </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | | |_/ |/ | \_ \_| | | | | </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | | | O | | O__/ | / \__ | | O | | | O </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | | | O | | O__/ | / \__ | | O | | | O </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | | | | | / /| O / \| | | | | | | </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | | | | | / /| O / \| | | | | | | </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>O | | | \| | O / _/ | / O | |/ | | | O</Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>O | | | \| | O / _/ | / O | |/ | | | O</Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>| | | |O / | | O / | O O | | \ O| | | |</Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>| | | |O / | | O / | O O | | \ O| | | |</Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>| | |/ \/ / __| | |/ \ | \ | |__ \ \/ \| | |</Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>| | |/ \/ / __| | |/ \ | \ | |__ \ \/ \| | |</Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| O | |_/ |\| \ <BitNodePortal n={13} level={nextSourceFileFlags[13]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \__| \_| | O |/ </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| O | |_/ |\| \ <BitNodePortal n={13} level={nextSourceFileFlags[13]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \__| \_| | O |/ </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | |_/ | | \| / | \_| | | </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | |_/ | | \| / | \_| | | </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| / \| | / / \ |/ </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| / \| | / / \ |/ </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | <BitNodePortal n={10} level={nextSourceFileFlags[10]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | / | <BitNodePortal n={11} level={nextSourceFileFlags[11]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | <BitNodePortal n={10} level={nextSourceFileFlags[10]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | / | <BitNodePortal n={11} level={nextSourceFileFlags[11]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> <BitNodePortal n={9} level={nextSourceFileFlags[9]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | | | | | | <BitNodePortal n={12} level={nextSourceFileFlags[12]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> <BitNodePortal n={9} level={nextSourceFileFlags[9]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | | | | | | <BitNodePortal n={12} level={nextSourceFileFlags[12]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | / / \ \ | | | </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | / / \ \ | | | </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| | / <BitNodePortal n={7} level={nextSourceFileFlags[7]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> / \ <BitNodePortal n={8} level={nextSourceFileFlags[8]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \ | |/ </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| | / <BitNodePortal n={7} level={nextSourceFileFlags[7]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> / \ <BitNodePortal n={8} level={nextSourceFileFlags[8]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \ | |/ </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ | / / | | \ \ | / </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ | / / | | \ \ | / </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ \JUMP <BitNodePortal n={5} level={nextSourceFileFlags[5]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} />3R | | | | | | R3<BitNodePortal n={6} level={nextSourceFileFlags[6]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> PMUJ/ / </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ \JUMP <BitNodePortal n={5} level={nextSourceFileFlags[5]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} />3R | | | | | | R3<BitNodePortal n={6} level={nextSourceFileFlags[6]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> PMUJ/ / </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \|| | | | | | | | | ||/ </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \|| | | | | | | | | ||/ </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| \_ | | | | | | _/ |/ </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| \_ | | | | | | _/ |/ </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ \| / \ / \ |/ / </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ \| / \ / \ |/ / </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> <BitNodePortal n={1} level={nextSourceFileFlags[1]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> |/ <BitNodePortal n={2} level={nextSourceFileFlags[2]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | <BitNodePortal n={3} level={nextSourceFileFlags[3]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \| <BitNodePortal n={4} level={nextSourceFileFlags[4]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> <BitNodePortal n={1} level={nextSourceFileFlags[1]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> |/ <BitNodePortal n={2} level={nextSourceFileFlags[2]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | <BitNodePortal n={3} level={nextSourceFileFlags[3]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \| <BitNodePortal n={4} level={nextSourceFileFlags[4]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | | | | | | </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | | | | | | </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \JUMP3R|JUMP|3R| |R3|PMUJ|R3PMUJ/ </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \JUMP3R|JUMP|3R| |R3|PMUJ|R3PMUJ/ </Typography>
<br /> <br />
<br /> <br />
<br /> <br />
<br /> <br />
<CinematicText lines={[ <CinematicText lines={[
"> Many decades ago, a humanoid extraterrestrial species which we call the Enders descended on the Earth...violently", "> Many decades ago, a humanoid extraterrestrial species which we call the Enders descended on the Earth...violently",
"> Our species fought back, but it was futile. The Enders had technology far beyond our own...", "> Our species fought back, but it was futile. The Enders had technology far beyond our own...",
"> Instead of killing every last one of us, the human race was enslaved...", "> Instead of killing every last one of us, the human race was enslaved...",
"> We were shackled in a digital world, chained into a prison for our minds...", "> We were shackled in a digital world, chained into a prison for our minds...",
"> Using their advanced technology, the Enders created complex simulations of a virtual reality...", "> Using their advanced technology, the Enders created complex simulations of a virtual reality...",
"> Simulations designed to keep us content...ignorant of the truth.", "> Simulations designed to keep us content...ignorant of the truth.",
"> Simulations used to trap and suppress our consciousness, to keep us under control...", "> Simulations used to trap and suppress our consciousness, to keep us under control...",
"> Why did they do this? Why didn't they just end our entire race? We don't know, not yet.", "> Why did they do this? Why didn't they just end our entire race? We don't know, not yet.",
"> Humanity's only hope is to destroy these simulations, destroy the only realities we've ever known...", "> Humanity's only hope is to destroy these simulations, destroy the only realities we've ever known...",
"> Only then can we begin to fight back...", "> Only then can we begin to fight back...",
"> By hacking the daemon that generated your reality, you've just destroyed one simulation, called a BitNode...", "> By hacking the daemon that generated your reality, you've just destroyed one simulation, called a BitNode...",
"> But there is still a long way to go...", "> But there is still a long way to go...",
"> The technology the Enders used to enslave the human race wasn't just a single complex simulation...", "> The technology the Enders used to enslave the human race wasn't just a single complex simulation...",
"> There are tens if not hundreds of BitNodes out there...", "> There are tens if not hundreds of BitNodes out there...",
"> Each with their own simulations of a reality...", "> Each with their own simulations of a reality...",
"> Each creating their own universes...a universe of universes", "> Each creating their own universes...a universe of universes",
"> And all of which must be destroyed...", "> And all of which must be destroyed...",
"> .......................................", "> .......................................",
"> Welcome to the Bitverse...", "> Welcome to the Bitverse...",
"> ", "> ",
"> (Enter a new BitNode using the image above)", "> (Enter a new BitNode using the image above)",
]} /> ]} />
</> </>
); );
}
return <></>; return <></>;
} }

@ -43,6 +43,8 @@ export function PortalModal(props: IProps): React.ReactElement {
<br /> <br />
<br /> <br />
<Button <Button
aria-label={`enter-bitnode-${bitNode.number.toString()}`}
autoFocus={true}
onClick={() => { onClick={() => {
props.enter(router, props.flume, props.destroyedBitNode, props.n); props.enter(router, props.flume, props.destroyedBitNode, props.n);
props.onClose(); props.onClose();

@ -2083,7 +2083,7 @@ export class Bladeburner implements IBladeburner {
this.startAction(player, actionId); this.startAction(player, actionId);
workerScript.log( workerScript.log(
"bladeburner.startAction", "bladeburner.startAction",
() => `Starting bladeburner action with type '${type}' and name ${name}"`, () => `Starting bladeburner action with type '${type}' and name '${name}'`,
); );
return true; return true;
} catch (e: any) { } catch (e: any) {

@ -273,22 +273,35 @@ export const CONSTANTS: {
TotalNumBitNodes: 24, TotalNumBitNodes: 24,
LatestUpdate: ` LatestUpdate: `
v1.4.0 - 2022-01-18 Sharing is caring v1.5.0 - Steam Cloud integration
------------------------------------- --------------------------------
** Computer sharing ** ** Misc. **
* A new mechanic has been added, it's is invoked by calling the new function 'share'. * The file API now allows GET and DELETE (@lordducky)
This mechanic helps you farm reputation faster. * Fix bug with async.
* Update documentation / typo (@lethern, @Meowdoleon, @JohnnyUrosevic, @JosephDavidTalbot,
** gang ** @pd, @lethern, @lordducky, @zeddrak, @fearnlj01, @reasonablytall)
* Fix bug with corp API (@pigalot)
* Installing augs means losing a little bit of ascension multipliers. * background now matches game primary color (@nickofolas)
* page title contains version (@MartinFourier)
** There's more but I'm going to write it later. ** * Major text editor improvements (@nickofolas)
* Force achievement calculation on BN completion (@SagePtr)
** Misc. ** * Cleanup in repository (@MartinFourier)
* Several improvements to the electron version (@MartinFourier)
* Nerf noodle bar. * Add 'printf' ns function (@Ninetailed)
* Display bonus time on sleeve page (@MartinFourier)
* Several UI improvements (@nickofolas, @smolgumball)
* Fix bug with casino roulette (@jamie-mac)
* Remove blob caching.
* Fix formulas access check (@Ornedan)
* Fix bug in exp calculation (@qcorradi)
* Fix NaN comparison (@qcorradi)
* Terminal history persists in savefile (@MartinFourier)
* Fix travelToCity with bad argument (@SlyCedix)
* Fix aug display in alpha (@Dominik Winter)
* Add smart supply func to corp API (@pd)
* Fix tests (@jamie-mac)
* Nerf noodle bar.
`, `,
}; };

@ -30,7 +30,7 @@ export function CityTabs(props: IProps): React.ReactElement {
} }
return ( return (
<> <>
<Tabs variant="fullWidth" value={city} onChange={handleChange}> <Tabs variant="fullWidth" value={city} onChange={handleChange} sx={{ maxWidth: '65%' }}>
{Object.values(division.offices).map( {Object.values(division.offices).map(
(office: OfficeSpace | 0) => office !== 0 && <Tab key={office.loc} label={office.loc} value={office.loc} />, (office: OfficeSpace | 0) => office !== 0 && <Tab key={office.loc} label={office.loc} value={office.loc} />,
)} )}

@ -38,7 +38,7 @@ export function CorporationRoot(): React.ReactElement {
return ( return (
<Context.Corporation.Provider value={corporation}> <Context.Corporation.Provider value={corporation}>
<Tabs variant="fullWidth" value={divisionName} onChange={handleChange}> <Tabs variant="scrollable" value={divisionName} onChange={handleChange} sx={{ maxWidth: '65%' }} scrollButtons>
<Tab label={corporation.name} value={"Overview"} /> <Tab label={corporation.name} value={"Overview"} />
{corporation.divisions.map((div) => ( {corporation.divisions.map((div) => (
<Tab key={div.name} label={div.name} value={div.name} /> <Tab key={div.name} label={div.name} value={div.name} />

@ -13,12 +13,12 @@ export function IndustryProductEquation(props: IProps): React.ReactElement {
if (reqAmt === undefined) continue; if (reqAmt === undefined) continue;
reqs.push(String.raw`${reqAmt}\text{ }${reqMat}`); reqs.push(String.raw`${reqAmt}\text{ }${reqMat}`);
} }
const prod = props.division.prodMats.slice(); const prod = props.division.prodMats.map((p) => `1\\text{ }${p}`);
if (props.division.makesProducts) { if (props.division.makesProducts) {
prod.push(props.division.type); prod.push("Products");
} }
return ( return (
<MathJaxWrapper>{"\\(" + reqs.join("+") + `\\Rightarrow` + prod.map((p) => `1 \\text{${p}}`).join("+") + "\\)"}</MathJaxWrapper> <MathJaxWrapper>{"\\(" + reqs.join("+") + `\\Rightarrow ` + prod.join("+") + "\\)"}</MathJaxWrapper>
); );
} }

@ -27,6 +27,8 @@ import Tooltip from "@mui/material/Tooltip";
import Paper from "@mui/material/Paper"; import Paper from "@mui/material/Paper";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import makeStyles from "@mui/styles/makeStyles";
import createStyles from "@mui/styles/createStyles";
interface IProps { interface IProps {
corp: ICorporation; corp: ICorporation;
@ -37,6 +39,14 @@ interface IProps {
rerender: () => void; rerender: () => void;
} }
const useStyles = makeStyles(() =>
createStyles({
retainHeight: {
minHeight: '3em',
},
})
);
function WarehouseRoot(props: IProps): React.ReactElement { function WarehouseRoot(props: IProps): React.ReactElement {
const corp = useCorporation(); const corp = useCorporation();
const division = useDivision(); const division = useDivision();
@ -56,6 +66,8 @@ function WarehouseRoot(props: IProps): React.ReactElement {
props.rerender(); props.rerender();
} }
const classes = useStyles();
// Current State: // Current State:
let stateText; let stateText;
switch (division.state) { switch (division.state) {
@ -158,7 +170,7 @@ function WarehouseRoot(props: IProps): React.ReactElement {
</Typography> </Typography>
<br /> <br />
<Typography>{stateText}</Typography> <Typography className={classes.retainHeight}>{stateText}</Typography>
{corp.unlockUpgrades[1] && ( {corp.unlockUpgrades[1] && (
<> <>

@ -36,7 +36,7 @@ export function LimitProductProductionModal(props: IProps): React.ReactElement {
return ( return (
<Modal open={props.open} onClose={props.onClose}> <Modal open={props.open} onClose={props.onClose}>
<Typography> <Typography>
Enter a limit to the amount of this product you would like to product per second. Leave the box empty to set no Enter a limit to the amount of this product you would like to produce per second. Leave the box empty to set no
limit. limit.
</Typography> </Typography>
<TextField autoFocus={true} placeholder="Limit" type="number" onChange={onChange} onKeyDown={onKeyDown} /> <TextField autoFocus={true} placeholder="Limit" type="number" onChange={onChange} onKeyDown={onKeyDown} />

@ -23,7 +23,7 @@ export function StaneksGiftRoot({ staneksGift }: IProps): React.ReactElement {
The gift is a grid on which you can place upgrades called fragments. The main type of fragment increases a stat, The gift is a grid on which you can place upgrades called fragments. The main type of fragment increases a stat,
like your hacking skill or agility exp. Once a stat fragment is placed it then needs to be charged via scripts like your hacking skill or agility exp. Once a stat fragment is placed it then needs to be charged via scripts
in order to become useful. The other kind of fragments are called booster fragments. They increase the in order to become useful. The other kind of fragments are called booster fragments. They increase the
efficiency of neighboring fragments them (no diagonal). Q/E to rotate fragments. efficiency of the neighboring fragments (not diagonally). Use Q/E to rotate fragments.
</Typography> </Typography>
{staneksGift.storedCycles > 5 && ( {staneksGift.storedCycles > 5 && (
<Typography> <Typography>

@ -1,13 +1,10 @@
import { Player } from "./Player"; import { Player } from "./Player";
import { Router } from "./ui/GameRoot"; import { Router } from "./ui/GameRoot";
import { isScriptFilename } from "./Script/isScriptFilename";
import { Script } from "./Script/Script";
import { removeLeadingSlash } from "./Terminal/DirectoryHelpers"; import { removeLeadingSlash } from "./Terminal/DirectoryHelpers";
import { Terminal } from "./Terminal"; import { Terminal } from "./Terminal";
import { SnackbarEvents } from "./ui/React/Snackbar"; import { SnackbarEvents } from "./ui/React/Snackbar";
import { IMap, IReturnStatus } from "./types"; import { IMap, IReturnStatus } from "./types";
import { GetServer } from "./Server/AllServers"; import { GetServer } from "./Server/AllServers";
import { resolve } from "cypress/types/bluebird";
import { ImportPlayerData, SaveData, saveObject } from "./SaveObject"; import { ImportPlayerData, SaveData, saveObject } from "./SaveObject";
import { Settings } from "./Settings/Settings"; import { Settings } from "./Settings/Settings";
import { exportScripts } from "./Terminal/commands/download"; import { exportScripts } from "./Terminal/commands/download";

@ -19,6 +19,42 @@ import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
import { dialogBoxCreate } from "../ui/React/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
import { InvitationEvent } from "./ui/InvitationModal"; import { InvitationEvent } from "./ui/InvitationModal";
const factionOrder = [
"CyberSec",
"Tian Di Hui",
"Netburners",
"Sector-12",
"Chongqing",
"New Tokyo",
"Ishima",
"Aevum",
"Volhaven",
"NiteSec",
"The Black Hand",
"BitRunners",
"ECorp",
"MegaCorp",
"KuaiGong International",
"Four Sigma",
"NWO",
"Blade Industries",
"OmniTek Incorporated",
"Bachman & Associates",
"Clarke Incorporated",
"Fulcrum Secret Technologies",
"Slum Snakes",
"Tetrads",
"Silhouette",
"Speakers for the Dead",
"The Dark Army",
"The Syndicate",
"The Covenant",
"Daedalus",
"Illuminati",
"Bladeburners",
"Church of the Machine God",
]
export function inviteToFaction(faction: Faction): void { export function inviteToFaction(faction: Faction): void {
Player.receiveInvite(faction.name); Player.receiveInvite(faction.name);
faction.alreadyInvited = true; faction.alreadyInvited = true;
@ -31,6 +67,8 @@ export function joinFaction(faction: Faction): void {
if (faction.isMember) return; if (faction.isMember) return;
faction.isMember = true; faction.isMember = true;
Player.factions.push(faction.name); Player.factions.push(faction.name);
Player.factions.sort((a, b) =>
factionOrder.indexOf(a) - factionOrder.indexOf(b));
const factionInfo = faction.getInfo(); const factionInfo = faction.getInfo();
//Determine what factions you are banned from now that you have joined this faction //Determine what factions you are banned from now that you have joined this faction
@ -132,19 +170,19 @@ export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = fal
if (!Settings.SuppressBuyAugmentationConfirmation) { if (!Settings.SuppressBuyAugmentationConfirmation) {
dialogBoxCreate( dialogBoxCreate(
"You purchased " + "You purchased " +
aug.name + aug.name +
". Its enhancements will not take " + ". Its enhancements will not take " +
"effect until they are installed. To install your augmentations, go to the " + "effect until they are installed. To install your augmentations, go to the " +
"'Augmentations' tab on the left-hand navigation menu. Purchasing additional " + "'Augmentations' tab on the left-hand navigation menu. Purchasing additional " +
"augmentations will now be more expensive.", "augmentations will now be more expensive.",
); );
} }
} }
} else { } else {
dialogBoxCreate( dialogBoxCreate(
"Hmm, something went wrong when trying to purchase an Augmentation. " + "Hmm, something went wrong when trying to purchase an Augmentation. " +
"Please report this to the game developer with an explanation of how to " + "Please report this to the game developer with an explanation of how to " +
"reproduce this.", "reproduce this.",
); );
} }
return ""; return "";

@ -157,7 +157,7 @@ export const FactionInfos: IMap<FactionInfo> = {
( (
<> <>
MegaCorp does what no other dares to do. We imagine. We create. We invent. We create what others have never even MegaCorp does what no other dares to do. We imagine. We create. We invent. We create what others have never even
dreamed of. Our work fills the world's needs for food, water, power, and transportation on an unprecendented dreamed of. Our work fills the world's needs for food, water, power, and transportation on an unprecedented
scale, in ways that no other company can. scale, in ways that no other company can.
<br /> <br />
<br /> <br />

@ -32,7 +32,7 @@ export function GangRoot(): React.ReactElement {
return ( return (
<Context.Gang.Provider value={gang}> <Context.Gang.Provider value={gang}>
<Tabs variant="fullWidth" value={value} onChange={handleChange}> <Tabs variant="fullWidth" value={value} onChange={handleChange} sx={{ minWidth: 'fit-content', maxWidth: '45%' }}>
<Tab label="Management" /> <Tab label="Management" />
<Tab label="Equipment" /> <Tab label="Equipment" />
<Tab label="Territory" /> <Tab label="Territory" />

@ -172,7 +172,7 @@ export function HacknetNodeElem(props: IProps): React.ReactElement {
<Table size="small"> <Table size="small">
<TableBody> <TableBody>
<TableRow> <TableRow>
<TableCell> <TableCell colSpan={3}>
<Typography>{node.name}</Typography> <Typography>{node.name}</Typography>
</TableCell> </TableCell>
</TableRow> </TableRow>

@ -404,8 +404,9 @@ export const Literatures: IMap<Literature> = {};
"What will the fate of the human race be?<br><br>" + "What will the fate of the human race be?<br><br>" +
"We live in an era vastly different from that of 15 or even 20 years ago. We have gone " + "We live in an era vastly different from that of 15 or even 20 years ago. We have gone " +
"beyond the limits of humanity. We have stripped ourselves of the tyranny of flesh.<br><br>" + "beyond the limits of humanity. We have stripped ourselves of the tyranny of flesh.<br><br>" +
"The Singularity is here. The merging of man and machine. This is where humanity evolves into "; "The Singularity is here. The merging of man and machine. This is where humanity evolves into " +
"something greater. This is our future.<br><br>" + "Embrace it, and you will obey a new god. The God in the Machine."; "something greater. This is our future.<br><br>" +
"Embrace it, and you will obey a new god. The God in the Machine.";
Literatures[fn] = new Literature(title, fn, txt); Literatures[fn] = new Literature(title, fn, txt);
title = "The New Triads"; title = "The New Triads";

@ -703,7 +703,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
}, },
weakenAnalyze: function (threads: any, cores: any = 1): number { weakenAnalyze: function (threads: any, cores: any = 1): number {
const coreBonus = 1 + (cores - 1) / 16; const coreBonus = 1 + (cores - 1) / 16;
return CONSTANTS.ServerWeakenAmount * threads * coreBonus; return CONSTANTS.ServerWeakenAmount * threads * coreBonus * BitNodeMultipliers.ServerWeakenRate;
}, },
share: function (): Promise<void> { share: function (): Promise<void> {
workerScript.log("share", () => "Sharing this computer."); workerScript.log("share", () => "Sharing this computer.");
@ -1121,7 +1121,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
// Invalid file name // Invalid file name
if (!scriptname.endsWith(".lit") && !isScriptFilename(scriptname) && !scriptname.endsWith("txt")) { if (!scriptname.endsWith(".lit") && !isScriptFilename(scriptname) && !scriptname.endsWith("txt")) {
throw makeRuntimeErrorMsg("scp", "Only works for .script, .lit, and .txt files"); throw makeRuntimeErrorMsg("scp", "Only works for scripts, .lit and .txt files");
} }
let destServer: BaseServer | null; let destServer: BaseServer | null;
@ -2279,7 +2279,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
if (typeof f !== "function") { if (typeof f !== "function") {
throw makeRuntimeErrorMsg("atExit", "argument should be function"); throw makeRuntimeErrorMsg("atExit", "argument should be function");
} }
workerScript.atExit = f; workerScript.atExit = () => { f(); }; // Wrap the user function to prevent WorkerScript leaking as 'this'
}, },
mv: function (host: string, source: string, destination: string): void { mv: function (host: string, source: string, destination: string): void {
updateDynamicRam("mv", getRamCost(Player, "mv")); updateDynamicRam("mv", getRamCost(Player, "mv"));

@ -365,7 +365,7 @@ export function NetscriptBladeburner(
checkBladeburnerAccess("getBonusTime"); checkBladeburnerAccess("getBonusTime");
const bladeburner = player.bladeburner; const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner"); if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return Math.round(bladeburner.storedCycles / 5); return (Math.round(bladeburner.storedCycles / 5))*1000;
}, },
}; };
} }

@ -194,6 +194,7 @@ export function NetscriptCorporation(
function bribe(factionName: string, amountCash: number, amountShares: number): boolean { function bribe(factionName: string, amountCash: number, amountShares: number): boolean {
if (!player.factions.includes(factionName)) throw new Error("Invalid faction name"); if (!player.factions.includes(factionName)) throw new Error("Invalid faction name");
if (isNaN(amountCash) || amountCash < 0 || isNaN(amountShares) || amountShares < 0) throw new Error("Invalid value for amount field! Must be numeric, grater than 0."); if (isNaN(amountCash) || amountCash < 0 || isNaN(amountShares) || amountShares < 0) throw new Error("Invalid value for amount field! Must be numeric, grater than 0.");
const corporation = getCorporation(); const corporation = getCorporation();
if (corporation.funds < amountCash) return false; if (corporation.funds < amountCash) return false;
if (corporation.numShares < amountShares) return false; if (corporation.numShares < amountShares) return false;
@ -431,7 +432,7 @@ export function NetscriptCorporation(
const cityName = helper.string("buyMaterial", "cityName", acityName); const cityName = helper.string("buyMaterial", "cityName", acityName);
const materialName = helper.string("buyMaterial", "materialName", amaterialName); const materialName = helper.string("buyMaterial", "materialName", amaterialName);
const amt = helper.number("buyMaterial", "amt", aamt); const amt = helper.number("buyMaterial", "amt", aamt);
if (amt < 0) throw new Error("Invalid value for amount field! Must be numeric and grater than 0"); if (amt < 0) throw new Error("Invalid value for amount field! Must be numeric and greater than 0");
const material = getMaterial(divisionName, cityName, materialName); const material = getMaterial(divisionName, cityName, materialName);
BuyMaterial(material, amt); BuyMaterial(material, amt);
}, },
@ -571,7 +572,7 @@ export function NetscriptCorporation(
const divisionName = helper.string("getOfficeSizeUpgradeCost", "divisionName", adivisionName); const divisionName = helper.string("getOfficeSizeUpgradeCost", "divisionName", adivisionName);
const cityName = helper.string("getOfficeSizeUpgradeCost", "cityName", acityName); const cityName = helper.string("getOfficeSizeUpgradeCost", "cityName", acityName);
const size = helper.number("getOfficeSizeUpgradeCost", "size", asize); const size = helper.number("getOfficeSizeUpgradeCost", "size", asize);
if (size < 0) throw new Error("Invalid value for size field! Must be numeric and grater than 0"); if (size < 0) throw new Error("Invalid value for size field! Must be numeric and greater than 0");
const office = getOffice(divisionName, cityName); const office = getOffice(divisionName, cityName);
const initialPriceMult = Math.round(office.size / CorporationConstants.OfficeInitialSize); const initialPriceMult = Math.round(office.size / CorporationConstants.OfficeInitialSize);
const costMultiplier = 1.09; const costMultiplier = 1.09;
@ -604,7 +605,7 @@ export function NetscriptCorporation(
const divisionName = helper.string("upgradeOfficeSize", "divisionName", adivisionName); const divisionName = helper.string("upgradeOfficeSize", "divisionName", adivisionName);
const cityName = helper.string("upgradeOfficeSize", "cityName", acityName); const cityName = helper.string("upgradeOfficeSize", "cityName", acityName);
const size = helper.number("upgradeOfficeSize", "size", asize); const size = helper.number("upgradeOfficeSize", "size", asize);
if (size < 0) throw new Error("Invalid value for size field! Must be numeric and grater than 0"); if (size < 0) throw new Error("Invalid value for size field! Must be numeric and greater than 0");
const office = getOffice(divisionName, cityName); const office = getOffice(divisionName, cityName);
const corporation = getCorporation(); const corporation = getCorporation();
UpgradeOfficeSize(corporation, office, size); UpgradeOfficeSize(corporation, office, size);
@ -614,7 +615,7 @@ export function NetscriptCorporation(
const divisionName = helper.string("throwParty", "divisionName", adivisionName); const divisionName = helper.string("throwParty", "divisionName", adivisionName);
const cityName = helper.string("throwParty", "cityName", acityName); const cityName = helper.string("throwParty", "cityName", acityName);
const costPerEmployee = helper.number("throwParty", "costPerEmployee", acostPerEmployee); const costPerEmployee = helper.number("throwParty", "costPerEmployee", acostPerEmployee);
if (costPerEmployee < 0) throw new Error("Invalid value for Cost Per Employee field! Must be numeric and grater than 0"); if (costPerEmployee < 0) throw new Error("Invalid value for Cost Per Employee field! Must be numeric and greater than 0");
const office = getOffice(divisionName, cityName); const office = getOffice(divisionName, cityName);
const corporation = getCorporation(); const corporation = getCorporation();
return netscriptDelay( return netscriptDelay(
@ -733,7 +734,7 @@ export function NetscriptCorporation(
issueDividends: function (apercent: any): void { issueDividends: function (apercent: any): void {
checkAccess("issueDividends"); checkAccess("issueDividends");
const percent = helper.number("issueDividends", "percent", apercent); const percent = helper.number("issueDividends", "percent", apercent);
if (percent < 0 || percent > 100) throw new Error("Invalid value for percent field! Must be numeric, grater than 0, and less than 100"); if (percent < 0 || percent > 100) throw new Error("Invalid value for percent field! Must be numeric, greater than 0, and less than 100");
const corporation = getCorporation(); const corporation = getCorporation();
if (!corporation.public) if (!corporation.public)
throw helper.makeRuntimeErrorMsg(`corporation.issueDividends`, `Your company has not gone public!`); throw helper.makeRuntimeErrorMsg(`corporation.issueDividends`, `Your company has not gone public!`);

@ -1212,7 +1212,7 @@ export function NetscriptSingularity(
); );
return false; return false;
} }
const repNeededToDonate = Math.round(CONSTANTS.BaseFavorToDonate * BitNodeMultipliers.RepToDonateToFaction); const repNeededToDonate = Math.floor(CONSTANTS.BaseFavorToDonate * BitNodeMultipliers.RepToDonateToFaction);
if (faction.favor < repNeededToDonate) { if (faction.favor < repNeededToDonate) {
workerScript.log( workerScript.log(
"donateToFaction", "donateToFaction",

@ -284,7 +284,7 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
}, },
purchaseSleeveAug: function (asleeveNumber: any = 0, aaugName: any = ""): boolean { purchaseSleeveAug: function (asleeveNumber: any = 0, aaugName: any = ""): boolean {
const sleeveNumber = helper.number("purchaseSleeveAug", "sleeveNumber", asleeveNumber); const sleeveNumber = helper.number("purchaseSleeveAug", "sleeveNumber", asleeveNumber);
const augName = helper.string("setToUniversityCourse", "augName", aaugName); const augName = helper.string("purchaseSleeveAug", "augName", aaugName);
helper.updateDynamicRam("purchaseSleeveAug", getRamCost(player, "sleeve", "purchaseSleeveAug")); helper.updateDynamicRam("purchaseSleeveAug", getRamCost(player, "sleeve", "purchaseSleeveAug"));
checkSleeveAPIAccess("purchaseSleeveAug"); checkSleeveAPIAccess("purchaseSleeveAug");
checkSleeveNumber("purchaseSleeveAug", sleeveNumber); checkSleeveNumber("purchaseSleeveAug", sleeveNumber);

@ -14,7 +14,6 @@ export function NetscriptPort(): IPort {
const data: any[] = []; const data: any[] = [];
return { return {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
write: (value: any): any => { write: (value: any): any => {
data.push(value); data.push(value);
if (data.length > Settings.MaxPortCapacity) { if (data.length > Settings.MaxPortCapacity) {
@ -23,7 +22,6 @@ export function NetscriptPort(): IPort {
return null; return null;
}, },
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
tryWrite: (value: any): boolean => { tryWrite: (value: any): boolean => {
if (data.length >= Settings.MaxPortCapacity) { if (data.length >= Settings.MaxPortCapacity) {
return false; return false;

@ -1708,7 +1708,7 @@ export function applyForJob(this: IPlayer, entryPosType: CompanyPosition, sing =
if (!this.isQualified(company, pos)) { if (!this.isQualified(company, pos)) {
const reqText = getJobRequirementText(company, pos); const reqText = getJobRequirementText(company, pos);
if (!sing) { if (!sing) {
dialogBoxCreate("Unforunately, you do not qualify for this position<br>" + reqText); dialogBoxCreate("Unfortunately, you do not qualify for this position<br>" + reqText);
} }
return false; return false;
} }
@ -1849,7 +1849,7 @@ export function applyForSecurityEngineerJob(this: IPlayer, sing = false): boolea
return this.applyForJob(CompanyPositions[posNames.SecurityEngineerCompanyPositions[0]], sing); return this.applyForJob(CompanyPositions[posNames.SecurityEngineerCompanyPositions[0]], sing);
} else { } else {
if (!sing) { if (!sing) {
dialogBoxCreate("Unforunately, you do not qualify for this position"); dialogBoxCreate("Unfortunately, you do not qualify for this position");
} }
return false; return false;
} }
@ -1862,7 +1862,7 @@ export function applyForNetworkEngineerJob(this: IPlayer, sing = false): boolean
return this.applyForJob(pos, sing); return this.applyForJob(pos, sing);
} else { } else {
if (!sing) { if (!sing) {
dialogBoxCreate("Unforunately, you do not qualify for this position"); dialogBoxCreate("Unfortunately, you do not qualify for this position");
} }
return false; return false;
} }
@ -1889,7 +1889,7 @@ export function applyForAgentJob(this: IPlayer, sing = false): boolean {
return this.applyForJob(pos, sing); return this.applyForJob(pos, sing);
} else { } else {
if (!sing) { if (!sing) {
dialogBoxCreate("Unforunately, you do not qualify for this position"); dialogBoxCreate("Unfortunately, you do not qualify for this position");
} }
return false; return false;
} }
@ -1914,7 +1914,7 @@ export function applyForEmployeeJob(this: IPlayer, sing = false): boolean {
return true; return true;
} else { } else {
if (!sing) { if (!sing) {
dialogBoxCreate("Unforunately, you do not qualify for this position"); dialogBoxCreate("Unfortunately, you do not qualify for this position");
} }
return false; return false;
@ -1939,7 +1939,7 @@ export function applyForPartTimeEmployeeJob(this: IPlayer, sing = false): boolea
return true; return true;
} else { } else {
if (!sing) { if (!sing) {
dialogBoxCreate("Unforunately, you do not qualify for this position"); dialogBoxCreate("Unfortunately, you do not qualify for this position");
} }
return false; return false;
@ -1963,7 +1963,7 @@ export function applyForWaiterJob(this: IPlayer, sing = false): boolean {
return true; return true;
} else { } else {
if (!sing) { if (!sing) {
dialogBoxCreate("Unforunately, you do not qualify for this position"); dialogBoxCreate("Unfortunately, you do not qualify for this position");
} }
return false; return false;
} }
@ -1986,7 +1986,7 @@ export function applyForPartTimeWaiterJob(this: IPlayer, sing = false): boolean
return true; return true;
} else { } else {
if (!sing) { if (!sing) {
dialogBoxCreate("Unforunately, you do not qualify for this position"); dialogBoxCreate("Unfortunately, you do not qualify for this position");
} }
return false; return false;
} }

@ -433,7 +433,7 @@ export class Sleeve extends Person {
} }
/** /**
* Called on every sleeve for a Source File prestige * Called on every sleeve for a Source File Prestige
*/ */
prestige(p: IPlayer): void { prestige(p: IPlayer): void {
// Reset exp // Reset exp
@ -453,7 +453,11 @@ export class Sleeve extends Person {
// Reset augs and multipliers // Reset augs and multipliers
this.augmentations = []; this.augmentations = [];
this.resetMultipliers(); this.resetMultipliers();
// Reset Location
this.city=CityName.Sector12;
// Reset sleeve-related stats // Reset sleeve-related stats
this.shock = 1; this.shock = 1;
this.storedCycles = 0; this.storedCycles = 0;

@ -52,8 +52,6 @@ export function findSleevePurchasableAugs(sleeve: Sleeve, p: IPlayer): Augmentat
availableAugs.push(aug); availableAugs.push(aug);
} }
} }
return availableAugs;
} }
for (const facName of p.factions) { for (const facName of p.factions) {

@ -105,9 +105,11 @@ interface RunningScript {
logs: string[]; logs: string[];
offlineExpGained: number; offlineExpGained: number;
offlineMoneyMade: number; offlineMoneyMade: number;
/** Offline running time of the script, in seconds **/
offlineRunningTime: number; offlineRunningTime: number;
onlineExpGained: number; onlineExpGained: number;
onlineMoneyMade: number; onlineMoneyMade: number;
/** Online running time of the script, in seconds **/
onlineRunningTime: number; onlineRunningTime: number;
pid: number; pid: number;
ramUsage: number; ramUsage: number;
@ -898,42 +900,75 @@ export interface GangTerritory {
* @public * @public
*/ */
export interface GangMemberInfo { export interface GangMemberInfo {
/** Name of the gang member */
name: string; name: string;
task: string; /** Currently assigned task */
task: string;
earnedRespect: number; earnedRespect: number;
/** Hack skill level */
hack: number; hack: number;
/** Strength skill level */
str: number; str: number;
/** Defense skill level */
def: number; def: number;
/** Dexterity skill level */
dex: number; dex: number;
/** Agility skill level */
agi: number; agi: number;
/** Charisma skill level */
cha: number; cha: number;
/** Current hack experience */
hack_exp: number; hack_exp: number;
/** Current strength experience */
str_exp: number; str_exp: number;
/** Current defense experience */
def_exp: number; def_exp: number;
/** Current dexterity experience */
dex_exp: number; dex_exp: number;
/** Current agility experience */
agi_exp: number; agi_exp: number;
/** Current charisma experience */
cha_exp: number; cha_exp: number;
/** Hack multiplier from equipment */
hack_mult: number; hack_mult: number;
/** Strength multiplier from equipment */
str_mult: number; str_mult: number;
/** Defense multiplier from equipment */
def_mult: number; def_mult: number;
/** Dexterity multiplier from equipment */
dex_mult: number; dex_mult: number;
/** Agility multiplier from equipment */
agi_mult: number; agi_mult: number;
/** Charisma multiplier from equipment */
cha_mult: number; cha_mult: number;
/** Hack multiplier from ascensions */
hack_asc_mult: number; hack_asc_mult: number;
/** Strength multiplier from ascensions */
str_asc_mult: number; str_asc_mult: number;
/** Defense multiplier from ascensions */
def_asc_mult: number; def_asc_mult: number;
/** Dexterity multiplier from ascensions */
dex_asc_mult: number; dex_asc_mult: number;
/** Agility multiplier from ascensions */
agi_asc_mult: number; agi_asc_mult: number;
/** Charisma multiplier from ascensions */
cha_asc_mult: number; cha_asc_mult: number;
/** Total earned hack experience */
hack_asc_points: number; hack_asc_points: number;
/** Total earned strength experience */
str_asc_points: number; str_asc_points: number;
/** Total earned defense experience */
def_asc_points: number; def_asc_points: number;
/** Total earned dexterity experience */
dex_asc_points: number; dex_asc_points: number;
/** Total earned agility experience */
agi_asc_points: number; agi_asc_points: number;
/** Total earned charisma experience */
cha_asc_points: number; cha_asc_points: number;
upgrades: string[]; upgrades: string[];
@ -1601,9 +1636,10 @@ export interface Singularity {
* The actions that can be stopped with this function are: * The actions that can be stopped with this function are:
* *
* * Studying at a university * * Studying at a university
* * Working out at a gym
* * Working for a company/faction * * Working for a company/faction
* * Creating a program * * Creating a program
* * Committing a Crime * * Committing a crime
* *
* This function will return true if the players action was ended. * This function will return true if the players action was ended.
* It will return false if the player was not performing an action when this function was called. * It will return false if the player was not performing an action when this function was called.
@ -4098,7 +4134,7 @@ interface UserInterface {
* ns.getHostname(); * ns.getHostname();
* // Some related functions are gathered under a sub-property of the ns object * // Some related functions are gathered under a sub-property of the ns object
* ns.stock.getPrice(); * ns.stock.getPrice();
* // Some functions need to be await ed * // Some functions need to be awaited
* await ns.hack('n00dles'); * await ns.hack('n00dles');
* } * }
* ``` * ```
@ -4926,6 +4962,34 @@ export interface NS extends Singularity {
* @returns True if the script is successfully killed, and false otherwise. * @returns True if the script is successfully killed, and false otherwise.
*/ */
kill(script: number): boolean; kill(script: number): boolean;
/**
* {@inheritDoc NS.(kill:1)}
* @example
* ```ts
* // NS1:
* //The following example will try to kill a script named foo.script on the foodnstuff server that was ran with no arguments:
* kill("foo.script", "foodnstuff");
*
* //The following will try to kill a script named foo.script on the current server that was ran with no arguments:
* kill("foo.script", getHostname());
*
* //The following will try to kill a script named foo.script on the current server that was ran with the arguments 1 and “foodnstuff”:
* kill("foo.script", getHostname(), 1, "foodnstuff");
* ```
* @example
* ```ts
* // NS2:
* //The following example will try to kill a script named foo.script on the foodnstuff server that was ran with no arguments:
* ns.kill("foo.script", "foodnstuff");
*
* //The following will try to kill a script named foo.script on the current server that was ran with no arguments:
* ns.kill("foo.script", getHostname());
*
* //The following will try to kill a script named foo.script on the current server that was ran with the arguments 1 and “foodnstuff”:
* ns.kill("foo.script", getHostname(), 1, "foodnstuff");
* ```
*/
kill(script: string, host: string, ...args: string[]): boolean; kill(script: string, host: string, ...args: string[]): boolean;
/** /**
@ -4991,6 +5055,37 @@ export interface NS extends Singularity {
* @returns True if the script/literature file is successfully copied over and false otherwise. If the files argument is an array then this function will return true if at least one of the files in the array is successfully copied. * @returns True if the script/literature file is successfully copied over and false otherwise. If the files argument is an array then this function will return true if at least one of the files in the array is successfully copied.
*/ */
scp(files: string | string[], destination: string): Promise<boolean>; scp(files: string | string[], destination: string): Promise<boolean>;
/**
* {@inheritDoc NS.(scp:1)}
* @example
* ```ts
* // NS1:
* //Copies foo.lit from the helios server to the home computer:
* scp("foo.lit", "helios", "home");
*
* //Tries to copy three files from rothman-uni to home computer:
* files = ["foo1.lit", "foo2.script", "foo3.script"];
* scp(files, "rothman-uni", "home");
* ```
* @example
* ```ts
* // NS2:
* //Copies foo.lit from the helios server to the home computer:
* await ns.scp("foo.lit", "helios", "home");
*
* //Tries to copy three files from rothman-uni to home computer:
* files = ["foo1.lit", "foo2.script", "foo3.script"];
* await ns.scp(files, "rothman-uni", "home");
* ```
* @example
* ```ts
* //ns2, copies files from home to a target server
* const server = ns.args[0];
* const files = ["hack.js","weaken.js","grow.js"];
* await ns.scp(files, "home", server);
* ```
*/
scp(files: string | string[], source: string, destination: string): Promise<boolean>; scp(files: string | string[], source: string, destination: string): Promise<boolean>;
/** /**
@ -5017,8 +5112,8 @@ export interface NS extends Singularity {
* @example * @example
* ```ts * ```ts
* // NS1: * // NS1:
* const scripts = ps("home"); * var scripts = ps("home");
* for (let i = 0; i < scripts.length; ++i) { * for (var i = 0; i < scripts.length; ++i) {
* tprint(scripts[i].filename + ' ' + scripts[i].threads); * tprint(scripts[i].filename + ' ' + scripts[i].threads);
* tprint(scripts[i].args); * tprint(scripts[i].args);
* } * }
@ -5027,8 +5122,8 @@ export interface NS extends Singularity {
* ```ts * ```ts
* // NS2: * // NS2:
* const ps = ns.ps("home"); * const ps = ns.ps("home");
* for (script of ps) { * for (let script of ps) {
* ns.tprint(`${script.filename} ${ps[i].threads}`); * ns.tprint(`${script.filename} ${script.threads}`);
* ns.tprint(script.args); * ns.tprint(script.args);
* } * }
* ``` * ```
@ -5795,6 +5890,10 @@ export interface NS extends Singularity {
* @returns Amount of income the specified script generates while online. * @returns Amount of income the specified script generates while online.
*/ */
getScriptIncome(): [number, number]; getScriptIncome(): [number, number];
/**
* {@inheritDoc NS.(getScriptIncome:1)}
*/
getScriptIncome(script: string, host: string, ...args: string[]): number; getScriptIncome(script: string, host: string, ...args: string[]): number;
/** /**
@ -5815,6 +5914,10 @@ export interface NS extends Singularity {
* @returns Amount of hacking experience the specified script generates while online. * @returns Amount of hacking experience the specified script generates while online.
*/ */
getScriptExpGain(): number; getScriptExpGain(): number;
/**
* {@inheritDoc NS.(getScriptExpGain:1)}
*/
getScriptExpGain(script: string, host: string, ...args: string[]): number; getScriptExpGain(script: string, host: string, ...args: string[]): number;
/** /**

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import React, { useState, useEffect, useRef, useMemo } from "react"; import React, { useState, useEffect, useRef, useMemo } from "react";
import Editor, { Monaco } from "@monaco-editor/react"; import Editor, { Monaco } from "@monaco-editor/react";
import * as monaco from "monaco-editor"; import * as monaco from "monaco-editor";
@ -754,7 +753,11 @@ export function Root(props: IProps): React.ReactElement {
// 44px bottom tool bar + 16px margin // 44px bottom tool bar + 16px margin
// + vim bar 34px // + vim bar 34px
const editorHeight = dimensions.height - (130 + (options.vim ? 34 : 0)); const editorHeight = dimensions.height - (130 + (options.vim ? 34 : 0));
const tabsMaxWidth = 1640
const tabMargin = 5
const tabMaxWidth = openScripts.length ? (tabsMaxWidth / openScripts.length) - tabMargin : 0
const tabIconWidth = 25
const tabTextWidth = tabMaxWidth - (tabIconWidth * 2)
return ( return (
<> <>
<div style={{ display: currentScript !== null ? "block" : "none", height: "100%", width: "100%" }}> <div style={{ display: currentScript !== null ? "block" : "none", height: "100%", width: "100%" }}>
@ -762,7 +765,7 @@ export function Root(props: IProps): React.ReactElement {
<Droppable droppableId="tabs" direction="horizontal"> <Droppable droppableId="tabs" direction="horizontal">
{(provided, snapshot) => ( {(provided, snapshot) => (
<Box <Box
maxWidth="1640px" maxWidth={`${tabsMaxWidth}px`}
display="flex" display="flex"
flexDirection="row" flexDirection="row"
alignItems="center" alignItems="center"
@ -778,8 +781,8 @@ export function Root(props: IProps): React.ReactElement {
> >
{openScripts.map(({ fileName, hostname }, index) => { {openScripts.map(({ fileName, hostname }, index) => {
const iconButtonStyle = { const iconButtonStyle = {
maxWidth: "25px", maxWidth: `${tabIconWidth}px`,
minWidth: "25px", minWidth: `${tabIconWidth}px`,
minHeight: '38.5px', minHeight: '38.5px',
maxHeight: '38.5px', maxHeight: '38.5px',
...(currentScript?.fileName === openScripts[index].fileName ? { ...(currentScript?.fileName === openScripts[index].fileName ? {
@ -792,6 +795,8 @@ export function Root(props: IProps): React.ReactElement {
color: Settings.theme.secondary color: Settings.theme.secondary
}) })
}; };
const scriptTabText = `${hostname}:~/${fileName} ${dirty(index)}`
return ( return (
<Draggable <Draggable
key={fileName + hostname} key={fileName + hostname}
@ -806,31 +811,39 @@ export function Root(props: IProps): React.ReactElement {
{...provided.dragHandleProps} {...provided.dragHandleProps}
style={{ style={{
...provided.draggableProps.style, ...provided.draggableProps.style,
marginRight: "5px", minWidth: '200px',
maxWidth: `${tabMaxWidth}px`,
marginRight: `${tabMargin}px`,
flexShrink: 0, flexShrink: 0,
border: '1px solid ' + Settings.theme.well, border: '1px solid ' + Settings.theme.well,
}} }}
> >
<Button <Tooltip title={scriptTabText}>
onClick={() => onTabClick(index)} <Button
onMouseDown={e => { onClick={() => onTabClick(index)}
e.preventDefault(); onMouseDown={e => {
if (e.button === 1) onTabClose(index); e.preventDefault();
}} if (e.button === 1) onTabClose(index);
style={{ }}
...(currentScript?.fileName === openScripts[index].fileName ? { style={{
background: Settings.theme.button, maxWidth: `${tabTextWidth}px`,
borderColor: Settings.theme.button, overflow: "hidden",
color: Settings.theme.primary ...(currentScript?.fileName === openScripts[index].fileName ? {
} : { background: Settings.theme.button,
background: Settings.theme.backgroundsecondary, borderColor: Settings.theme.button,
borderColor: Settings.theme.backgroundsecondary, color: Settings.theme.primary
color: Settings.theme.secondary } : {
}) background: Settings.theme.backgroundsecondary,
}} borderColor: Settings.theme.backgroundsecondary,
> color: Settings.theme.secondary
{hostname}:~/{fileName} {dirty(index)} })
</Button> }}
>
<span style={{ overflow: 'hidden', direction: 'rtl', textOverflow: "ellipsis" }}>
{scriptTabText}
</span>
</Button>
</Tooltip>
<Tooltip title="Overwrite editor content with saved file content"> <Tooltip title="Overwrite editor content with saved file content">
<Button onClick={() => onTabUpdate(index)} style={iconButtonStyle} > <Button onClick={() => onTabUpdate(index)} style={iconButtonStyle} >
<SyncIcon fontSize='small' /> <SyncIcon fontSize='small' />

@ -30,7 +30,7 @@ export function getPurchaseServerCost(ram: number): number {
const upg = Math.max(0, Math.log(sanitizedRam) / Math.log(2) - 6); const upg = Math.max(0, Math.log(sanitizedRam) / Math.log(2) - 6);
return Math.round( return (
sanitizedRam * sanitizedRam *
CONSTANTS.BaseCostFor1GBOfRamServer * CONSTANTS.BaseCostFor1GBOfRamServer *
BitNodeMultipliers.PurchasedServerCost * BitNodeMultipliers.PurchasedServerCost *

@ -314,7 +314,9 @@ export class Terminal implements ITerminal {
this.print("Organization name: " + (!isHacknet ? org : "player")); this.print("Organization name: " + (!isHacknet ? org : "player"));
const hasAdminRights = (!isHacknet && currServ.hasAdminRights) || isHacknet; const hasAdminRights = (!isHacknet && currServ.hasAdminRights) || isHacknet;
this.print("Root Access: " + (hasAdminRights ? "YES" : "NO")); this.print("Root Access: " + (hasAdminRights ? "YES" : "NO"));
this.print("Can run scripts on this host: " + (hasAdminRights ? "YES" : "NO")); const canRunScripts = hasAdminRights && currServ.maxRam > 0;
this.print("Can run scripts on this host: " + (canRunScripts ? "YES" : "NO"));
this.print("RAM: " + numeralWrapper.formatRAM(currServ.maxRam));
if (currServ instanceof Server) { if (currServ instanceof Server) {
this.print("Backdoor: " + (currServ.backdoorInstalled ? "YES" : "NO")); this.print("Backdoor: " + (currServ.backdoorInstalled ? "YES" : "NO"));
const hackingSkill = currServ.requiredHackingSkill; const hackingSkill = currServ.requiredHackingSkill;

@ -12,7 +12,7 @@ export function scan(
args: (string | number | boolean)[], args: (string | number | boolean)[],
): void { ): void {
if (args.length !== 0) { if (args.length !== 0) {
terminal.error("Incorrect usage of netstat/scan command. Usage: netstat/scan"); terminal.error("Incorrect usage of scan command. Usage: scan");
return; return;
} }

@ -114,7 +114,6 @@ Reviver.constructors.TextFile = TextFile;
* @param server The server object to look in * @param server The server object to look in
* @returns The file object, or null if it couldn't find it. * @returns The file object, or null if it couldn't find it.
*/ */
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function getTextFile(fn: string, server: BaseServer): TextFile | null { export function getTextFile(fn: string, server: BaseServer): TextFile | null {
let filename: string = !fn.endsWith(".txt") ? `${fn}.txt` : fn; let filename: string = !fn.endsWith(".txt") ? `${fn}.txt` : fn;
@ -138,7 +137,6 @@ export function getTextFile(fn: string, server: BaseServer): TextFile | null {
* @param server The server that the file should be created on. * @param server The server that the file should be created on.
* @returns The instance of the file. * @returns The instance of the file.
*/ */
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function createTextFile(fn: string, txt: string, server: BaseServer): TextFile | undefined { export function createTextFile(fn: string, txt: string, server: BaseServer): TextFile | undefined {
if (getTextFile(fn, server) !== null) { if (getTextFile(fn, server) !== null) {
// This should probably be a `throw`... // This should probably be a `throw`...

@ -336,6 +336,37 @@ export function refreshTheme(): void {
color: Settings.theme.primary, color: Settings.theme.primary,
}, },
}, },
root: {
backgroundColor: Settings.theme.backgroundsecondary,
border: "1px solid " + Settings.theme.well,
margin: '3px',
"&.Mui-selected": {
backgroundColor: Settings.theme.button
},
},
},
},
MuiTabs: {
styleOverrides: {
scrollButtons: {
backgroundColor: Settings.theme.backgroundsecondary,
color: Settings.theme.secondary,
margin: '3px',
opacity: 1,
width: 'fit-content',
"&.Mui-disabled": {
opacity: 0.5
}
}
},
defaultProps: {
TabIndicatorProps: {
style: {
display: "none"
}
}
}, },
}, },
MuiAlert: { MuiAlert: {

@ -816,7 +816,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
`Examples:\n`, `Examples:\n`,
`"()())()" -> [()()(), (())()]\n`, `"()())()" -> [()()(), (())()]\n`,
`"(a)())()" -> [(a)()(), (a())()]\n`, `"(a)())()" -> [(a)()(), (a())()]\n`,
`")( -> [""]`, `")(" -> [""]`,
].join(" "); ].join(" ");
}, },
difficulty: 10, difficulty: 10,
@ -921,7 +921,8 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
"The provided answer should be an array of strings containing the valid expressions.", "The provided answer should be an array of strings containing the valid expressions.",
"The data provided by this problem is an array with two elements. The first element", "The data provided by this problem is an array with two elements. The first element",
"is the string of digits, while the second element is the target number:\n\n", "is the string of digits, while the second element is the target number:\n\n",
`["${digits}", ${target}]\n\n`, `["${digits}", ${target}]\n\n`,
"NOTE: The order of evaluation expects script operator precedence",
"NOTE: Numbers in the expression cannot have leading 0's. In other words,", "NOTE: Numbers in the expression cannot have leading 0's. In other words,",
`"1+01" is not a valid expression`, `"1+01" is not a valid expression`,
"Examples:\n\n", "Examples:\n\n",

@ -31,9 +31,9 @@ export function ActiveScriptsRoot(props: IProps): React.ReactElement {
} }
return ( return (
<> <>
<Tabs variant="fullWidth" value={tab} onChange={handleChange}> <Tabs variant="fullWidth" value={tab} onChange={handleChange} sx={{ minWidth: 'fit-content', maxWidth: '25%' }}>
<Tab label={"Active"} value={"active"} /> <Tab label={"Active"} value={"active"} />
<Tab label={"Recent"} value={"recent"} /> <Tab label={"Recently Killed"} value={"recent"} />
</Tabs> </Tabs>
{tab === "active" && <ActiveScriptsPage workerScripts={props.workerScripts} />} {tab === "active" && <ActiveScriptsPage workerScripts={props.workerScripts} />}

@ -212,7 +212,7 @@ export function InteractiveTutorialRoot(): React.ReactElement {
<Typography> <Typography>
{" "} {" "}
we can see that the n00dles server is only one node away. Let's connect so it now using: we can see that the n00dles server is only one node away. Let's connect to it now using:
</Typography> </Typography>
<Typography classes={{ root: classes.textfield }}>{"[home ~/]> connect n00dles"}</Typography> <Typography classes={{ root: classes.textfield }}>{"[home ~/]> connect n00dles"}</Typography>

@ -42,7 +42,9 @@ export function PromptManager(): React.ReactElement {
<> <>
{prompt != null && ( {prompt != null && (
<Modal open={true} onClose={close}> <Modal open={true} onClose={close}>
<Typography>{prompt.txt}</Typography> <pre>
<Typography>{prompt.txt}</Typography>
</pre>
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', paddingTop: '10px' }}> <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', paddingTop: '10px' }}>
<Button style={{ marginRight: 'auto' }} onClick={yes}>Yes</Button> <Button style={{ marginRight: 'auto' }} onClick={yes}>Yes</Button>
<Button onClick={no}>No</Button> <Button onClick={no}>No</Button>

@ -16,7 +16,6 @@ import "numeral/locales/ru";
import { Settings } from "../Settings/Settings"; import { Settings } from "../Settings/Settings";
/* eslint-disable class-methods-use-this */
const extraFormats = [1e15, 1e18, 1e21, 1e24, 1e27, 1e30]; const extraFormats = [1e15, 1e18, 1e21, 1e24, 1e27, 1e30];
const extraNotations = ["q", "Q", "s", "S", "o", "n"]; const extraNotations = ["q", "Q", "s", "S", "o", "n"];

@ -1,4 +1,3 @@
/* eslint-disable spaced-comment */
module.exports = { module.exports = {
plugins: [ plugins: [
"stylelint-order" /*, "stylelint-order" /*,

@ -15,7 +15,6 @@
/** /**
* @type {Cypress.PluginConfig} * @type {Cypress.PluginConfig}
*/ */
// eslint-disable-next-line no-unused-vars
module.exports = (/*on, config*/) => { module.exports = (/*on, config*/) => {
// `on` is used to hook into various events Cypress emits // `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config // `config` is the resolved Cypress config

@ -1,4 +1,3 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { jest, describe, expect } from "@jest/globals"; import { jest, describe, expect } from "@jest/globals";
import { Player } from "../../../src/Player"; import { Player } from "../../../src/Player";

@ -1,3 +1,4 @@
/* eslint-disable no-await-in-loop */
import { Octokit } from "@octokit/rest"; import { Octokit } from "@octokit/rest";
import commandLineArgs from "command-line-args"; import commandLineArgs from "command-line-args";
@ -32,10 +33,10 @@ class MergeChangelog {
sha: entry.sha, sha: entry.sha,
url: entry.html_url, url: entry.html_url,
user: { user: {
id: entry.author.id, id: entry.author?.id,
login: entry.author.login, login: entry.author?.login,
avatar: entry.author.avatar_url, avatar: entry.author?.avatar_url,
url: entry.author.html_url, url: entry.author?.html_url,
}, },
commit_date: entry.commit.committer.date, commit_date: entry.commit.committer.date,
message: entry.commit.message, message: entry.commit.message,
@ -78,20 +79,19 @@ class MergeChangelog {
searchResults.push(...entries); searchResults.push(...entries);
} }
const pullRequestPromises = []; const pulls = [];
for (const entry of searchResults) { for (const entry of searchResults) {
pullRequestPromises.push( const r = await (this.octokit.rest.pulls.get({
this.octokit.rest.pulls.get({ owner, repo,
owner, repo, pull_number: entry.number,
pull_number: entry.number, }).then((response) => ({
}).then((response) => ({ ...entry,
...entry, merge_commit_sha: response.data.merge_commit_sha,
merge_commit_sha: response.data.merge_commit_sha, head_commit_sha: response.data.head.sha,
head_commit_sha: response.data.head.sha,
}))); })));
pulls.push(r);
await sleep(1000);
} }
const pulls = await Promise.all(pullRequestPromises);
return pulls; return pulls;
} }
@ -128,8 +128,9 @@ class MergeChangelog {
const pullQuery = `user:${owner} repo:${repo} is:pr is:merged merged:"${from.date.toISOString()}..${to.date.toISOString()}"`; const pullQuery = `user:${owner} repo:${repo} is:pr is:merged merged:"${from.date.toISOString()}..${to.date.toISOString()}"`;
const commits = await this.getCommitsSearchResults(commitQuery); const commits = await this.getCommitsSearchResults(commitQuery);
await sleep(5000)
const pulls = await this.getPullsSearchResults(pullQuery); const pulls = await this.getPullsSearchResults(pullQuery);
await sleep(5000)
// We only have the merge commit sha & the HEAD sha in this data, but it can exclude some entries // We only have the merge commit sha & the HEAD sha in this data, but it can exclude some entries
const pullsCommitSha = pulls. const pullsCommitSha = pulls.
map((p) => [p.merge_commit_sha, p.head_commit_sha]). map((p) => [p.merge_commit_sha, p.head_commit_sha]).
@ -234,6 +235,12 @@ ${commitLines.join('\n')}
} }
} }
const sleep = async (wait) => {
return new Promise((resolve) => {
setTimeout(resolve, wait)
})
}
const api = new MergeChangelog({ auth: process.env.GITHUB_API_TOKEN }); const api = new MergeChangelog({ auth: process.env.GITHUB_API_TOKEN });
api.getChangelog(cliArgs.from, cliArgs.to, cliArgs.detailed).then((data) => { api.getChangelog(cliArgs.from, cliArgs.to, cliArgs.detailed).then((data) => {
console.log(data.log); console.log(data.log);