Merge pull request #3414 from danielyxie/dev

clean up root files.
This commit is contained in:
hydroflame 2022-04-12 17:38:31 -04:00 committed by GitHub
commit 997f20aa68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 1797 additions and 1487 deletions

@ -1,3 +1,3 @@
last 4 versions last 8 versions
not dead not dead
not ie <= 11 not ie <= 11

@ -353,6 +353,7 @@ module.exports = {
"no-useless-constructor": [ "no-useless-constructor": [
"off", // Valid for typescript due to property ctor shorthand "off", // Valid for typescript due to property ctor shorthand
], ],
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
"@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/ban-ts-ignore": "off", "@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/camelcase": "off", "@typescript-eslint/camelcase": "off",

11
dist/bitburner.d.ts vendored

@ -2101,7 +2101,7 @@ export declare interface Hacknet {
* // NS1: * // NS1:
* var upgradeName = "Sell for Corporation Funds"; * var upgradeName = "Sell for Corporation Funds";
* if (hacknet.numHashes() > hacknet.hashCost(upgradeName)) { * if (hacknet.numHashes() > hacknet.hashCost(upgradeName)) {
* hacknet.spendHashes(upgName); * hacknet.spendHashes(upgradeName);
* } * }
* ``` * ```
* @example * @example
@ -2109,7 +2109,7 @@ export declare interface Hacknet {
* // NS2: * // NS2:
* const upgradeName = "Sell for Corporation Funds"; * const upgradeName = "Sell for Corporation Funds";
* if (ns.hacknet.numHashes() > ns.hacknet.hashCost(upgradeName)) { * if (ns.hacknet.numHashes() > ns.hacknet.hashCost(upgradeName)) {
* ns.hacknet.spendHashes(upgName); * ns.hacknet.spendHashes(upgradeName);
* } * }
* ``` * ```
* @param upgName - Name of the upgrade of Hacknet Node. * @param upgName - Name of the upgrade of Hacknet Node.
@ -2825,9 +2825,10 @@ export declare interface NS extends Singularity {
* Returns the security increase that would occur if a hack with this many threads happened. * Returns the security increase that would occur if a hack with this many threads happened.
* *
* @param threads - Amount of threads that will be used. * @param threads - Amount of threads that will be used.
* @param hostname - Hostname of the target server. The number of threads is limited to the number needed to hack the servers maximum amount of money.
* @returns The security increase. * @returns The security increase.
*/ */
hackAnalyzeSecurity(threads: number): number; hackAnalyzeSecurity(threads: number, hostname?: string): number;
/** /**
* Get the chance of successfully hacking a server. * Get the chance of successfully hacking a server.
@ -4671,8 +4672,10 @@ export declare interface Office {
maxMor: number; maxMor: number;
/** Name of all the employees */ /** Name of all the employees */
employees: string[]; employees: string[];
/** Positions of the employees */ /** Production of the employees */
employeeProd: EmployeeJobs; employeeProd: EmployeeJobs;
/** Positions of the employees */
employeeJobs: EmployeeJobs;
} }
/** /**

4
dist/main.bundle.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

42
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -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="main.bundle.js"></script></body> <script type="text/javascript" src="dist/vendor.bundle.js"></script><script type="text/javascript" src="dist/main.bundle.js"></script></body>
</html> </html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

20
package-lock.json generated

@ -5965,13 +5965,19 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001265", "version": "1.0.30001328",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001328.tgz",
"integrity": "sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw==", "integrity": "sha512-Ue55jHkR/s4r00FLNiX+hGMMuwml/QGqqzVeMQ5thUewznU2EdULFvI3JR7JJid6OrjJNfFvHY2G2dIjmRaDDQ==",
"funding": { "funding": [
{
"type": "opencollective", "type": "opencollective",
"url": "https://opencollective.com/browserslist" "url": "https://opencollective.com/browserslist"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
} }
]
}, },
"node_modules/caseless": { "node_modules/caseless": {
"version": "0.12.0", "version": "0.12.0",
@ -26994,9 +27000,9 @@
"dev": true "dev": true
}, },
"caniuse-lite": { "caniuse-lite": {
"version": "1.0.30001265", "version": "1.0.30001328",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001328.tgz",
"integrity": "sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw==" "integrity": "sha512-Ue55jHkR/s4r00FLNiX+hGMMuwml/QGqqzVeMQ5thUewznU2EdULFvI3JR7JJid6OrjJNfFvHY2G2dIjmRaDDQ=="
}, },
"caseless": { "caseless": {
"version": "0.12.0", "version": "0.12.0",

@ -115,15 +115,15 @@
"start:container": "webpack-dev-server --progress --env.devServer --mode development --env.runInContainer", "start:container": "webpack-dev-server --progress --env.devServer --mode development --env.runInContainer",
"build": "webpack --mode production", "build": "webpack --mode production",
"build:dev": "webpack --mode development", "build:dev": "webpack --mode development",
"lint": "eslint --fix . --ext js,jsx,ts,tsx", "lint": "eslint --fix --ext js,jsx,ts,tsx --max-warnings 0 .",
"lint:report": "eslint --ext js,jsx,ts,tsx .", "lint:report": "eslint --ext js,jsx,ts,tsx --max-warnings 0 .",
"preinstall": "node ./tools/engines-check/engines-check.js", "preinstall": "node ./tools/engines-check/engines-check.js",
"postinstall": "cd electron && npm install", "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",
"watch:dev": "webpack --watch --mode development", "watch:dev": "webpack --watch --mode development",
"electron": "sh ./package.sh", "electron": "sh ./tools/package-electron.sh",
"electron:packager": "electron-packager .package bitburner --all --out .build --overwrite --icon .package/icon.png --no-prune", "electron:packager": "electron-packager .package bitburner --all --out .build --overwrite --icon .package/icon.png --no-prune",
"electron:packager-all": "electron-packager .package bitburner --all --out .build --overwrite --icon .package/icon.png", "electron:packager-all": "electron-packager .package bitburner --all --out .build --overwrite --icon .package/icon.png",
"electron:packager-win": "electron-packager .package bitburner --platform win32 --arch x64 --out .build --overwrite --icon .package/icon.png", "electron:packager-win": "electron-packager .package bitburner --platform win32 --arch x64 --out .build --overwrite --icon .package/icon.png",

@ -1,31 +0,0 @@
#!/bin/sh
# Clear out any files remaining from old builds
rm -rf .package
mkdir -p .package/dist/src/ThirdParty || true
mkdir -p .package/src/ThirdParty || true
mkdir -p .package/node_modules || true
cp index.html .package
cp -r electron/* .package
cp -r dist/ext .package/dist
cp -r dist/icons .package/dist
cp -r dist/images .package/dist
# The js files.
cp dist/vendor.bundle.js .package/dist/vendor.bundle.js
cp main.bundle.js .package/main.bundle.js
# Source maps
cp dist/vendor.bundle.js.map .package/dist/vendor.bundle.js.map
cp main.bundle.js.map .package/main.bundle.js.map
# Install electron sub-dependencies
cd electron
npm install
cd ..
BUILD_PLATFORM="${1:-"all"}"
# And finally build the app.
npm run electron:packager-$BUILD_PLATFORM

@ -21,6 +21,7 @@ import {
initNeuroFluxGovernor, initNeuroFluxGovernor,
initUnstableCircadianModulator, initUnstableCircadianModulator,
} from "./AugmentationCreator"; } from "./AugmentationCreator";
import { Router } from "../ui/GameRoot";
export function AddToAugmentations(aug: Augmentation): void { export function AddToAugmentations(aug: Augmentation): void {
const name = aug.name; const name = aug.name;
@ -194,6 +195,7 @@ function installAugmentations(force?: boolean): boolean {
); );
} }
prestigeAugmentation(); prestigeAugmentation();
Router.toTerminal();
return true; return true;
} }

@ -7,7 +7,7 @@ import React, { useState, useEffect } from "react";
import { InstalledAugmentations } from "./InstalledAugmentations"; import { InstalledAugmentations } from "./InstalledAugmentations";
import { PlayerMultipliers } from "./PlayerMultipliers"; import { PlayerMultipliers } from "./PlayerMultipliers";
import { PurchasedAugmentations } from "./PurchasedAugmentations"; import { PurchasedAugmentations } from "./PurchasedAugmentations";
import { SourceFiles } from "./SourceFiles"; import { SourceFilesElement } from "./SourceFiles";
import { canGetBonus } from "../../ExportBonus"; import { canGetBonus } from "../../ExportBonus";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
@ -16,8 +16,55 @@ import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import Tooltip from "@mui/material/Tooltip"; import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Paper from "@mui/material/Paper";
import Container from "@mui/material/Container";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { ConfirmationModal } from "../../ui/React/ConfirmationModal"; import { ConfirmationModal } from "../../ui/React/ConfirmationModal";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { AugmentationNames } from "../data/AugmentationNames";
import { Augmentations } from "../Augmentations";
import { CONSTANTS } from "../../Constants";
import { formatNumber } from "../../utils/StringHelperFunctions";
import { Info } from "@mui/icons-material";
interface NFGDisplayProps {
player: IPlayer;
}
const NeuroFluxDisplay = ({ player }: NFGDisplayProps): React.ReactElement => {
const level = player.augmentations.find((e) => e.name === AugmentationNames.NeuroFluxGovernor)?.level ?? 0;
return level > 0 ? (
<Paper sx={{ p: 1 }}>
<Typography variant="h5" color={Settings.theme.info}>
NeuroFlux Governor - Level {level}
</Typography>
<Typography color={Settings.theme.info}>{Augmentations[AugmentationNames.NeuroFluxGovernor].stats}</Typography>
</Paper>
) : (
<></>
);
};
interface EntropyDisplayProps {
player: IPlayer;
}
const EntropyDisplay = ({ player }: EntropyDisplayProps): React.ReactElement => {
return player.entropy > 0 ? (
<Paper sx={{ p: 1 }}>
<Typography variant="h5" color={Settings.theme.error}>
Entropy Virus - Level {player.entropy}
</Typography>
<Typography color={Settings.theme.error}>
<b>All multipliers decreased by:</b> {formatNumber((1 - CONSTANTS.EntropyEffect ** player.entropy) * 100, 3)}%
(multiplicative)
</Typography>
</Paper>
) : (
<></>
);
};
interface IProps { interface IProps {
exportGameFn: () => void; exportGameFn: () => void;
@ -55,14 +102,22 @@ export function AugmentationsRoot(props: IProps): React.ReactElement {
} }
return ( return (
<> <Container disableGutters maxWidth="lg" sx={{ mx: 0 }}>
<Typography variant="h4">Augmentations</Typography> <Typography variant="h4">Augmentations</Typography>
<Box mx={2}> <Box sx={{ mb: 1 }}>
<Paper sx={{ p: 1 }}>
<Typography variant="h5" color="primary" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
Purchased Augmentations
<Tooltip
title={
<>
<Typography> <Typography>
Below is a list of all Augmentations you have purchased but not yet installed. Click the button below to Below is a list of all Augmentations you have purchased but not yet installed. Click the button
install them. below to install them.
</Typography>
<Typography>
WARNING: Installing your Augmentations resets most of your progress, including:
</Typography> </Typography>
<Typography>WARNING: Installing your Augmentations resets most of your progress, including:</Typography>
<br /> <br />
<Typography>- Stats/Skill levels and Experience</Typography> <Typography>- Stats/Skill levels and Experience</Typography>
<Typography>- Money</Typography> <Typography>- Money</Typography>
@ -73,22 +128,16 @@ export function AugmentationsRoot(props: IProps): React.ReactElement {
<Typography>- Stocks</Typography> <Typography>- Stocks</Typography>
<br /> <br />
<Typography> <Typography>
Installing Augmentations lets you start over with the perks and benefits granted by all of the Augmentations Installing Augmentations lets you start over with the perks and benefits granted by all of the
you have ever installed. Also, you will keep any scripts and RAM/Core upgrades on your home computer (but you Augmentations you have ever installed. Also, you will keep any scripts and RAM/Core upgrades on your
will lose all programs besides NUKE.exe) home computer (but you will lose all programs besides NUKE.exe)
</Typography> </Typography>
</Box> </>
<Typography variant="h4" color="primary"> }
Purchased Augmentations >
</Typography> <Info sx={{ ml: 1, mb: 0.5 }} color="info" />
<Box mx={2}>
<Tooltip title={<Typography>'I never asked for this'</Typography>}>
<span>
<Button disabled={player.queuedAugmentations.length === 0} onClick={doInstall}>
Install Augmentations
</Button>
</span>
</Tooltip> </Tooltip>
</Typography>
<ConfirmationModal <ConfirmationModal
open={installOpen} open={installOpen}
onClose={() => setInstallOpen(false)} onClose={() => setInstallOpen(false)}
@ -109,27 +158,57 @@ export function AugmentationsRoot(props: IProps): React.ReactElement {
<br />- home ram and cores <br />- home ram and cores
<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
choosing. your choosing.
</> </>
} }
/> />
<Box sx={{ display: "grid", width: "100%", gridTemplateColumns: "1fr 1fr" }}>
<Tooltip title={<Typography>'I never asked for this'</Typography>}>
<span>
<Button sx={{ width: "100%" }} disabled={player.queuedAugmentations.length === 0} onClick={doInstall}>
Install Augmentations
</Button>
</span>
</Tooltip>
<Tooltip title={<Typography>It's always a good idea to backup/export your save!</Typography>}> <Tooltip title={<Typography>It's always a good idea to backup/export your save!</Typography>}>
<Button sx={{ mx: 2 }} onClick={doExport} color="error"> <Button sx={{ width: "100%" }} onClick={doExport} color="error">
Backup Save {exportBonusStr()} Backup Save {exportBonusStr()}
</Button> </Button>
</Tooltip> </Tooltip>
<PurchasedAugmentations />
</Box> </Box>
<Typography variant="h4">Installed Augmentations</Typography> </Paper>
<Box mx={2}> {player.queuedAugmentations.length > 0 ? (
<Typography> <Box sx={{ display: "grid", gridTemplateColumns: "1fr 3fr" }}>
List of all Augmentations that have been installed. You have gained the effects of these. <PurchasedAugmentations />
</Typography> <PlayerMultipliers />
</Box>
) : (
<Paper sx={{ p: 1 }}>
<Typography>No Augmentations have been purchased yet</Typography>
</Paper>
)}
</Box>
<Box
sx={{
my: 1,
display: "grid",
gridTemplateColumns: `repeat(${
+!!((player.augmentations.find((e) => e.name === AugmentationNames.NeuroFluxGovernor)?.level ?? 0) > 0) +
+!!(player.entropy > 0)
}, 1fr)`,
gap: 1,
}}
>
<NeuroFluxDisplay player={player} />
<EntropyDisplay player={player} />
</Box>
<Box>
<InstalledAugmentations /> <InstalledAugmentations />
</Box> </Box>
<PlayerMultipliers /> <SourceFilesElement />
<SourceFiles /> </Container>
</>
); );
} }

@ -5,28 +5,23 @@
* It also contains 'configuration' buttons that allow you to change how the * It also contains 'configuration' buttons that allow you to change how the
* Augs/SF's are displayed * Augs/SF's are displayed
*/ */
import { Box, ListItemButton, Paper, Typography } from "@mui/material";
import Button from "@mui/material/Button";
import List from "@mui/material/List";
import Tooltip from "@mui/material/Tooltip";
import React, { useState } from "react"; import React, { useState } from "react";
import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums";
import { AugmentationAccordion } from "../../ui/React/AugmentationAccordion";
import { Augmentations } from "../Augmentations";
import { AugmentationNames } from "../data/AugmentationNames";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums"; import { Augmentations } from "../Augmentations";
import Button from "@mui/material/Button"; import { AugmentationNames } from "../data/AugmentationNames";
import Tooltip from "@mui/material/Tooltip";
import List from "@mui/material/List";
import { ExpandLess, ExpandMore } from "@mui/icons-material";
import { Box, Paper, ListItemButton, ListItemText, Typography, Collapse } from "@mui/material";
import { CONSTANTS } from "../../Constants";
import { formatNumber } from "../../utils/StringHelperFunctions";
export function InstalledAugmentations(): React.ReactElement { export function InstalledAugmentations(): React.ReactElement {
const setRerender = useState(true)[1]; const setRerender = useState(true)[1];
const player = use.Player(); const player = use.Player();
const sourceAugs = player.augmentations.slice().filter((aug) => aug.name !== AugmentationNames.NeuroFluxGovernor);
const sourceAugs = player.augmentations.slice(); const [selectedAug, setSelectedAug] = useState(sourceAugs[0]);
if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) { if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) {
sourceAugs.sort((aug1, aug2) => { sourceAugs.sort((aug1, aug2) => {
@ -49,59 +44,60 @@ export function InstalledAugmentations(): React.ReactElement {
} }
return ( return (
<> <Box sx={{ width: "100%" }}>
<Paper sx={{ p: 1 }}>
<Typography variant="h5">Installed Augmentations</Typography>
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
<Tooltip title={"Sorts the Augmentations alphabetically in numeral order"}> <Tooltip title={"Sorts the Augmentations alphabetically in numeral order"}>
<Button onClick={sortInOrder}>Sort in Order</Button> <Button sx={{ width: "100%" }} onClick={sortInOrder}>
</Tooltip> Sort in Order
<Tooltip title={"Sorts the Augmentations based on when you acquired them (same as default)"}>
<Button sx={{ mx: 2 }} onClick={sortByAcquirementTime}>
Sort by Acquirement Time
</Button> </Button>
</Tooltip> </Tooltip>
<List dense> <Tooltip title={"Sorts the Augmentations based on when you acquired them (same as default)"}>
{player.entropy > 0 && <Button sx={{ width: "100%" }} onClick={sortByAcquirementTime}>
(() => { Sort by Time of Acquirement
const [open, setOpen] = useState(false); </Button>
</Tooltip>
return ( </Box>
<Box component={Paper}> </Paper>
<ListItemButton onClick={() => setOpen((old) => !old)}> {sourceAugs.length > 0 ? (
<ListItemText <Paper sx={{ display: "grid", gridTemplateColumns: "1fr 3fr" }}>
primary={ <Box>
<Typography color={Settings.theme.hp} style={{ whiteSpace: "pre-wrap" }}> <List sx={{ height: 400, overflowY: "scroll", borderRight: `1px solid ${Settings.theme.welllight}` }}>
Entropy Virus - Level {player.entropy} {sourceAugs.map((k, i) => (
</Typography> <ListItemButton key={i + 1} onClick={() => setSelectedAug(k)} selected={selectedAug === k}>
} <Typography>{k.name}</Typography>
/>
{open ? (
<ExpandLess sx={{ color: Settings.theme.hp }} />
) : (
<ExpandMore sx={{ color: Settings.theme.hp }} />
)}
</ListItemButton> </ListItemButton>
<Collapse in={open} unmountOnExit> ))}
<Box m={4}>
<Typography color={Settings.theme.hp}>
<b>All multipliers decreased by:</b>{" "}
{formatNumber((1 - CONSTANTS.EntropyEffect ** player.entropy) * 100, 3)}% (multiplicative)
</Typography>
</Box>
</Collapse>
</Box>
);
})()}
{sourceAugs.map((e) => {
const aug = Augmentations[e.name];
let level = null;
if (e.name === AugmentationNames.NeuroFluxGovernor) {
level = e.level;
}
return <AugmentationAccordion key={aug.name} aug={aug} level={level} />;
})}
</List> </List>
</Box>
<Box sx={{ m: 1 }}>
<Typography variant="h6" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
{selectedAug.name}
</Typography>
<Typography sx={{ maxHeight: 350, overflowY: "scroll" }}>
{(() => {
const aug = Augmentations[selectedAug.name];
const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info;
const tooltip = (
<>
{info}
<br />
<br />
{aug.stats}
</> </>
); );
return tooltip;
})()}
</Typography>
</Box>
</Paper>
) : (
<Paper sx={{ p: 1 }}>
<Typography>No Augmentations have been installed yet</Typography>
</Paper>
)}
</Box>
);
} }

@ -1,20 +1,22 @@
/** /**
* React component for displaying the player's multipliers on the Augmentation UI page * React component for displaying the player's multipliers on the Augmentation UI page
*/ */
import { DoubleArrow } from "@mui/icons-material";
import { List, ListItem, ListItemText, Paper, Typography } from "@mui/material";
import * as React from "react"; import * as React from "react";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { Player } from "../../Player"; import { Player } from "../../Player";
import { Settings } from "../../Settings/Settings";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { Augmentations } from "../Augmentations"; import { Augmentations } from "../Augmentations";
import { Table, TableCell } from "../../ui/React/Table";
import TableBody from "@mui/material/TableBody";
import TableRow from "@mui/material/TableRow";
import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
function calculateAugmentedStats(): any { interface IAugmentedStats {
const augP: any = {}; [index: string]: number;
}
function calculateAugmentedStats(): IAugmentedStats {
const augP: IAugmentedStats = {};
for (const aug of Player.queuedAugmentations) { for (const aug of Player.queuedAugmentations) {
const augObj = Augmentations[aug.name]; const augObj = Augmentations[aug.name];
for (const mult of Object.keys(augObj.mults)) { for (const mult of Object.keys(augObj.mults)) {
@ -25,112 +27,82 @@ function calculateAugmentedStats(): any {
return augP; return augP;
} }
function Improvements({ r, m }: { r: number; m: number }): React.ReactElement { interface IBitNodeModifiedStatsProps {
if (r) {
return (
<>
<TableCell key="2">
<Typography>&nbsp;{"=>"}&nbsp;</Typography>
</TableCell>
<TableCell key="3">
<Typography>
{numeralWrapper.formatPercentage(r)} <BN5Stat base={r} mult={m} />
</Typography>
</TableCell>
</>
);
}
return <></>;
}
interface IBN5StatsProps {
base: number; base: number;
mult: number; mult: number;
color: string;
} }
function BN5Stat(props: IBN5StatsProps): React.ReactElement { function BitNodeModifiedStats(props: IBitNodeModifiedStatsProps): React.ReactElement {
if (props.mult === 1) return <></>; // If player doesn't have SF5 or if the property isn't affected by BitNode mults
return <>({numeralWrapper.formatPercentage(props.base * props.mult)})</>; if (props.mult === 1 || SourceFileFlags[5] === 0)
} return <Typography color={props.color}>{numeralWrapper.formatPercentage(props.base)}</Typography>;
function MultiplierTable({ rows }: { rows: [string, number, number, number][] }): React.ReactElement {
return ( return (
<Table size="small" padding="none"> <Typography color={props.color}>
<TableBody> <span style={{ opacity: 0.5 }}>{numeralWrapper.formatPercentage(props.base)}</span>{" "}
{rows.map((r: any) => ( {numeralWrapper.formatPercentage(props.base * props.mult)}
<TableRow key={r[0]}>
<TableCell key="0">
<Typography noWrap>{r[0]} multiplier:&nbsp;</Typography>
</TableCell>
<TableCell key="1" style={{ textAlign: "right" }}>
<Typography noWrap>
{numeralWrapper.formatPercentage(r[1])} <BN5Stat base={r[1]} mult={r[3]} />
</Typography> </Typography>
</TableCell>
<Improvements r={r[2]} m={r[3]} />
</TableRow>
))}
</TableBody>
</Table>
); );
} }
type MultiplierListItemData = [
multiplier: string,
currentValue: number,
augmentedValue: number,
bitNodeMultiplier: number,
color: string,
];
interface IMultiplierListProps {
rows: MultiplierListItemData[];
}
function MultiplierList(props: IMultiplierListProps): React.ReactElement {
const listItems = props.rows
.map((data) => {
const [multiplier, currentValue, augmentedValue, bitNodeMultiplier, color] = data;
if (!isNaN(augmentedValue)) {
return (
<ListItem key={multiplier} disableGutters sx={{ py: 0 }}>
<ListItemText
sx={{ my: 0.1 }}
primary={
<Typography color={color}>
<b>{multiplier}</b>
</Typography>
}
secondary={
<span style={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
<BitNodeModifiedStats base={currentValue} mult={bitNodeMultiplier} color={color} />
<DoubleArrow fontSize="small" color="success" sx={{ mb: 0.5, mx: 1 }} />
<BitNodeModifiedStats base={augmentedValue} mult={bitNodeMultiplier} color={Settings.theme.success} />
</span>
}
disableTypography
/>
</ListItem>
);
}
return;
})
.filter((i) => i !== undefined);
return listItems.length > 0 ? <List disablePadding>{listItems}</List> : <></>;
}
export function PlayerMultipliers(): React.ReactElement { export function PlayerMultipliers(): React.ReactElement {
const mults = calculateAugmentedStats(); const mults = calculateAugmentedStats();
function BladeburnerMults(): React.ReactElement { // Column data is a bit janky, so it's set up here to allow for
if (!Player.canAccessBladeburner()) return <></>; // easier logic in setting up the layout
return ( const leftColData: MultiplierListItemData[] = [
<> ...[
<MultiplierTable
rows={[
[
"Bladeburner Success Chance",
Player.bladeburner_success_chance_mult,
Player.bladeburner_success_chance_mult * mults.bladeburner_success_chance_mult,
1,
],
[
"Bladeburner Max Stamina",
Player.bladeburner_max_stamina_mult,
Player.bladeburner_max_stamina_mult * mults.bladeburner_max_stamina_mult,
1,
],
[
"Bladeburner Stamina Gain",
Player.bladeburner_stamina_gain_mult,
Player.bladeburner_stamina_gain_mult * mults.bladeburner_stamina_gain_mult,
1,
],
[
"Bladeburner Field Analysis",
Player.bladeburner_analysis_mult,
Player.bladeburner_analysis_mult * mults.bladeburner_analysis_mult,
1,
],
]}
/>
<br />
</>
);
}
return (
<>
<Typography variant="h4">Multipliers</Typography>
<Box mx={2}>
<MultiplierTable
rows={[
["Hacking Chance ", Player.hacking_chance_mult, Player.hacking_chance_mult * mults.hacking_chance_mult, 1], ["Hacking Chance ", Player.hacking_chance_mult, Player.hacking_chance_mult * mults.hacking_chance_mult, 1],
["Hacking Speed ", Player.hacking_speed_mult, Player.hacking_speed_mult * mults.hacking_speed_mult, 1], ["Hacking Speed ", Player.hacking_speed_mult, Player.hacking_speed_mult * mults.hacking_speed_mult, 1],
["Hacking Money ", Player.hacking_money_mult, Player.hacking_money_mult * mults.hacking_money_mult, 1], ["Hacking Money ", Player.hacking_money_mult, Player.hacking_money_mult * mults.hacking_money_mult, 1],
["Hacking Growth ", Player.hacking_grow_mult, Player.hacking_grow_mult * mults.hacking_grow_mult, 1], ["Hacking Growth ", Player.hacking_grow_mult, Player.hacking_grow_mult * mults.hacking_grow_mult, 1],
]}
/>
<br />
<MultiplierTable
rows={[
[ [
"Hacking Level ", "Hacking Level ",
Player.hacking_mult, Player.hacking_mult,
@ -143,12 +115,8 @@ export function PlayerMultipliers(): React.ReactElement {
Player.hacking_exp_mult * mults.hacking_exp_mult, Player.hacking_exp_mult * mults.hacking_exp_mult,
BitNodeMultipliers.HackExpGain, BitNodeMultipliers.HackExpGain,
], ],
]} ].map((data): MultiplierListItemData => (data as any).concat([Settings.theme.hack])),
/> ...[
<br />
<MultiplierTable
rows={[
[ [
"Strength Level ", "Strength Level ",
Player.strength_mult, Player.strength_mult,
@ -156,12 +124,6 @@ export function PlayerMultipliers(): React.ReactElement {
BitNodeMultipliers.StrengthLevelMultiplier, BitNodeMultipliers.StrengthLevelMultiplier,
], ],
["Strength Experience ", Player.strength_exp_mult, Player.strength_exp_mult * mults.strength_exp_mult, 1], ["Strength Experience ", Player.strength_exp_mult, Player.strength_exp_mult * mults.strength_exp_mult, 1],
]}
/>
<br />
<MultiplierTable
rows={[
[ [
"Defense Level ", "Defense Level ",
Player.defense_mult, Player.defense_mult,
@ -169,30 +131,13 @@ export function PlayerMultipliers(): React.ReactElement {
BitNodeMultipliers.DefenseLevelMultiplier, BitNodeMultipliers.DefenseLevelMultiplier,
], ],
["Defense Experience ", Player.defense_exp_mult, Player.defense_exp_mult * mults.defense_exp_mult, 1], ["Defense Experience ", Player.defense_exp_mult, Player.defense_exp_mult * mults.defense_exp_mult, 1],
]}
/>
<br />
<MultiplierTable
rows={[
[ [
"Dexterity Level ", "Dexterity Level ",
Player.dexterity_mult, Player.dexterity_mult,
Player.dexterity_mult * mults.dexterity_mult, Player.dexterity_mult * mults.dexterity_mult,
BitNodeMultipliers.DexterityLevelMultiplier, BitNodeMultipliers.DexterityLevelMultiplier,
], ],
[ ["Dexterity Experience ", Player.dexterity_exp_mult, Player.dexterity_exp_mult * mults.dexterity_exp_mult, 1],
"Dexterity Experience ",
Player.dexterity_exp_mult,
Player.dexterity_exp_mult * mults.dexterity_exp_mult,
1,
],
]}
/>
<br />
<MultiplierTable
rows={[
[ [
"Agility Level ", "Agility Level ",
Player.agility_mult, Player.agility_mult,
@ -200,25 +145,24 @@ export function PlayerMultipliers(): React.ReactElement {
BitNodeMultipliers.AgilityLevelMultiplier, BitNodeMultipliers.AgilityLevelMultiplier,
], ],
["Agility Experience ", Player.agility_exp_mult, Player.agility_exp_mult * mults.agility_exp_mult, 1], ["Agility Experience ", Player.agility_exp_mult, Player.agility_exp_mult * mults.agility_exp_mult, 1],
]} ].map((data): MultiplierListItemData => (data as any).concat([Settings.theme.combat])),
/>
<br />
<MultiplierTable
rows={[
[ [
"Charisma Level ", "Charisma Level ",
Player.charisma_mult, Player.charisma_mult,
Player.charisma_mult * mults.charisma_mult, Player.charisma_mult * mults.charisma_mult,
BitNodeMultipliers.CharismaLevelMultiplier, BitNodeMultipliers.CharismaLevelMultiplier,
Settings.theme.cha,
], ],
["Charisma Experience ", Player.charisma_exp_mult, Player.charisma_exp_mult * mults.charisma_exp_mult, 1], [
]} "Charisma Experience ",
/> Player.charisma_exp_mult,
<br /> Player.charisma_exp_mult * mults.charisma_exp_mult,
1,
<MultiplierTable Settings.theme.cha,
rows={[ ],
];
const rightColData: MultiplierListItemData[] = [
...[
[ [
"Hacknet Node production ", "Hacknet Node production ",
Player.hacknet_node_money_mult, Player.hacknet_node_money_mult,
@ -249,12 +193,6 @@ export function PlayerMultipliers(): React.ReactElement {
Player.hacknet_node_level_cost_mult * mults.hacknet_node_level_cost_mult, Player.hacknet_node_level_cost_mult * mults.hacknet_node_level_cost_mult,
1, 1,
], ],
]}
/>
<br />
<MultiplierTable
rows={[
["Company reputation gain ", Player.company_rep_mult, Player.company_rep_mult * mults.company_rep_mult, 1], ["Company reputation gain ", Player.company_rep_mult, Player.company_rep_mult * mults.company_rep_mult, 1],
[ [
"Faction reputation gain ", "Faction reputation gain ",
@ -262,31 +200,76 @@ export function PlayerMultipliers(): React.ReactElement {
Player.faction_rep_mult * mults.faction_rep_mult, Player.faction_rep_mult * mults.faction_rep_mult,
BitNodeMultipliers.FactionWorkRepGain, BitNodeMultipliers.FactionWorkRepGain,
], ],
].map((data): MultiplierListItemData => (data as any).concat([Settings.theme.primary])),
[ [
"Salary ", "Salary ",
Player.work_money_mult, Player.work_money_mult,
Player.work_money_mult * mults.work_money_mult, Player.work_money_mult * mults.work_money_mult,
BitNodeMultipliers.CompanyWorkMoney, BitNodeMultipliers.CompanyWorkMoney,
Settings.theme.money,
],
[
"Crime success ",
Player.crime_success_mult,
Player.crime_success_mult * mults.crime_success_mult,
1,
Settings.theme.combat,
], ],
]}
/>
<br />
<MultiplierTable
rows={[
["Crime success ", Player.crime_success_mult, Player.crime_success_mult * mults.crime_success_mult, 1],
[ [
"Crime money ", "Crime money ",
Player.crime_money_mult, Player.crime_money_mult,
Player.crime_money_mult * mults.crime_money_mult, Player.crime_money_mult * mults.crime_money_mult,
BitNodeMultipliers.CrimeMoney, BitNodeMultipliers.CrimeMoney,
Settings.theme.money,
], ],
]} ];
/>
<br />
<BladeburnerMults /> if (Player.canAccessBladeburner()) {
</Box> rightColData.push(
</> ...[
[
"Bladeburner Success Chance",
Player.bladeburner_success_chance_mult,
Player.bladeburner_success_chance_mult * mults.bladeburner_success_chance_mult,
1,
],
[
"Bladeburner Max Stamina",
Player.bladeburner_max_stamina_mult,
Player.bladeburner_max_stamina_mult * mults.bladeburner_max_stamina_mult,
1,
],
[
"Bladeburner Stamina Gain",
Player.bladeburner_stamina_gain_mult,
Player.bladeburner_stamina_gain_mult * mults.bladeburner_stamina_gain_mult,
1,
],
[
"Bladeburner Field Analysis",
Player.bladeburner_analysis_mult,
Player.bladeburner_analysis_mult * mults.bladeburner_analysis_mult,
1,
],
].map((data): MultiplierListItemData => (data as any).concat([Settings.theme.primary])),
);
}
const hasLeftImprovements = +!!(leftColData.filter((item) => item[2] !== 0).length > 0),
hasRightImprovements = +!!(rightColData.filter((item) => item[2] !== 0).length > 0);
return (
<Paper
sx={{
p: 1,
maxHeight: 400,
overflowY: "scroll",
display: "grid",
gridTemplateColumns: `repeat(${hasLeftImprovements + hasRightImprovements}, 1fr)`,
}}
>
<MultiplierList rows={leftColData} />
<MultiplierList rows={rightColData} />
</Paper>
); );
} }

@ -2,14 +2,11 @@
* React component for displaying all of the player's purchased (but not installed) * React component for displaying all of the player's purchased (but not installed)
* Augmentations on the Augmentations UI. * Augmentations on the Augmentations UI.
*/ */
import { List, ListItemText, Paper, Tooltip, Typography } from "@mui/material";
import * as React from "react"; import * as React from "react";
import { Player } from "../../Player";
import { Augmentations } from "../Augmentations"; import { Augmentations } from "../Augmentations";
import { AugmentationNames } from "../data/AugmentationNames"; import { AugmentationNames } from "../data/AugmentationNames";
import { Player } from "../../Player";
import { AugmentationAccordion } from "../../ui/React/AugmentationAccordion";
import List from "@mui/material/List";
export function PurchasedAugmentations(): React.ReactElement { export function PurchasedAugmentations(): React.ReactElement {
const augs: React.ReactElement[] = []; const augs: React.ReactElement[] = [];
@ -23,14 +20,48 @@ export function PurchasedAugmentations(): React.ReactElement {
} }
for (let i = 0; i < Player.queuedAugmentations.length; i++) { for (let i = 0; i < Player.queuedAugmentations.length; i++) {
const ownedAug = Player.queuedAugmentations[i]; const ownedAug = Player.queuedAugmentations[i];
let displayName = ownedAug.name;
if (ownedAug.name === AugmentationNames.NeuroFluxGovernor && i !== nfgIndex) continue; if (ownedAug.name === AugmentationNames.NeuroFluxGovernor && i !== nfgIndex) continue;
const aug = Augmentations[ownedAug.name]; const aug = Augmentations[ownedAug.name];
let level = null; let level = null;
if (ownedAug.name === AugmentationNames.NeuroFluxGovernor) { if (ownedAug.name === AugmentationNames.NeuroFluxGovernor) {
level = ownedAug.level; level = ownedAug.level;
} displayName += ` - Level ${level}`;
augs.push(<AugmentationAccordion key={aug.name} aug={aug} level={level} />);
} }
return <List dense>{augs}</List>; augs.push(
<Tooltip
title={
<Typography>
{(() => {
const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info;
const tooltip = (
<>
{info}
<br />
<br />
{aug.stats}
</>
);
return tooltip;
})()}
</Typography>
}
enterNextDelay={500}
key={displayName}
>
<ListItemText sx={{ px: 2, py: 1 }} primary={displayName} />
</Tooltip>,
);
}
return (
<Paper sx={{ py: 1, maxHeight: 400, overflowY: "scroll" }}>
<List sx={{ height: 400, overflowY: "scroll" }} disablePadding>
{augs}
</List>
</Paper>
);
} }

@ -1,21 +1,159 @@
import React from "react"; import { ListItemButton, ListItemText, Paper } from "@mui/material";
import { SourceFileMinus1 } from "./SourceFileMinus1";
import { OwnedSourceFiles } from "./OwnedSourceFiles";
import List from "@mui/material/List";
import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import List from "@mui/material/List";
import Typography from "@mui/material/Typography";
import React, { useState } from "react";
import { Exploit, ExploitName } from "../../Exploits/Exploit";
import { Player } from "../../Player";
import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums";
import { Settings } from "../../Settings/Settings";
import { SourceFile } from "../../SourceFile/SourceFile";
import { SourceFiles } from "../../SourceFile/SourceFiles";
export function SourceFiles(): React.ReactElement { interface SfMinus1 {
return ( info: React.ReactElement;
n: number;
name: string;
lvl: number;
}
const safeGetSf = (sfNum: number): SourceFile | SfMinus1 | null => {
if (sfNum === -1) {
const sfMinus1: SfMinus1 = {
info: (
<> <>
<Typography variant="h4">Source Files</Typography> This Source-File can only be acquired with obscure knowledge of the game, javascript, and the web ecosystem.
<Box mx={2}> <br />
<List dense> <br />
<SourceFileMinus1 /> It increases all of the player's multipliers by 0.1%
<OwnedSourceFiles /> <br />
<br />
You have found the following exploits:
<br />
<br />
{Player.exploits.map((c: Exploit) => (
<React.Fragment key={c}>
* {ExploitName(c)}
<br />
</React.Fragment>
))}
</>
),
lvl: Player.exploits.length,
n: -1,
name: "Source-File -1: Exploits in the BitNodes",
};
return sfMinus1;
}
const srcFileKey = "SourceFile" + sfNum;
const sfObj = SourceFiles[srcFileKey];
if (sfObj == null) {
console.error(`Invalid source file number: ${sfNum}`);
return null;
}
return sfObj;
};
const getMaxLevel = (sfObj: SourceFile | SfMinus1): string | number => {
let maxLevel;
switch (sfObj.n) {
case 12:
maxLevel = "∞";
break;
case -1:
maxLevel = Object.keys(Exploit).length;
break;
default:
maxLevel = "3";
}
return maxLevel;
};
export function SourceFilesElement(): React.ReactElement {
const sourceSfs = Player.sourceFiles.slice();
const exploits = Player.exploits;
// Create a fake SF for -1, if "owned"
if (exploits.length > 0) {
sourceSfs.unshift({
n: -1,
lvl: exploits.length,
});
}
if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) {
sourceSfs.sort((sf1, sf2) => {
return sf1.n - sf2.n;
});
}
if (sourceSfs.length === 0) {
return <></>;
}
const [selectedSf, setSelectedSf] = useState(sourceSfs[0]);
return (
<Box sx={{ width: "100%", mt: 1 }}>
<Paper sx={{ p: 1 }}>
<Typography variant="h5">Source Files</Typography>
</Paper>
<Paper sx={{ display: "grid", gridTemplateColumns: "1fr 3fr" }}>
<Box>
<List
sx={{ height: 400, overflowY: "scroll", borderRight: `1px solid ${Settings.theme.welllight}` }}
disablePadding
>
{sourceSfs.map((e, i) => {
const sfObj = safeGetSf(e.n);
if (!sfObj) return;
const maxLevel = getMaxLevel(sfObj);
return (
<ListItemButton
key={i + 1}
onClick={() => setSelectedSf(e)}
selected={selectedSf.n === e.n}
sx={{ py: 0 }}
>
<ListItemText
disableTypography
primary={<Typography>{sfObj.name}</Typography>}
secondary={
<Typography>
Level {e.lvl} / {maxLevel}
</Typography>
}
/>
</ListItemButton>
);
})}
</List> </List>
</Box> </Box>
<Box sx={{ m: 1 }}>
<Typography variant="h6" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
{safeGetSf(selectedSf.n)?.name}
</Typography>
<Typography sx={{ maxHeight: 350, overflowY: "scroll" }}>
{(() => {
const sfObj = safeGetSf(selectedSf.n);
if (!sfObj) return;
const maxLevel = getMaxLevel(sfObj);
return (
<>
Level {selectedSf.lvl} / {maxLevel}
<br />
<br />
{sfObj.info}
</> </>
); );
})()}
</Typography>
</Box>
</Paper>
</Box>
);
} }

@ -29,6 +29,16 @@ export class OfficeSpace {
[EmployeePositions.RandD]: 0, [EmployeePositions.RandD]: 0,
total: 0, total: 0,
}; };
employeeJobs: { [key: string]: number } = {
[EmployeePositions.Operations]: 0,
[EmployeePositions.Engineer]: 0,
[EmployeePositions.Business]: 0,
[EmployeePositions.Management]: 0,
[EmployeePositions.RandD]: 0,
[EmployeePositions.Training]: 0,
[EmployeePositions.Unassigned]: 0,
total: 0,
};
constructor(params: IParams = {}) { constructor(params: IParams = {}) {
this.loc = params.loc ? params.loc : ""; this.loc = params.loc ? params.loc : "";
@ -48,6 +58,8 @@ export class OfficeSpace {
} }
} }
this.calculateTotalEmployees();
// Process Office properties // Process Office properties
this.maxEne = 100; this.maxEne = 100;
this.maxHap = 100; this.maxHap = 100;
@ -101,6 +113,19 @@ export class OfficeSpace {
return salaryPaid; return salaryPaid;
} }
calculateTotalEmployees(): void {
//Reset
for (const name of Object.keys(this.employeeJobs)) {
this.employeeJobs[name] = 0;
}
for (let i = 0; i < this.employees.length; ++i) {
const employee = this.employees[i];
this.employeeJobs[employee.pos]++;
}
this.employeeJobs.total = this.employees.length;
}
calculateEmployeeProductivity(corporation: ICorporation, industry: IIndustry): void { calculateEmployeeProductivity(corporation: ICorporation, industry: IIndustry): void {
//Reset //Reset
for (const name of Object.keys(this.employeeProd)) { for (const name of Object.keys(this.employeeProd)) {

@ -1,19 +1,18 @@
import { Clear, ExpandMore, Reply, ReplyAll } from "@mui/icons-material";
import {
Accordion,
AccordionDetails,
AccordionSummary,
Button,
IconButton,
MenuItem,
Select,
SelectChangeEvent,
Typography,
} from "@mui/material";
import React, { useState } from "react"; import React, { useState } from "react";
import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
import AccordionDetails from "@mui/material/AccordionDetails";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import Typography from "@mui/material/Typography"; import { IPlayer } from "../../PersonObjects/IPlayer";
import MenuItem from "@mui/material/MenuItem";
import IconButton from "@mui/material/IconButton";
import ReplyAllIcon from "@mui/icons-material/ReplyAll";
import ReplyIcon from "@mui/icons-material/Reply";
import ClearIcon from "@mui/icons-material/Clear";
interface IProps { interface IProps {
player: IPlayer; player: IPlayer;
@ -39,36 +38,33 @@ export function Augmentations(props: IProps): React.ReactElement {
props.player.augmentations = []; props.player.augmentations = [];
} }
function clearQueuedAugs(): void {
props.player.queuedAugmentations = [];
}
return ( return (
<Accordion TransitionProps={{ unmountOnExit: true }}> <Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}> <AccordionSummary expandIcon={<ExpandMore />}>
<Typography>Augmentations</Typography> <Typography>Augmentations</Typography>
</AccordionSummary> </AccordionSummary>
<AccordionDetails> <AccordionDetails>
<table>
<tbody>
<tr>
<td>
<Typography>Aug:</Typography>
</td>
<td>
<Select <Select
onChange={setAugmentationDropdown} onChange={setAugmentationDropdown}
value={augmentation} value={augmentation}
startAdornment={ startAdornment={
<> <>
<IconButton onClick={queueAllAugs} size="large"> <IconButton onClick={queueAllAugs} size="large">
<ReplyAllIcon /> <ReplyAll />
</IconButton> </IconButton>
<IconButton onClick={queueAug} size="large"> <IconButton onClick={queueAug} size="large">
<ReplyIcon /> <Reply />
</IconButton> </IconButton>
</> </>
} }
endAdornment={ endAdornment={
<> <>
<IconButton onClick={clearAugs} size="large"> <IconButton onClick={clearAugs} size="large">
<ClearIcon /> <Clear />
</IconButton> </IconButton>
</> </>
} }
@ -79,10 +75,9 @@ export function Augmentations(props: IProps): React.ReactElement {
</MenuItem> </MenuItem>
))} ))}
</Select> </Select>
</td> <Button sx={{ display: "block" }} onClick={clearQueuedAugs}>
</tr> Clear Queued Augmentations
</tbody> </Button>
</table>
</AccordionDetails> </AccordionDetails>
</Accordion> </Accordion>
); );

@ -81,7 +81,6 @@ export function hasAugmentationPrereqs(aug: Augmentation): boolean {
} }
export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = false): string { export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = false): string {
const factionInfo = fac.getInfo();
const hasPrereqs = hasAugmentationPrereqs(aug); const hasPrereqs = hasAugmentationPrereqs(aug);
if (!hasPrereqs) { if (!hasPrereqs) {
const txt = `You must first purchase or install ${aug.prereqs.join(",")} before you can purchase this one.`; const txt = `You must first purchase or install ${aug.prereqs.join(",")} before you can purchase this one.`;
@ -90,7 +89,7 @@ export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = fal
} else { } else {
dialogBoxCreate(txt); dialogBoxCreate(txt);
} }
} else if (aug.baseCost !== 0 && Player.money < aug.baseCost * factionInfo.augmentationPriceMult) { } else if (aug.baseCost !== 0 && Player.money < aug.baseCost) {
const txt = "You don't have enough money to purchase " + aug.name; const txt = "You don't have enough money to purchase " + aug.name;
if (sing) { if (sing) {
return txt; return txt;
@ -102,14 +101,14 @@ export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = fal
return txt; return txt;
} }
dialogBoxCreate(txt); dialogBoxCreate(txt);
} else if (aug.baseCost === 0 || Player.money >= aug.baseCost * factionInfo.augmentationPriceMult) { } else if (aug.baseCost === 0 || Player.money >= aug.baseCost) {
const queuedAugmentation = new PlayerOwnedAugmentation(aug.name); const queuedAugmentation = new PlayerOwnedAugmentation(aug.name);
if (aug.name == AugmentationNames.NeuroFluxGovernor) { if (aug.name == AugmentationNames.NeuroFluxGovernor) {
queuedAugmentation.level = getNextNeurofluxLevel(); queuedAugmentation.level = getNextNeurofluxLevel();
} }
Player.queuedAugmentations.push(queuedAugmentation); Player.queuedAugmentations.push(queuedAugmentation);
Player.loseMoney(aug.baseCost * factionInfo.augmentationPriceMult, "augmentations"); Player.loseMoney(aug.baseCost, "augmentations");
// If you just purchased Neuroflux Governor, recalculate the cost // If you just purchased Neuroflux Governor, recalculate the cost
if (aug.name == AugmentationNames.NeuroFluxGovernor) { if (aug.name == AugmentationNames.NeuroFluxGovernor) {

@ -2,20 +2,20 @@ import React from "react";
import { IMap } from "../types"; import { IMap } from "../types";
import { FactionNames } from "./data/FactionNames"; import { FactionNames } from "./data/FactionNames";
interface FactionInfoParams {
infoText?: JSX.Element;
enemies?: string[];
offerHackingWork?: boolean;
offerFieldWork?: boolean;
offerSecurityWork?: boolean;
special?: boolean;
keepOnInstall?: boolean;
}
/** /**
* Contains the "information" property for all the Factions, which is just a description of each faction * Contains the "information" property for all the Factions, which is just a description of each faction
*/ */
export class FactionInfo { export class FactionInfo {
/**
* The multiplier to apply to augmentation base purchase price.
*/
augmentationPriceMult: number;
/**
* The multiplier to apply to augmentation reputation base requirement.
*/
augmentationRepRequirementMult: number;
/** /**
* The names of all other factions considered to be enemies to this faction. * The names of all other factions considered to be enemies to this faction.
*/ */
@ -31,11 +31,6 @@ export class FactionInfo {
*/ */
offerFieldWork: boolean; offerFieldWork: boolean;
/**
* A flag indicating if the faction supports hacking missions to earn reputation.
*/
offerHackingMission: boolean;
/** /**
* A flag indicating if the faction supports hacking work to earn reputation. * A flag indicating if the faction supports hacking work to earn reputation.
*/ */
@ -56,32 +51,19 @@ export class FactionInfo {
*/ */
special: boolean; special: boolean;
constructor( constructor(params: FactionInfoParams) {
infoText: JSX.Element, this.infoText = params.infoText ?? <></>;
enemies: string[], this.enemies = params.enemies ?? [];
offerHackingMission: boolean, this.offerHackingWork = params.offerHackingWork ?? false;
offerHackingWork: boolean, this.offerFieldWork = params.offerFieldWork ?? false;
offerFieldWork: boolean, this.offerSecurityWork = params.offerSecurityWork ?? false;
offerSecurityWork: boolean,
special: boolean,
keep: boolean,
) {
this.infoText = infoText;
this.enemies = enemies;
this.offerHackingMission = offerHackingMission;
this.offerHackingWork = offerHackingWork;
this.offerFieldWork = offerFieldWork;
this.offerSecurityWork = offerSecurityWork;
// These are always all 1 for now. this.keep = params.keepOnInstall ?? false;
this.augmentationPriceMult = 1; this.special = params.special ?? false;
this.augmentationRepRequirementMult = 1;
this.keep = keep;
this.special = special;
} }
offersWork(): boolean { offersWork(): boolean {
return this.offerFieldWork || this.offerHackingMission || this.offerHackingWork || this.offerSecurityWork; return this.offerFieldWork || this.offerHackingWork || this.offerSecurityWork;
} }
} }
@ -91,35 +73,25 @@ export class FactionInfo {
// tslint:disable-next-line:variable-name // tslint:disable-next-line:variable-name
export const FactionInfos: IMap<FactionInfo> = { export const FactionInfos: IMap<FactionInfo> = {
// Endgame // Endgame
[FactionNames.Illuminati]: new FactionInfo( [FactionNames.Illuminati]: new FactionInfo({
( infoText: (
<> <>
Humanity never changes. No matter how civilized society becomes, it will eventually fall back into chaos. And Humanity never changes. No matter how civilized society becomes, it will eventually fall back into chaos. And
from this chaos, we are the invisible hand that guides them to order.{" "} from this chaos, we are the invisible hand that guides them to order.{" "}
</> </>
), ),
[], offerHackingWork: true,
true, offerFieldWork: true,
true, }),
true,
false,
false,
false,
),
[FactionNames.Daedalus]: new FactionInfo( [FactionNames.Daedalus]: new FactionInfo({
<>Yesterday we obeyed kings and bent our necks to emperors. Today we kneel only to truth.</>, infoText: <>Yesterday we obeyed kings and bent our necks to emperors. Today we kneel only to truth.</>,
[], offerHackingWork: true,
true, offerFieldWork: true,
true, }),
true,
false,
false,
false,
),
[FactionNames.TheCovenant]: new FactionInfo( [FactionNames.TheCovenant]: new FactionInfo({
( infoText: (
<> <>
Surrender yourself. Give up your empty individuality to become part of something great, something eternal. Surrender yourself. Give up your empty individuality to become part of something great, something eternal.
Become a slave. Submit your mind, body, and soul. Only then can you set yourself free. Become a slave. Submit your mind, body, and soul. Only then can you set yourself free.
@ -128,35 +100,27 @@ export const FactionInfos: IMap<FactionInfo> = {
Only then can you discover immortality. Only then can you discover immortality.
</> </>
), ),
[], offerHackingWork: true,
true, offerFieldWork: true,
true, }),
true,
false,
false,
false,
),
// Megacorporations, each forms its own faction // Megacorporations, each forms its own faction
[FactionNames.ECorp]: new FactionInfo( [FactionNames.ECorp]: new FactionInfo({
( infoText: (
<> <>
{FactionNames.ECorp}'s mission is simple: to connect the world of today with the technology of tomorrow. With {FactionNames.ECorp}'s mission is simple: to connect the world of today with the technology of tomorrow. With
our wide range of Internet-related software and commercial hardware, {FactionNames.ECorp} makes the world's our wide range of Internet-related software and commercial hardware, {FactionNames.ECorp} makes the world's
information universally accessible. information universally accessible.
</> </>
), ),
[], offerHackingWork: true,
true, offerFieldWork: true,
true, offerSecurityWork: true,
true, keepOnInstall: true,
true, }),
false,
true,
),
[FactionNames.MegaCorp]: new FactionInfo( [FactionNames.MegaCorp]: new FactionInfo({
( infoText: (
<> <>
{FactionNames.MegaCorp} does what no other dares to do. We imagine. We create. We invent. We create what others {FactionNames.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 have never even dreamed of. Our work fills the world's needs for food, water, power, and transportation on an
@ -167,17 +131,14 @@ export const FactionInfos: IMap<FactionInfo> = {
the world. the world.
</> </>
), ),
[], offerHackingWork: true,
true, offerFieldWork: true,
true, offerSecurityWork: true,
true, keepOnInstall: true,
true, }),
false,
true,
),
[FactionNames.BachmanAssociates]: new FactionInfo( [FactionNames.BachmanAssociates]: new FactionInfo({
( infoText: (
<> <>
Where Law and Business meet - thats where we are. Where Law and Business meet - thats where we are.
<br /> <br />
@ -185,112 +146,87 @@ export const FactionInfos: IMap<FactionInfo> = {
Legal Insight - Business Instinct - Innovative Experience. Legal Insight - Business Instinct - Innovative Experience.
</> </>
), ),
[], offerHackingWork: true,
true, offerFieldWork: true,
true, offerSecurityWork: true,
true, keepOnInstall: true,
true, }),
false,
true,
),
[FactionNames.BladeIndustries]: new FactionInfo( [FactionNames.BladeIndustries]: new FactionInfo({
<>Augmentation is Salvation.</>, infoText: <>Augmentation is Salvation.</>,
[], offerHackingWork: true,
true, offerFieldWork: true,
true, offerSecurityWork: true,
true, keepOnInstall: true,
true, }),
false,
true,
),
[FactionNames.NWO]: new FactionInfo( [FactionNames.NWO]: new FactionInfo({
( infoText: (
<> <>
Humans don't truly desire freedom. They want to be observed, understood, and judged. They want to be given Humans don't truly desire freedom. They want to be observed, understood, and judged. They want to be given
purpose and direction in life. That is why they created God. And that is why they created civilization - not purpose and direction in life. That is why they created God. And that is why they created civilization - not
because of willingness, but because of a need to be incorporated into higher orders of structure and meaning. because of willingness, but because of a need to be incorporated into higher orders of structure and meaning.
</> </>
), ),
[], offerHackingWork: true,
true, offerFieldWork: true,
true, offerSecurityWork: true,
true, keepOnInstall: true,
true, }),
false,
true,
),
[FactionNames.ClarkeIncorporated]: new FactionInfo( [FactionNames.ClarkeIncorporated]: new FactionInfo({
<>The Power of the Genome - Unlocked.</>, infoText: <>The Power of the Genome - Unlocked.</>,
[], offerHackingWork: true,
true, offerFieldWork: true,
true, offerSecurityWork: true,
true, keepOnInstall: true,
true, }),
false,
true,
),
[FactionNames.OmniTekIncorporated]: new FactionInfo( [FactionNames.OmniTekIncorporated]: new FactionInfo({
<>Simply put, our mission is to design and build robots that make a difference.</>, infoText: <>Simply put, our mission is to design and build robots that make a difference.</>,
[], offerHackingWork: true,
true, offerFieldWork: true,
true, offerSecurityWork: true,
true, keepOnInstall: true,
true, }),
false,
true,
),
[FactionNames.FourSigma]: new FactionInfo( [FactionNames.FourSigma]: new FactionInfo({
( infoText: (
<> <>
The scientific method is the best way to approach investing. Big strategies backed up with big data. Driven by The scientific method is the best way to approach investing. Big strategies backed up with big data. Driven by
deep learning and innovative ideas. And improved by iteration. That's {FactionNames.FourSigma}. deep learning and innovative ideas. And improved by iteration. That's {FactionNames.FourSigma}.
</> </>
), ),
[], offerHackingWork: true,
true, offerFieldWork: true,
true, offerSecurityWork: true,
true, keepOnInstall: true,
true, }),
false,
true,
),
[FactionNames.KuaiGongInternational]: new FactionInfo( [FactionNames.KuaiGongInternational]: new FactionInfo({
<>Dream big. Work hard. Make history.</>, infoText: <>Dream big. Work hard. Make history.</>,
[], offerHackingWork: true,
true, offerFieldWork: true,
true, offerSecurityWork: true,
true, keepOnInstall: true,
true, }),
false,
true,
),
// Other Corporations // Other Corporations
[FactionNames.FulcrumSecretTechnologies]: new FactionInfo( [FactionNames.FulcrumSecretTechnologies]: new FactionInfo({
( infoText: (
<> <>
The human organism has an innate desire to worship. That is why they created gods. If there were no gods, it The human organism has an innate desire to worship. That is why they created gods. If there were no gods, it
would be necessary to create them. And now we can. would be necessary to create them. And now we can.
</> </>
), ),
[], offerHackingWork: true,
true, offerSecurityWork: true,
true, keepOnInstall: true,
false, }),
true,
false,
true,
),
// Hacker groups // Hacker groups
[FactionNames.BitRunners]: new FactionInfo( [FactionNames.BitRunners]: new FactionInfo({
( infoText: (
<> <>
Our entire lives are controlled by bits. All of our actions, our thoughts, our personal information. It's all Our entire lives are controlled by bits. All of our actions, our thoughts, our personal information. It's all
transformed into bits, stored in bits, communicated through bits. Its impossible for any person to move, to transformed into bits, stored in bits, communicated through bits. Its impossible for any person to move, to
@ -302,17 +238,11 @@ export const FactionInfos: IMap<FactionInfo> = {
Those who run the bits, run the world. Those who run the bits, run the world.
</> </>
), ),
[], offerHackingWork: true,
true, }),
true,
false,
false,
false,
false,
),
[FactionNames.TheBlackHand]: new FactionInfo( [FactionNames.TheBlackHand]: new FactionInfo({
( infoText: (
<> <>
The world, so afraid of strong government, now has no government. Only power - Digital power. Financial power. The world, so afraid of strong government, now has no government. Only power - Digital power. Financial power.
Technological power. And those at the top rule with an invisible hand. They built a society where the rich get Technological power. And those at the top rule with an invisible hand. They built a society where the rich get
@ -322,17 +252,13 @@ export const FactionInfos: IMap<FactionInfo> = {
So much pain. So many lives. Their darkness must end. So much pain. So many lives. Their darkness must end.
</> </>
), ),
[], offerHackingWork: true,
true, offerFieldWork: true,
true, }),
true,
false,
false,
false,
),
// prettier-ignore // prettier-ignore
[FactionNames.NiteSec]: new FactionInfo(<> [FactionNames.NiteSec]: new FactionInfo({
infoText:(<>
{" __..__ "}<br /> {" __..__ "}<br />
{" _.nITESECNIt. "}<br /> {" _.nITESECNIt. "}<br />
{" .-'NITESECNITESEc. "}<br /> {" .-'NITESECNITESEc. "}<br />
@ -367,105 +293,87 @@ export const FactionInfos: IMap<FactionInfo> = {
{" / .d/$/$; , ; "}<br /> {" / .d/$/$; , ; "}<br />
{" d .dNITESEC $ | "}<br /> {" d .dNITESEC $ | "}<br />
{" :bp.__.gNITESEC/$ :$ ; "}<br /> {" :bp.__.gNITESEC/$ :$ ; "}<br />
{" NITESECNITESECNIT /$b : "}<br /></>, {" NITESECNITESECNIT /$b : "}<br /></>),
[], offerHackingWork: true,
true, offerFieldWork: false,
true, offerSecurityWork: false,
false, special: false,
false, keepOnInstall: false,
false, }),
false,
),
// City factions, essentially governments // City factions, essentially governments
[FactionNames.Aevum]: new FactionInfo( [FactionNames.Aevum]: new FactionInfo({
<>The Silicon City.</>, infoText: <>The Silicon City.</>,
[FactionNames.Chongqing, FactionNames.NewTokyo, FactionNames.Ishima, FactionNames.Volhaven], enemies: [FactionNames.Chongqing, FactionNames.NewTokyo, FactionNames.Ishima, FactionNames.Volhaven],
true, offerHackingWork: true,
true, offerFieldWork: true,
true, offerSecurityWork: true,
true, }),
false, [FactionNames.Chongqing]: new FactionInfo({
false, infoText: <>Serve the People.</>,
), enemies: [FactionNames.Sector12, FactionNames.Aevum, FactionNames.Volhaven],
[FactionNames.Chongqing]: new FactionInfo( offerHackingWork: true,
<>Serve the People.</>, offerFieldWork: true,
[FactionNames.Sector12, FactionNames.Aevum, FactionNames.Volhaven], offerSecurityWork: true,
true, }),
true, [FactionNames.Ishima]: new FactionInfo({
true, infoText: <>The East Asian Order of the Future.</>,
true, enemies: [FactionNames.Sector12, FactionNames.Aevum, FactionNames.Volhaven],
false, offerHackingWork: true,
false, offerFieldWork: true,
), offerSecurityWork: true,
[FactionNames.Ishima]: new FactionInfo( }),
<>The East Asian Order of the Future.</>, [FactionNames.NewTokyo]: new FactionInfo({
[FactionNames.Sector12, FactionNames.Aevum, FactionNames.Volhaven], infoText: <>Asia's World City.</>,
true, enemies: [FactionNames.Sector12, FactionNames.Aevum, FactionNames.Volhaven],
true, offerHackingWork: true,
true, offerFieldWork: true,
true, offerSecurityWork: true,
false, }),
false, [FactionNames.Sector12]: new FactionInfo({
), infoText: <>The City of the Future.</>,
[FactionNames.NewTokyo]: new FactionInfo( enemies: [FactionNames.Chongqing, FactionNames.NewTokyo, FactionNames.Ishima, FactionNames.Volhaven],
<>Asia's World City.</>, offerHackingWork: true,
[FactionNames.Sector12, FactionNames.Aevum, FactionNames.Volhaven], offerFieldWork: true,
true, offerSecurityWork: true,
true, }),
true, [FactionNames.Volhaven]: new FactionInfo({
true, infoText: <>Benefit, Honor, and Glory.</>,
false, enemies: [
false, FactionNames.Chongqing,
), FactionNames.Sector12,
[FactionNames.Sector12]: new FactionInfo( FactionNames.NewTokyo,
<>The City of the Future.</>, FactionNames.Aevum,
[FactionNames.Chongqing, FactionNames.NewTokyo, FactionNames.Ishima, FactionNames.Volhaven], FactionNames.Ishima,
true, ],
true, offerHackingWork: true,
true, offerFieldWork: true,
true, offerSecurityWork: true,
false, }),
false,
),
[FactionNames.Volhaven]: new FactionInfo(
<>Benefit, Honor, and Glory.</>,
[FactionNames.Chongqing, FactionNames.Sector12, FactionNames.NewTokyo, FactionNames.Aevum, FactionNames.Ishima],
true,
true,
true,
true,
false,
false,
),
// Criminal Organizations/Gangs // Criminal Organizations/Gangs
[FactionNames.SpeakersForTheDead]: new FactionInfo( [FactionNames.SpeakersForTheDead]: new FactionInfo({
<>It is better to reign in Hell than to serve in Heaven.</>, infoText: <>It is better to reign in Hell than to serve in Heaven.</>,
[], offerHackingWork: true,
true, offerFieldWork: true,
true, offerSecurityWork: true,
true, }),
true,
false,
false,
),
[FactionNames.TheDarkArmy]: new FactionInfo( [FactionNames.TheDarkArmy]: new FactionInfo({
<>The World doesn't care about right or wrong. It only cares about power.</>, infoText: <>The World doesn't care about right or wrong. It only cares about power.</>,
[], offerHackingWork: true,
true, offerFieldWork: true,
true, }),
true,
false,
false,
false,
),
[FactionNames.TheSyndicate]: new FactionInfo(<>Honor holds you back.</>, [], true, true, true, true, false, false), [FactionNames.TheSyndicate]: new FactionInfo({
infoText: <>Honor holds you back.</>,
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
}),
[FactionNames.Silhouette]: new FactionInfo( [FactionNames.Silhouette]: new FactionInfo({
( infoText: (
<> <>
Corporations have filled the void of power left behind by the collapse of Western government. The issue is Corporations have filled the void of power left behind by the collapse of Western government. The issue is
they've become so big that you don't know who they're working for. And if you're employed at one of these they've become so big that you don't know who they're working for. And if you're employed at one of these
@ -475,100 +383,66 @@ export const FactionInfos: IMap<FactionInfo> = {
That's terror. Terror, fear, and corruption. All born into the system, all propagated by the system. That's terror. Terror, fear, and corruption. All born into the system, all propagated by the system.
</> </>
), ),
[], offerHackingWork: true,
true, offerFieldWork: true,
true, }),
true,
false,
false,
false,
),
[FactionNames.Tetrads]: new FactionInfo( [FactionNames.Tetrads]: new FactionInfo({
<>Following the mandate of Heaven and carrying out the way.</>, infoText: <>Following the mandate of Heaven and carrying out the way.</>,
[],
false,
false,
true,
true,
false,
false,
),
[FactionNames.SlumSnakes]: new FactionInfo( offerFieldWork: true,
<>{FactionNames.SlumSnakes} rule!</>, offerSecurityWork: true,
[], }),
false,
false, [FactionNames.SlumSnakes]: new FactionInfo({
true, infoText: <>{FactionNames.SlumSnakes} rule!</>,
true,
false, offerFieldWork: true,
false, offerSecurityWork: true,
), }),
// Earlygame factions - factions the player will prestige with early on that don't belong in other categories. // Earlygame factions - factions the player will prestige with early on that don't belong in other categories.
[FactionNames.Netburners]: new FactionInfo( [FactionNames.Netburners]: new FactionInfo({
<>{"~~//*>H4CK||3T 8URN3R5**>?>\\~~"}</>, infoText: <>{"~~//*>H4CK||3T 8URN3R5**>?>\\~~"}</>,
[], offerHackingWork: true,
true, }),
true,
false,
false,
false,
false,
),
[FactionNames.TianDiHui]: new FactionInfo( [FactionNames.TianDiHui]: new FactionInfo({
<>Obey Heaven and work righteously.</>, infoText: <>Obey Heaven and work righteously.</>,
[], offerHackingWork: true,
true,
true,
false,
true,
false,
false,
),
[FactionNames.CyberSec]: new FactionInfo( offerSecurityWork: true,
( }),
[FactionNames.CyberSec]: new FactionInfo({
infoText: (
<> <>
The Internet is the first thing that was built that we don't fully understand, the largest experiment in anarchy The Internet is the first thing that was built that we don't fully understand, the largest experiment in anarchy
that we have ever had. And as the world becomes increasingly dominated by it, society approaches the brink of that we have ever had. And as the world becomes increasingly dominated by it, society approaches the brink of
total chaos. We serve only to protect society, to protect humanity, to protect the world from imminent collapse. total chaos. We serve only to protect society, to protect humanity, to protect the world from imminent collapse.
</> </>
), ),
[], offerHackingWork: true,
true, }),
true,
false,
false,
false,
false,
),
// Special Factions // Special Factions
[FactionNames.Bladeburners]: new FactionInfo( [FactionNames.Bladeburners]: new FactionInfo({
( infoText: (
<> <>
It's too bad they won't live. But then again, who does? It's too bad they won't live. But then again, who does?
<br /> <br />
<br /> <br />
Note that for this faction, reputation can only be gained through {FactionNames.Bladeburners} actions. Note that for this faction, reputation can only be gained through {FactionNames.Bladeburners} actions.{" "}
Completing {FactionNames.Bladeburners} Completing {FactionNames.Bladeburners} contracts/operations will increase your reputation.
contracts/operations will increase your reputation.
</> </>
), ),
[],
false, special: true,
false, }),
false,
false,
true,
false,
),
// prettier-ignore // prettier-ignore
[FactionNames.ChurchOfTheMachineGod]: new FactionInfo(<> [FactionNames.ChurchOfTheMachineGod]: new FactionInfo({
infoText:(<>
{" `` "}<br /> {" `` "}<br />
{" -odmmNmds: "}<br /> {" -odmmNmds: "}<br />
{" `hNmo:..-omNh. "}<br /> {" `hNmo:..-omNh. "}<br />
@ -600,13 +474,11 @@ export const FactionInfos: IMap<FactionInfo> = {
Many cultures predict an end to humanity in the near future, a final Many cultures predict an end to humanity in the near future, a final
Armageddon that will end the world; but we disagree. Armageddon that will end the world; but we disagree.
<br /><br />Note that for this faction, reputation can <br /><br />Note that for this faction, reputation can
only be gained by charging Stanek's gift.</>, only be gained by charging Stanek's gift.</>),
[], offerHackingWork: false,
false, offerFieldWork: false,
false, offerSecurityWork: false,
false, special: true,
false, keepOnInstall: true,
true, }),
true,
),
}; };

@ -78,10 +78,10 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
const augs = getAugs(); const augs = getAugs();
function canBuy(augName: string): boolean { function canBuy(augName: string): boolean {
const aug = Augmentations[augName]; const aug = Augmentations[augName];
const repCost = aug.baseRepRequirement * props.faction.getInfo().augmentationRepRequirementMult; const repCost = aug.baseRepRequirement;
const hasReq = props.faction.playerReputation >= repCost; const hasReq = props.faction.playerReputation >= repCost;
const hasRep = hasAugmentationPrereqs(aug); const hasRep = hasAugmentationPrereqs(aug);
const hasCost = aug.baseCost !== 0 && player.money > aug.baseCost * props.faction.getInfo().augmentationPriceMult; const hasCost = aug.baseCost !== 0 && player.money > aug.baseCost;
return hasCost && hasReq && hasRep; return hasCost && hasReq && hasRep;
} }
const buy = augs.filter(canBuy).sort((augName1, augName2) => { const buy = augs.filter(canBuy).sort((augName1, augName2) => {

@ -20,7 +20,6 @@ interface IProps {
export function PurchaseAugmentationModal(props: IProps): React.ReactElement { export function PurchaseAugmentationModal(props: IProps): React.ReactElement {
const player = use.Player(); const player = use.Player();
const factionInfo = props.faction.getInfo();
function buy(): void { function buy(): void {
if (!isRepeatableAug(props.aug) && player.hasAugmentation(props.aug)) { if (!isRepeatableAug(props.aug) && player.hasAugmentation(props.aug)) {
@ -43,7 +42,7 @@ export function PurchaseAugmentationModal(props: IProps): React.ReactElement {
<br /> <br />
<br /> <br />
Would you like to purchase the {props.aug.name} Augmentation for&nbsp; Would you like to purchase the {props.aug.name} Augmentation for&nbsp;
<Money money={props.aug.baseCost * factionInfo.augmentationPriceMult} />? <Money money={props.aug.baseCost} />?
<br /> <br />
<br /> <br />
</Typography> </Typography>

@ -84,11 +84,11 @@ export function PurchaseableAugmentation(props: IProps): React.ReactElement {
return <></>; return <></>;
} }
const moneyCost = aug.baseCost * props.faction.getInfo().augmentationPriceMult; const moneyCost = aug.baseCost;
const repCost = aug.baseRepRequirement * props.faction.getInfo().augmentationRepRequirementMult; const repCost = aug.baseRepRequirement;
const hasReq = hasAugmentationPrereqs(aug); const hasReq = hasAugmentationPrereqs(aug);
const hasRep = props.faction.playerReputation >= repCost; const hasRep = props.faction.playerReputation >= repCost;
const hasCost = aug.baseCost === 0 || props.p.money > aug.baseCost * props.faction.getInfo().augmentationPriceMult; const hasCost = aug.baseCost === 0 || props.p.money > aug.baseCost;
// Determine UI properties // Determine UI properties
const color: "error" | "primary" = !hasReq || !hasRep || !hasCost ? "error" : "primary"; const color: "error" | "primary" = !hasReq || !hasRep || !hasCost ? "error" : "primary";

@ -174,10 +174,11 @@ export function SpecialLocation(props: IProps): React.ReactElement {
applyAugmentation({ name: AugmentationNames.StaneksGift1, level: 1 }); applyAugmentation({ name: AugmentationNames.StaneksGift1, level: 1 });
} }
router.toFaction(faction); router.toStaneksGift();
} }
function renderCotMG(): React.ReactElement { function renderCotMG(): React.ReactElement {
const toStanek = <Button onClick={() => router.toStaneksGift()}>Open Stanek's Gift</Button>;
// prettier-ignore // prettier-ignore
const symbol = <Typography sx={{ lineHeight: '1em', whiteSpace: 'pre' }}> const symbol = <Typography sx={{ lineHeight: '1em', whiteSpace: 'pre' }}>
{" `` "}<br /> {" `` "}<br />
@ -218,6 +219,9 @@ export function SpecialLocation(props: IProps): React.ReactElement {
seems. Curious, Just how much of a machine's soul do you house in that body? seems. Curious, Just how much of a machine's soul do you house in that body?
</i> </i>
</Typography> </Typography>
<br />
{toStanek}
<br />
{symbol} {symbol}
</> </>
); );
@ -232,6 +236,9 @@ export function SpecialLocation(props: IProps): React.ReactElement {
mastery of the gift clearly demonstrates that. My hopes are climbing by the day for you. mastery of the gift clearly demonstrates that. My hopes are climbing by the day for you.
</i> </i>
</Typography> </Typography>
<br />
{toStanek}
<br />
{symbol} {symbol}
</> </>
); );
@ -242,6 +249,9 @@ export function SpecialLocation(props: IProps): React.ReactElement {
<Typography> <Typography>
<i>Allison "Mother" Stanek: Welcome back my child!</i> <i>Allison "Mother" Stanek: Welcome back my child!</i>
</Typography> </Typography>
<br />
{toStanek}
<br />
{symbol} {symbol}
</> </>
); );

@ -15,6 +15,7 @@ export const RamCostConstants: IMap<number> = {
ScriptWeakenRamCost: 0.15, ScriptWeakenRamCost: 0.15,
ScriptWeakenAnalyzeRamCost: 1, ScriptWeakenAnalyzeRamCost: 1,
ScriptScanRamCost: 0.2, ScriptScanRamCost: 0.2,
ScriptRecentScriptsRamCost: 0.2,
ScriptPortProgramRamCost: 0.05, ScriptPortProgramRamCost: 0.05,
ScriptRunRamCost: 1.0, ScriptRunRamCost: 1.0,
ScriptExecRamCost: 1.3, ScriptExecRamCost: 1.3,
@ -140,6 +141,7 @@ export const RamCosts: IMap<any> = {
scp: RamCostConstants.ScriptScpRamCost, scp: RamCostConstants.ScriptScpRamCost,
ls: RamCostConstants.ScriptScanRamCost, ls: RamCostConstants.ScriptScanRamCost,
ps: RamCostConstants.ScriptScanRamCost, ps: RamCostConstants.ScriptScanRamCost,
getRecentScripts: RamCostConstants.ScriptRecentScriptsRamCost,
hasRootAccess: RamCostConstants.ScriptHasRootAccessRamCost, hasRootAccess: RamCostConstants.ScriptHasRootAccessRamCost,
getIp: RamCostConstants.ScriptGetHostnameRamCost, getIp: RamCostConstants.ScriptGetHostnameRamCost,
getHostname: RamCostConstants.ScriptGetHostnameRamCost, getHostname: RamCostConstants.ScriptGetHostnameRamCost,

@ -1,27 +1,24 @@
import { RunningScript } from "src/Script/RunningScript"; import { RunningScript } from "src/Script/RunningScript";
import { Settings } from "../Settings/Settings";
import { WorkerScript } from "./WorkerScript"; import { WorkerScript } from "./WorkerScript";
export const recentScripts: RecentScript[] = []; export const recentScripts: RecentScript[] = [];
export function AddRecentScript(workerScript: WorkerScript): void { export function AddRecentScript(workerScript: WorkerScript): void {
if (recentScripts.find((r) => r.pid === workerScript.pid)) return; if (recentScripts.find((r) => r.runningScript.pid === workerScript.pid)) return;
recentScripts.unshift({
filename: workerScript.name,
args: workerScript.args,
pid: workerScript.pid,
timestamp: new Date(),
const killedTime = new Date();
recentScripts.unshift({
timeOfDeath: killedTime,
runningScript: workerScript.scriptRef, runningScript: workerScript.scriptRef,
}); });
while (recentScripts.length > 50) {
while (recentScripts.length > Settings.MaxRecentScriptsCapacity) {
recentScripts.pop(); recentScripts.pop();
} }
} }
export interface RecentScript { export interface RecentScript {
filename: string; timeOfDeath: Date;
args: string[];
pid: number;
timestamp: Date;
runningScript: RunningScript; runningScript: RunningScript;
} }

@ -0,0 +1,37 @@
import { WorkerScript } from "./WorkerScript";
/**
* Script death marker.
*
* IMPORTANT: the game engine should not base any of it's decisions on the data
* carried in a ScriptDeath instance.
*
* This is because ScriptDeath instances are thrown through player code when a
* script is killed. Which grants the player access to the class and the ability
* to construct new instances with arbitrary data.
*/
export class ScriptDeath {
/** Process ID number. */
pid: number;
/** Filename of the script. */
name: string;
/** IP Address on which the script was running */
hostname: string;
/** Status message in case of script error. */
errorMessage = "";
constructor(ws: WorkerScript) {
this.pid = ws.pid;
this.name = ws.name;
this.hostname = ws.hostname;
this.errorMessage = ws.errorMessage;
Object.freeze(this);
}
}
Object.freeze(ScriptDeath);
Object.freeze(ScriptDeath.prototype);

@ -60,7 +60,7 @@ export class WorkerScript {
env: Environment; env: Environment;
/** /**
* Status message in case of script error. Currently unused I think * Status message in case of script error.
*/ */
errorMessage = ""; errorMessage = "";

@ -2,6 +2,7 @@
* Stops an actively-running script (represented by a WorkerScript object) * Stops an actively-running script (represented by a WorkerScript object)
* and removes it from the global pool of active scripts. * and removes it from the global pool of active scripts.
*/ */
import { ScriptDeath } from "./ScriptDeath";
import { WorkerScript } from "./WorkerScript"; import { WorkerScript } from "./WorkerScript";
import { workerScripts } from "./WorkerScripts"; import { workerScripts } from "./WorkerScripts";
import { WorkerScriptStartStopEventEmitter } from "./WorkerScriptStartStopEventEmitter"; import { WorkerScriptStartStopEventEmitter } from "./WorkerScriptStartStopEventEmitter";
@ -139,7 +140,7 @@ function killNetscriptDelay(workerScript: WorkerScript): void {
if (workerScript.delay) { if (workerScript.delay) {
clearTimeout(workerScript.delay); clearTimeout(workerScript.delay);
if (workerScript.delayReject) { if (workerScript.delayReject) {
workerScript.delayReject(workerScript); workerScript.delayReject(new ScriptDeath(workerScript));
} }
} }
} }

@ -1,15 +1,20 @@
import { isString } from "./utils/helpers/isString"; import { isString } from "./utils/helpers/isString";
import { GetServer } from "./Server/AllServers"; import { GetServer } from "./Server/AllServers";
import { ScriptDeath } from "./Netscript/ScriptDeath";
import { WorkerScript } from "./Netscript/WorkerScript"; import { WorkerScript } from "./Netscript/WorkerScript";
export function netscriptDelay(time: number, workerScript: WorkerScript): Promise<void> { export function netscriptDelay(time: number, workerScript: WorkerScript): Promise<void> {
// Cancel any pre-existing netscriptDelay'ed function call
// TODO: the rejection almost certainly ends up in the uncaught rejection handler.
// Maybe reject with a stack-trace'd error message?
if (workerScript.delayReject) workerScript.delayReject(); if (workerScript.delayReject) workerScript.delayReject();
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
workerScript.delay = window.setTimeout(() => { workerScript.delay = window.setTimeout(() => {
workerScript.delay = null; workerScript.delay = null;
workerScript.delayReject = undefined; workerScript.delayReject = undefined;
if (workerScript.env.stopFlag) reject(workerScript); if (workerScript.env.stopFlag) reject(new ScriptDeath(workerScript));
else resolve(); else resolve();
}, time); }, time);
workerScript.delayReject = reject; workerScript.delayReject = reject;

@ -79,6 +79,8 @@ import {
Gang as IGang, Gang as IGang,
Bladeburner as IBladeburner, Bladeburner as IBladeburner,
Stanek as IStanek, Stanek as IStanek,
RunningScript as IRunningScript,
RecentScript as IRecentScript,
SourceFileLvl, SourceFileLvl,
BasicHGWOptions, BasicHGWOptions,
ProcessInfo, ProcessInfo,
@ -98,6 +100,7 @@ import { SnackbarEvents } from "./ui/React/Snackbar";
import { Flags } from "./NetscriptFunctions/Flags"; import { Flags } from "./NetscriptFunctions/Flags";
import { calculateIntelligenceBonus } from "./PersonObjects/formulas/intelligence"; import { calculateIntelligenceBonus } from "./PersonObjects/formulas/intelligence";
import { CalculateShareMult, StartSharing } from "./NetworkShare/Share"; import { CalculateShareMult, StartSharing } from "./NetworkShare/Share";
import { recentScripts } from "./Netscript/RecentScripts";
import { CityName } from "./Locations/data/CityNames"; import { CityName } from "./Locations/data/CityNames";
import { wrapAPI } from "./Netscript/APIWrapper"; import { wrapAPI } from "./Netscript/APIWrapper";
@ -213,6 +216,32 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return null; return null;
}; };
/**
* Sanitizes a `RunningScript` to remove sensitive information, making it suitable for
* return through an NS function.
* @see NS.getRecentScripts
* @see NS.getRunningScript
* @param runningScript Existing, internal RunningScript
* @returns A sanitized, NS-facing copy of the RunningScript
*/
const createPublicRunningScript = function (runningScript: RunningScript): IRunningScript {
return {
args: runningScript.args.slice(),
filename: runningScript.filename,
logs: runningScript.logs.slice(),
offlineExpGained: runningScript.offlineExpGained,
offlineMoneyMade: runningScript.offlineMoneyMade,
offlineRunningTime: runningScript.offlineRunningTime,
onlineExpGained: runningScript.onlineExpGained,
onlineMoneyMade: runningScript.onlineMoneyMade,
onlineRunningTime: runningScript.onlineRunningTime,
pid: runningScript.pid,
ramUsage: runningScript.ramUsage,
server: runningScript.server,
threads: runningScript.threads,
};
};
/** /**
* Helper function for getting the error log message when the user specifies * Helper function for getting the error log message when the user specifies
* a nonexistent running script * a nonexistent running script
@ -585,9 +614,25 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return calculatePercentMoneyHacked(server, Player); return calculatePercentMoneyHacked(server, Player);
}, },
hackAnalyzeSecurity: function (_threads: unknown): number { hackAnalyzeSecurity: function (_threads: unknown, _hostname?: unknown): number {
updateDynamicRam("hackAnalyzeSecurity", getRamCost(Player, "hackAnalyzeSecurity")); updateDynamicRam("hackAnalyzeSecurity", getRamCost(Player, "hackAnalyzeSecurity"));
const threads = helper.number("hackAnalyzeSecurity", "threads", _threads); let threads = helper.number("hackAnalyzeSecurity", "threads", _threads);
if (_hostname) {
const hostname = helper.string("hackAnalyzeSecurity", "hostname", _hostname);
const server = safeGetServer(hostname, "hackAnalyze");
if (!(server instanceof Server)) {
workerScript.log("hackAnalyzeSecurity", () => "Cannot be executed on this server.");
return 0;
}
const percentHacked = calculatePercentMoneyHacked(server, Player);
if (percentHacked > 0) {
// thread count is limited to the maximum number of threads needed
threads = Math.ceil(1 / percentHacked);
}
}
return CONSTANTS.ServerFortifyAmount * threads; return CONSTANTS.ServerFortifyAmount * threads;
}, },
hackAnalyzeChance: function (_hostname: unknown): number { hackAnalyzeChance: function (_hostname: unknown): number {
@ -1415,6 +1460,13 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
allFiles.sort(); allFiles.sort();
return allFiles; return allFiles;
}, },
getRecentScripts: function (): IRecentScript[] {
updateDynamicRam("getRecentScripts", getRamCost(Player, "getRecentScripts"));
return recentScripts.map((rs) => ({
timeOfDeath: rs.timeOfDeath,
...createPublicRunningScript(rs.runningScript),
}));
},
ps: function (_hostname: unknown = workerScript.hostname): ProcessInfo[] { ps: function (_hostname: unknown = workerScript.hostname): ProcessInfo[] {
updateDynamicRam("ps", getRamCost(Player, "ps")); updateDynamicRam("ps", getRamCost(Player, "ps"));
const hostname = helper.string("ps", "hostname", _hostname); const hostname = helper.string("ps", "hostname", _hostname);
@ -2130,21 +2182,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
runningScript = getRunningScript(fn, hostname, "getRunningScript", args); runningScript = getRunningScript(fn, hostname, "getRunningScript", args);
} }
if (runningScript === null) return null; if (runningScript === null) return null;
return { return createPublicRunningScript(runningScript);
args: runningScript.args.slice(),
filename: runningScript.filename,
logs: runningScript.logs.slice(),
offlineExpGained: runningScript.offlineExpGained,
offlineMoneyMade: runningScript.offlineMoneyMade,
offlineRunningTime: runningScript.offlineRunningTime,
onlineExpGained: runningScript.onlineExpGained,
onlineMoneyMade: runningScript.onlineMoneyMade,
onlineRunningTime: runningScript.onlineRunningTime,
pid: runningScript.pid,
ramUsage: runningScript.ramUsage,
server: runningScript.server,
threads: runningScript.threads,
};
}, },
getHackTime: function (_hostname: unknown = workerScript.hostname): number { getHackTime: function (_hostname: unknown = workerScript.hostname): number {
updateDynamicRam("getHackTime", getRamCost(Player, "getHackTime")); updateDynamicRam("getHackTime", getRamCost(Player, "getHackTime"));

@ -637,9 +637,6 @@ export function NetscriptCorporation(
const office = getOffice(divisionName, cityName); const office = getOffice(divisionName, cityName);
if (!Object.values(EmployeePositions).includes(job)) throw new Error(`'${job}' is not a valid job.`); if (!Object.values(EmployeePositions).includes(job)) throw new Error(`'${job}' is not a valid job.`);
return netscriptDelay(1000, workerScript).then(function () { return netscriptDelay(1000, workerScript).then(function () {
if (workerScript.env.stopFlag) {
return Promise.reject(workerScript);
}
return Promise.resolve(office.setEmployeeToJob(job, amount)); return Promise.resolve(office.setEmployeeToJob(job, amount));
}); });
}, },
@ -753,6 +750,15 @@ export function NetscriptCorporation(
"Research & Development": office.employeeProd[EmployeePositions.RandD], "Research & Development": office.employeeProd[EmployeePositions.RandD],
Training: office.employeeProd[EmployeePositions.Training], Training: office.employeeProd[EmployeePositions.Training],
}, },
employeeJobs: {
Operations: office.employeeJobs[EmployeePositions.Operations],
Engineer: office.employeeJobs[EmployeePositions.Engineer],
Business: office.employeeJobs[EmployeePositions.Business],
Management: office.employeeJobs[EmployeePositions.Management],
"Research & Development": office.employeeJobs[EmployeePositions.RandD],
Training: office.employeeJobs[EmployeePositions.Training],
Unassigned: office.employeeJobs[EmployeePositions.Unassigned],
},
}; };
}, },
getEmployee: function (_divisionName: unknown, _cityName: unknown, _employeeName: unknown): NSEmployee { getEmployee: function (_divisionName: unknown, _cityName: unknown, _employeeName: unknown): NSEmployee {

@ -219,7 +219,7 @@ export function NetscriptSingularity(
workerScript.running = false; workerScript.running = false;
killWorkerScript(workerScript); killWorkerScript(workerScript);
}, },
installAugmentations: function (_cbScript: unknown): boolean { installAugmentations: function (_cbScript: unknown = ""): boolean {
updateRam("installAugmentations"); updateRam("installAugmentations");
const cbScript = helper.string("installAugmentations", "cbScript", _cbScript); const cbScript = helper.string("installAugmentations", "cbScript", _cbScript);
helper.checkSingularityAccess("installAugmentations"); helper.checkSingularityAccess("installAugmentations");

@ -12,7 +12,7 @@ import {
Stanek as IStanek, Stanek as IStanek,
} from "../ScriptEditor/NetscriptDefinitions"; } from "../ScriptEditor/NetscriptDefinitions";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { NetscriptContext, InternalAPI } from "src/Netscript/APIWrapper"; import { NetscriptContext, InternalAPI } from "../Netscript/APIWrapper";
export function NetscriptStanek( export function NetscriptStanek(
player: IPlayer, player: IPlayer,
@ -26,12 +26,12 @@ export function NetscriptStanek(
} }
return { return {
giftWidth: (_ctx: NetscriptContext) => giftWidth: () =>
function (): number { function (): number {
checkStanekAPIAccess("giftWidth"); checkStanekAPIAccess("giftWidth");
return staneksGift.width(); return staneksGift.width();
}, },
giftHeight: (_ctx: NetscriptContext) => giftHeight: () =>
function (): number { function (): number {
checkStanekAPIAccess("giftHeight"); checkStanekAPIAccess("giftHeight");
return staneksGift.height(); return staneksGift.height();

@ -202,7 +202,7 @@ function _getScriptUrls(script: Script, scripts: Script[], seen: Script[]): Scri
// We automatically define a print function() in the NetscriptJS module so that // We automatically define a print function() in the NetscriptJS module so that
// accidental calls to window.print() do not bring up the "print screen" dialog // accidental calls to window.print() do not bring up the "print screen" dialog
transformedCode += `\n\nfunction print() {throw new Error("Invalid call to window.print(). Did you mean to use Netscript's print()?");}`; transformedCode += `\n\nfunction print() {throw new Error("Invalid call to window.print(). Did you mean to use Netscript's print()?");}\n//# sourceURL=${script.server}/${script.filename}`;
const blob = URL.createObjectURL(makeScriptBlob(transformedCode)); const blob = URL.createObjectURL(makeScriptBlob(transformedCode));
// Push the blob URL onto the top of the stack. // Push the blob URL onto the top of the stack.

@ -3,6 +3,7 @@
* that allows for scripts to run * that allows for scripts to run
*/ */
import { killWorkerScript } from "./Netscript/killWorkerScript"; import { killWorkerScript } from "./Netscript/killWorkerScript";
import { ScriptDeath } from "./Netscript/ScriptDeath";
import { WorkerScript } from "./Netscript/WorkerScript"; import { WorkerScript } from "./Netscript/WorkerScript";
import { workerScripts } from "./Netscript/WorkerScripts"; import { workerScripts } from "./Netscript/WorkerScripts";
import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStartStopEventEmitter"; import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStartStopEventEmitter";
@ -59,7 +60,7 @@ export function prestigeWorkerScripts(): void {
// JS script promises need a little massaging to have the same guarantees as netscript // JS script promises need a little massaging to have the same guarantees as netscript
// promises. This does said massaging and kicks the script off. It returns a promise // promises. This does said massaging and kicks the script off. It returns a promise
// that resolves or rejects when the corresponding worker script is done. // that resolves or rejects when the corresponding worker script is done.
function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Promise<WorkerScript> { function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Promise<void> {
workerScript.running = true; workerScript.running = true;
// The name of the currently running netscript function, to prevent concurrent // The name of the currently running netscript function, to prevent concurrent
@ -79,7 +80,7 @@ function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Pro
// This is not a problem for legacy Netscript because it also checks the // This is not a problem for legacy Netscript because it also checks the
// stop flag in the evaluator. // stop flag in the evaluator.
if (workerScript.env.stopFlag) { if (workerScript.env.stopFlag) {
throw workerScript; throw new ScriptDeath(workerScript);
} }
if (propName === "asleep") return f(...args); // OK for multiple simultaneous calls to sleep. if (propName === "asleep") return f(...args); // OK for multiple simultaneous calls to sleep.
@ -90,7 +91,7 @@ function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Pro
"promise-returning function? (Currently running: %s tried to run: %s)"; "promise-returning function? (Currently running: %s tried to run: %s)";
if (runningFn) { if (runningFn) {
workerScript.errorMessage = makeRuntimeRejectMsg(workerScript, sprintf(msg, runningFn, propName)); workerScript.errorMessage = makeRuntimeRejectMsg(workerScript, sprintf(msg, runningFn, propName));
throw workerScript; throw new ScriptDeath(workerScript);
} }
runningFn = propName; runningFn = propName;
@ -116,18 +117,29 @@ function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Pro
}; };
} }
for (const prop of Object.keys(workerScript.env.vars)) { function wrapObject(vars: any, ...tree: string[]): void {
if (typeof workerScript.env.vars[prop] !== "function") continue; for (const prop of Object.keys(vars)) {
workerScript.env.vars[prop] = wrap(prop, workerScript.env.vars[prop]); switch (typeof vars[prop]) {
case "function": {
vars[prop] = wrap([...tree, prop].join("."), vars[prop]);
break;
} }
workerScript.env.vars.stanek.charge = wrap("stanek.charge", workerScript.env.vars.stanek.charge); case "object": {
if (Array.isArray(vars[prop])) continue;
wrapObject(vars[prop], ...tree, prop);
break;
}
}
}
}
wrapObject(workerScript.env.vars);
// Note: the environment that we pass to the JS script only needs to contain the functions visible // Note: the environment that we pass to the JS script only needs to contain the functions visible
// to that script, which env.vars does at this point. // to that script, which env.vars does at this point.
return new Promise<WorkerScript>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
executeJSScript(player, workerScript.getServer().scripts, workerScript) executeJSScript(player, workerScript.getServer().scripts, workerScript)
.then(() => { .then(() => {
resolve(workerScript); resolve();
}) })
.catch((e) => reject(e)); .catch((e) => reject(e));
}).catch((e) => { }).catch((e) => {
@ -140,20 +152,21 @@ function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Pro
e.message + ((e.stack && "\nstack:\n" + e.stack.toString()) || ""), e.message + ((e.stack && "\nstack:\n" + e.stack.toString()) || ""),
); );
} }
throw workerScript; throw new ScriptDeath(workerScript);
} else if (isScriptErrorMessage(e)) { } else if (isScriptErrorMessage(e)) {
workerScript.errorMessage = e; workerScript.errorMessage = e;
throw workerScript; throw new ScriptDeath(workerScript);
} else if (e instanceof WorkerScript) { } else if (e instanceof ScriptDeath) {
throw e; throw e;
} }
workerScript.errorMessage = makeRuntimeRejectMsg(workerScript, e); // Don't know what to do with it, let's try making an error message out of it
throw workerScript; // Don't know what to do with it, let's rethrow. workerScript.errorMessage = makeRuntimeRejectMsg(workerScript, "" + e);
throw new ScriptDeath(workerScript);
}); });
} }
function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript> { function startNetscript1Script(workerScript: WorkerScript): Promise<void> {
const code = workerScript.code; const code = workerScript.code;
workerScript.running = true; workerScript.running = true;
@ -168,7 +181,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
workerScript.env.stopFlag = true; workerScript.env.stopFlag = true;
workerScript.running = false; workerScript.running = false;
killWorkerScript(workerScript); killWorkerScript(workerScript);
return Promise.resolve(workerScript); return Promise.resolve();
} }
const interpreterInitialization = function (int: any, scope: any): void { const interpreterInitialization = function (int: any, scope: any): void {
@ -201,7 +214,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
}) })
.catch(function (err: any) { .catch(function (err: any) {
// workerscript is when you cancel a delay // workerscript is when you cancel a delay
if (!(err instanceof WorkerScript)) { if (!(err instanceof ScriptDeath)) {
console.error(err); console.error(err);
const errorTextArray = err.split("|DELIMITER|"); const errorTextArray = err.split("|DELIMITER|");
const hostname = errorTextArray[1]; const hostname = errorTextArray[1];
@ -214,7 +227,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
workerScript.env.stopFlag = true; workerScript.env.stopFlag = true;
workerScript.running = false; workerScript.running = false;
killWorkerScript(workerScript); killWorkerScript(workerScript);
return Promise.resolve(workerScript); return Promise.resolve();
} }
}); });
}; };
@ -277,14 +290,14 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
workerScript.env.stopFlag = true; workerScript.env.stopFlag = true;
workerScript.running = false; workerScript.running = false;
killWorkerScript(workerScript); killWorkerScript(workerScript);
return Promise.resolve(workerScript); return Promise.resolve();
} }
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
function runInterpreter(): void { function runInterpreter(): void {
try { try {
if (workerScript.env.stopFlag) { if (workerScript.env.stopFlag) {
return reject(workerScript); return reject(new ScriptDeath(workerScript));
} }
let more = true; let more = true;
@ -297,7 +310,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
if (more) { if (more) {
setTimeout(runInterpreter, Settings.CodeInstructionRunTime); setTimeout(runInterpreter, Settings.CodeInstructionRunTime);
} else { } else {
resolve(workerScript); resolve();
} }
} catch (e: any) { } catch (e: any) {
e = e.toString(); e = e.toString();
@ -305,7 +318,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
e = makeRuntimeRejectMsg(workerScript, e); e = makeRuntimeRejectMsg(workerScript, e);
} }
workerScript.errorMessage = e; workerScript.errorMessage = e;
return reject(workerScript); return reject(new ScriptDeath(workerScript));
} }
} }
@ -314,11 +327,12 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
} catch (e: any) { } catch (e: any) {
if (isString(e)) { if (isString(e)) {
workerScript.errorMessage = e; workerScript.errorMessage = e;
return reject(workerScript); return reject(new ScriptDeath(workerScript));
} else if (e instanceof WorkerScript) { } else if (e instanceof ScriptDeath) {
return reject(e); return reject(e);
} else { } else {
return reject(workerScript); console.error(e);
return reject(new ScriptDeath(workerScript));
} }
} }
}); });
@ -541,29 +555,30 @@ function createAndAddWorkerScript(
// Create the WorkerScript. NOTE: WorkerScript ctor will set the underlying // Create the WorkerScript. NOTE: WorkerScript ctor will set the underlying
// RunningScript's PID as well // RunningScript's PID as well
const s = new WorkerScript(runningScriptObj, pid, NetscriptFunctions); const workerScript = new WorkerScript(runningScriptObj, pid, NetscriptFunctions);
s.ramUsage = oneRamUsage; workerScript.ramUsage = oneRamUsage;
// Add the WorkerScript to the global pool // Add the WorkerScript to the global pool
workerScripts.set(pid, s); workerScripts.set(pid, workerScript);
WorkerScriptStartStopEventEmitter.emit(); WorkerScriptStartStopEventEmitter.emit();
// Start the script's execution // Start the script's execution
let p: Promise<WorkerScript> | null = null; // Script's resulting promise let scriptExecution: Promise<void> | null = null; // Script's resulting promise
if (s.name.endsWith(".js") || s.name.endsWith(".ns")) { if (workerScript.name.endsWith(".js") || workerScript.name.endsWith(".ns")) {
p = startNetscript2Script(player, s); scriptExecution = startNetscript2Script(player, workerScript);
} else { } else {
p = startNetscript1Script(s); scriptExecution = startNetscript1Script(workerScript);
if (!(p instanceof Promise)) { if (!(scriptExecution instanceof Promise)) {
return false; return false;
} }
} }
// Once the code finishes (either resolved or rejected, doesnt matter), set its // Once the code finishes (either resolved or rejected, doesnt matter), set its
// running status to false // running status to false
p.then(function (w: WorkerScript) { scriptExecution
w.running = false; .then(function () {
w.env.stopFlag = true; workerScript.running = false;
workerScript.env.stopFlag = true;
// On natural death, the earnings are transfered to the parent if it still exists. // On natural death, the earnings are transfered to the parent if it still exists.
if (parent !== undefined) { if (parent !== undefined) {
if (parent.running) { if (parent.running) {
@ -572,51 +587,52 @@ function createAndAddWorkerScript(
} }
} }
killWorkerScript(s); killWorkerScript(workerScript);
w.log("", () => "Script finished running"); workerScript.log("", () => "Script finished running");
}).catch(function (w) { })
if (w instanceof Error) { .catch(function (e) {
if (e instanceof Error) {
dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer"); dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
console.error("Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + w.toString()); console.error("Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + e.toString());
return; return;
} else if (w instanceof WorkerScript) { } else if (e instanceof ScriptDeath) {
if (isScriptErrorMessage(w.errorMessage)) { if (isScriptErrorMessage(workerScript.errorMessage)) {
const errorTextArray = w.errorMessage.split("|DELIMITER|"); const errorTextArray = workerScript.errorMessage.split("|DELIMITER|");
if (errorTextArray.length != 4) { if (errorTextArray.length != 4) {
console.error("ERROR: Something wrong with Error text in evaluator..."); console.error("ERROR: Something wrong with Error text in evaluator...");
console.error("Error text: " + w.errorMessage); console.error("Error text: " + workerScript.errorMessage);
return; return;
} }
const hostname = errorTextArray[1]; const hostname = errorTextArray[1];
const scriptName = errorTextArray[2]; const scriptName = errorTextArray[2];
const errorMsg = errorTextArray[3]; const errorMsg = errorTextArray[3];
let msg = `RUNTIME ERROR<br>${scriptName}@${hostname}<br>`; let msg = `RUNTIME ERROR<br>${scriptName}@${hostname} (PID - ${workerScript.pid})<br>`;
if (w.args.length > 0) { if (workerScript.args.length > 0) {
msg += `Args: ${arrayToString(w.args)}<br>`; msg += `Args: ${arrayToString(workerScript.args)}<br>`;
} }
msg += "<br>"; msg += "<br>";
msg += errorMsg; msg += errorMsg;
dialogBoxCreate(msg); dialogBoxCreate(msg);
w.log("", () => "Script crashed with runtime error"); workerScript.log("", () => "Script crashed with runtime error");
} else { } else {
w.log("", () => "Script killed"); workerScript.log("", () => "Script killed");
return; // Already killed, so stop here return; // Already killed, so stop here
} }
} else if (isScriptErrorMessage(w)) { } else if (isScriptErrorMessage(e)) {
dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer"); dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
console.error( console.error(
"ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " + "ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " +
w.toString(), e.toString(),
); );
return; return;
} else { } else {
dialogBoxCreate("An unknown script died for an unknown reason. This is a bug please contact game dev"); dialogBoxCreate("An unknown script died for an unknown reason. This is a bug please contact game dev");
console.error(w); console.error(e);
} }
killWorkerScript(s); killWorkerScript(workerScript);
}); });
return true; return true;

@ -94,7 +94,7 @@ export const GraftingRoot = (): React.ReactElement => {
<Typography variant="h5">Graft Augmentations</Typography> <Typography variant="h5">Graft Augmentations</Typography>
{getAvailableAugs(player).length > 0 ? ( {getAvailableAugs(player).length > 0 ? (
<Paper sx={{ my: 1, width: "fit-content", display: "grid", gridTemplateColumns: "1fr 3fr" }}> <Paper sx={{ my: 1, width: "fit-content", display: "grid", gridTemplateColumns: "1fr 3fr" }}>
<List sx={{ maxHeight: 400, overflowY: "scroll", borderRight: `1px solid ${Settings.theme.welllight}` }}> <List sx={{ height: 400, overflowY: "scroll", borderRight: `1px solid ${Settings.theme.welllight}` }}>
{getAvailableAugs(player).map((k, i) => ( {getAvailableAugs(player).map((k, i) => (
<ListItemButton key={i + 1} onClick={() => setSelectedAug(k)} selected={selectedAug === k}> <ListItemButton key={i + 1} onClick={() => setSelectedAug(k)} selected={selectedAug === k}>
<Typography>{k}</Typography> <Typography>{k}</Typography>

@ -101,24 +101,46 @@ interface Player {
/** /**
* @public * @public
*/ */
interface RunningScript { export interface RunningScript {
/** Arguments the script was called with */
args: string[]; args: string[];
/** Filename of the script */
filename: string; filename: string;
/**
* Script logs as an array. The newest log entries are at the bottom.
* Timestamps, if enabled, are placed inside `[brackets]` at the start of each line.
**/
logs: string[]; logs: string[];
/** Total amount of hacking experience earned from this script when offline */
offlineExpGained: number; offlineExpGained: number;
/** Total amount of money made by this script when offline */
offlineMoneyMade: number; offlineMoneyMade: number;
/** Offline running time of the script, in seconds **/ /** Number of seconds that the script has been running offline */
offlineRunningTime: number; offlineRunningTime: number;
/** Total amount of hacking experience earned from this script when online */
onlineExpGained: number; onlineExpGained: number;
/** Total amount of money made by this script when online */
onlineMoneyMade: number; onlineMoneyMade: number;
/** Online running time of the script, in seconds **/ /** Number of seconds that this script has been running online */
onlineRunningTime: number; onlineRunningTime: number;
/** Process ID. Must be an integer */
pid: number; pid: number;
/** How much RAM this script uses for ONE thread */
ramUsage: number; ramUsage: number;
/** Hostname of the server on which this script runs */
server: string; server: string;
/** Number of threads that this script runs with */
threads: number; threads: number;
} }
/**
* @public
*/
export interface RecentScript extends RunningScript {
/** Timestamp of when the script was killed */
timeOfDeath: Date;
}
/** /**
* Data representing the internal values of a crime. * Data representing the internal values of a crime.
* @public * @public
@ -2618,7 +2640,7 @@ export interface Hacknet {
* // NS1: * // NS1:
* var upgradeName = "Sell for Corporation Funds"; * var upgradeName = "Sell for Corporation Funds";
* if (hacknet.numHashes() > hacknet.hashCost(upgradeName)) { * if (hacknet.numHashes() > hacknet.hashCost(upgradeName)) {
* hacknet.spendHashes(upgName); * hacknet.spendHashes(upgradeName);
* } * }
* ``` * ```
* @example * @example
@ -2626,7 +2648,7 @@ export interface Hacknet {
* // NS2: * // NS2:
* const upgradeName = "Sell for Corporation Funds"; * const upgradeName = "Sell for Corporation Funds";
* if (ns.hacknet.numHashes() > ns.hacknet.hashCost(upgradeName)) { * if (ns.hacknet.numHashes() > ns.hacknet.hashCost(upgradeName)) {
* ns.hacknet.spendHashes(upgName); * ns.hacknet.spendHashes(upgradeName);
* } * }
* ``` * ```
* @param upgName - Name of the upgrade of Hacknet Node. * @param upgName - Name of the upgrade of Hacknet Node.
@ -4555,9 +4577,10 @@ export interface NS extends Singularity {
* Returns the security increase that would occur if a hack with this many threads happened. * Returns the security increase that would occur if a hack with this many threads happened.
* *
* @param threads - Amount of threads that will be used. * @param threads - Amount of threads that will be used.
* @param hostname - Hostname of the target server. The number of threads is limited to the number needed to hack the servers maximum amount of money.
* @returns The security increase. * @returns The security increase.
*/ */
hackAnalyzeSecurity(threads: number): number; hackAnalyzeSecurity(threads: number, hostname?: string): number;
/** /**
* Get the chance of successfully hacking a server. * Get the chance of successfully hacking a server.
@ -4781,6 +4804,27 @@ export interface NS extends Singularity {
*/ */
getScriptLogs(fn?: string, host?: string, ...args: any[]): string[]; getScriptLogs(fn?: string, host?: string, ...args: any[]): string[];
/**
* Get an array of recently killed scripts across all servers.
* @remarks
* RAM cost: 0.2 GB
*
* The most recently killed script is the first element in the array.
* Note that there is a maximum number of recently killed scripts which are tracked.
* This is configurable in the game's options as `Recently killed scripts size`.
*
* @usage below:
* ```ts
* let recentScripts = ns.getRecentScripts();
* let mostRecent = recentScripts.shift()
* if (mostRecent)
* ns.tprint(mostRecent.logs.join('\n'))
* ```
*
* @returns Array with information about previously killed scripts.
*/
getRecentScripts(): RecentScript[];
/** /**
* Open the tail window of a script. * Open the tail window of a script.
* @remarks * @remarks
@ -6965,8 +7009,10 @@ interface Office {
maxMor: number; maxMor: number;
/** Name of all the employees */ /** Name of all the employees */
employees: string[]; employees: string[];
/** Positions of the employees */ /** Production of the employees */
employeeProd: EmployeeJobs; employeeProd: EmployeeJobs;
/** Positions of the employees */
employeeJobs: EmployeeJobs;
} }
/** /**

@ -981,11 +981,11 @@ export function Root(props: IProps): React.ReactElement {
</Button> </Button>
<Typography> <Typography>
{" "} {" "}
Documentation:{" "} <strong>Documentation:</strong>{" "}
<Link target="_blank" href="https://bitburner.readthedocs.io/en/latest/index.html"> <Link target="_blank" href="https://bitburner.readthedocs.io/en/latest/index.html">
Basic Basic
</Link>{" "} </Link>
| {" | "}
<Link target="_blank" href="https://github.com/danielyxie/bitburner/blob/dev/markdown/bitburner.ns.md"> <Link target="_blank" href="https://github.com/danielyxie/bitburner/blob/dev/markdown/bitburner.ns.md">
Full Full
</Link> </Link>

@ -63,6 +63,11 @@ interface IDefaultSettings {
*/ */
Locale: string; Locale: string;
/**
* Limit the number of recently killed script entries being tracked.
*/
MaxRecentScriptsCapacity: number;
/** /**
* Limit the number of log entries for each script being executed on each server. * Limit the number of log entries for each script being executed on each server.
*/ */
@ -191,6 +196,7 @@ export const defaultSettings: IDefaultSettings = {
EnableBashHotkeys: false, EnableBashHotkeys: false,
TimestampsFormat: "", TimestampsFormat: "",
Locale: "en", Locale: "en",
MaxRecentScriptsCapacity: 50,
MaxLogCapacity: 50, MaxLogCapacity: 50,
MaxPortCapacity: 50, MaxPortCapacity: 50,
MaxTerminalCapacity: 500, MaxTerminalCapacity: 500,
@ -228,6 +234,7 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
EnableBashHotkeys: defaultSettings.EnableBashHotkeys, EnableBashHotkeys: defaultSettings.EnableBashHotkeys,
TimestampsFormat: defaultSettings.TimestampsFormat, TimestampsFormat: defaultSettings.TimestampsFormat,
Locale: "en", Locale: "en",
MaxRecentScriptsCapacity: defaultSettings.MaxRecentScriptsCapacity,
MaxLogCapacity: defaultSettings.MaxLogCapacity, MaxLogCapacity: defaultSettings.MaxLogCapacity,
MaxPortCapacity: defaultSettings.MaxPortCapacity, MaxPortCapacity: defaultSettings.MaxPortCapacity,
MaxTerminalCapacity: defaultSettings.MaxTerminalCapacity, MaxTerminalCapacity: defaultSettings.MaxTerminalCapacity,

@ -191,8 +191,8 @@ export const HelpTexts: IMap<string[]> = {
"Usage: connect [hostname]", "Usage: connect [hostname]",
" ", " ",
"Connect to a remote server. The hostname of the remote server must be given as the argument ", "Connect to a remote server. The hostname of the remote server must be given as the argument ",
"to this command. Note that only servers that are immediately adjacent to the current server in the network can be connected to. To ", "to this command. Note that only servers that are immediately adjacent to the current server in the network and the ones that have",
"see which servers can be connected to, use the 'scan' command.", "a backdoor installed can be connected to. To see which servers can be connected to, use the 'scan' command.",
" ", " ",
], ],
cp: ["Usage: cp [src] [dst]", " ", "Copy a file on this server. To copy a file to another server use scp.", " "], cp: ["Usage: cp [src] [dst]", " ", "Copy a file on this server. To copy a file to another server use scp.", " "],

@ -4,7 +4,8 @@ import { getSubdirectories } from "./DirectoryServerHelpers";
import { Aliases, GlobalAliases, substituteAliases } from "../Alias"; import { Aliases, GlobalAliases, substituteAliases } from "../Alias";
import { DarkWebItems } from "../DarkWeb/DarkWebItems"; import { DarkWebItems } from "../DarkWeb/DarkWebItems";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { GetServer, GetAllServers } from "../Server/AllServers"; import { GetAllServers } from "../Server/AllServers";
import { Server } from "../Server/Server";
import { ParseCommand, ParseCommands } from "./Parser"; import { ParseCommand, ParseCommands } from "./Parser";
import { HelpTexts } from "./HelpText"; import { HelpTexts } from "./HelpText";
import { isScriptFilename } from "../Script/isScriptFilename"; import { isScriptFilename } from "../Script/isScriptFilename";
@ -238,16 +239,14 @@ export async function determineAllPossibilitiesForTabCompletion(
} }
if (isCommand("connect")) { if (isCommand("connect")) {
// All network connections // All directly connected and backdoored servers are reachable
for (let i = 0; i < currServ.serversOnNetwork.length; ++i) { console.log(GetAllServers());
const serv = GetServer(currServ.serversOnNetwork[i]); return GetAllServers()
if (serv == null) { .filter(
continue; (server) =>
} currServ.serversOnNetwork.includes(server.hostname) || (server instanceof Server && server.backdoorInstalled),
allPos.push(serv.hostname); )
} .map((server) => server.hostname);
return allPos;
} }
if (isCommand("nano") || isCommand("vim")) { if (isCommand("nano") || isCommand("vim")) {

@ -1,4 +1,4 @@
import { WorkerScript } from "./Netscript/WorkerScript"; import { ScriptDeath } from "./Netscript/ScriptDeath";
import { isScriptErrorMessage } from "./NetscriptEvaluator"; import { isScriptErrorMessage } from "./NetscriptEvaluator";
import { dialogBoxCreate } from "./ui/React/DialogBox"; import { dialogBoxCreate } from "./ui/React/DialogBox";
@ -14,9 +14,9 @@ export function setupUncaughtPromiseHandler(): void {
msg += "<br>"; msg += "<br>";
msg += errorMsg; msg += errorMsg;
dialogBoxCreate(msg); dialogBoxCreate(msg);
} else if (e.reason instanceof WorkerScript) { } else if (e.reason instanceof ScriptDeath) {
const msg = const msg =
`UNCAUGHT PROMISE ERROR<br>You forgot to await a promise<br>${e.reason.name}@${e.reason.hostname}<br>` + `UNCAUGHT PROMISE ERROR<br>You forgot to await a promise<br>${e.reason.name}@${e.reason.hostname} (PID - ${e.reason.pid})<br>` +
`Maybe hack / grow / weaken ?`; `Maybe hack / grow / weaken ?`;
dialogBoxCreate(msg); dialogBoxCreate(msg);
} }

@ -1256,7 +1256,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
"Convert it into a binary string and encode it as a 'Hamming-Code'. eg:\n ", "Convert it into a binary string and encode it as a 'Hamming-Code'. eg:\n ",
"Value 8 will result into binary '1000', which will be encoded", "Value 8 will result into binary '1000', which will be encoded",
"with the pattern 'pppdpddd', where p is a paritybit and d a databit,\n", "with the pattern 'pppdpddd', where p is a paritybit and d a databit,\n",
"or '10101' (Value 21) will result into (pppdpdddpd) '1111101011'.\n\n", "or '10101' (Value 21) will result into (pppdpdddpd) '1001101011'.\n\n",
"NOTE: You need an parity Bit on Index 0 as an 'overall'-paritybit. \n", "NOTE: You need an parity Bit on Index 0 as an 'overall'-paritybit. \n",
"NOTE 2: You should watch the HammingCode-video from 3Blue1Brown, which explains the 'rule' of encoding,", "NOTE 2: You should watch the HammingCode-video from 3Blue1Brown, which explains the 'rule' of encoding,",
"including the first Index parity-bit mentioned on the first note.\n\n", "including the first Index parity-bit mentioned on the first note.\n\n",

@ -57,8 +57,8 @@ export function RecentScriptAccordion(props: IProps): React.ReactElement {
<ListItemText <ListItemText
primary={ primary={
<Typography> <Typography>
{recentScript.filename} (died{" "} {recentScript.runningScript.filename} (died{" "}
{convertTimeMsToTimeElapsedString(new Date().getTime() - recentScript.timestamp.getTime())} ago) {convertTimeMsToTimeElapsedString(new Date().getTime() - recentScript.timeOfDeath.getTime())} ago)
</Typography> </Typography>
} }
/> />
@ -78,7 +78,7 @@ export function RecentScriptAccordion(props: IProps): React.ReactElement {
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableCell className={classes.noborder} colSpan={2}> <TableCell className={classes.noborder} colSpan={2}>
<Typography> Args: {arrayToString(recentScript.args)}</Typography> <Typography> Args: {arrayToString(recentScript.runningScript.args)}</Typography>
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>

@ -13,7 +13,7 @@ export function RecentScriptsPage(): React.ReactElement {
<> <>
<Typography>List of all recently killed scripts.</Typography> <Typography>List of all recently killed scripts.</Typography>
{recentScripts.map((r) => ( {recentScripts.map((r) => (
<RecentScriptAccordion key={r.pid} recentScript={r} /> <RecentScriptAccordion key={r.runningScript.pid} recentScript={r} />
))} ))}
</> </>
); );

@ -1,144 +1,80 @@
import React, { useState, useEffect } from "react"; import { Paper, Table, TableBody, Box, IconButton, Typography, Container, Tooltip } from "@mui/material";
import { MoreHoriz, Info } from "@mui/icons-material";
import { numeralWrapper } from "./numeralFormat"; import React, { useEffect, useState } from "react";
import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
import { getPurchaseServerLimit } from "../Server/ServerPurchases";
import { HacknetServerConstants } from "../Hacknet/data/Constants";
import { StatsTable } from "./React/StatsTable";
import { Money } from "./React/Money";
import { use } from "./Context";
import { MoneySourceTracker } from "../utils/MoneySourceTracker";
import { BitNodes } from "../BitNode/BitNode"; import { BitNodes } from "../BitNode/BitNode";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import Typography from "@mui/material/Typography"; import { HacknetServerConstants } from "../Hacknet/data/Constants";
import Box from "@mui/material/Box"; import { getPurchaseServerLimit } from "../Server/ServerPurchases";
import IconButton from "@mui/material/IconButton"; import { Settings } from "../Settings/Settings";
import MoreHorizIcon from "@mui/icons-material/MoreHoriz"; import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
import { MoneySourceTracker } from "../utils/MoneySourceTracker";
import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
import { use } from "./Context";
import { numeralWrapper } from "./numeralFormat";
import { Modal } from "./React/Modal"; import { Modal } from "./React/Modal";
import { Money } from "./React/Money";
import { StatsRow } from "./React/StatsRow";
import { StatsTable } from "./React/StatsTable";
import TableBody from "@mui/material/TableBody"; interface EmployersModalProps {
import { Table, TableCell } from "./React/Table"; open: boolean;
import TableRow from "@mui/material/TableRow"; onClose: () => void;
}
function LastEmployer(): React.ReactElement { const EmployersModal = ({ open, onClose }: EmployersModalProps): React.ReactElement => {
const player = use.Player(); const player = use.Player();
if (player.companyName) {
return <Typography>Employer at which you last worked: {player.companyName}</Typography>;
}
return <></>;
}
function LastJob(): React.ReactElement {
const player = use.Player();
if (player.companyName !== "") {
return <Typography>Job you last worked: {player.jobs[player.companyName]}</Typography>;
}
return <></>;
}
function Employers(): React.ReactElement {
const player = use.Player();
if (player.jobs && Object.keys(player.jobs).length !== 0)
return ( return (
<Modal open={open} onClose={onClose}>
<> <>
<Typography>All Employers:</Typography> <Typography variant="h5">All Employers</Typography>
<ul> <ul>
{Object.keys(player.jobs).map((j) => ( {Object.keys(player.jobs).map((j) => (
<Typography key={j}>* {j}</Typography> <Typography key={j}>* {j}</Typography>
))} ))}
</ul> </ul>
</> </>
</Modal>
); );
return <></>; };
interface MultTableProps {
rows: (string | number)[][];
color: string;
noMargin?: boolean;
} }
function Hacknet(): React.ReactElement { function MultiplierTable(props: MultTableProps): React.ReactElement {
const player = use.Player();
// Can't import HacknetHelpers for some reason.
if (!(player.bitNodeN === 9 || SourceFileFlags[9] > 0)) {
return ( return (
<> <Table sx={{ display: "table", width: "100%", mb: (props.noMargin ?? false) === true ? 0 : 2 }}>
<Typography>{`Hacknet Nodes owned: ${player.hacknetNodes.length}`}</Typography>
<br />
</>
);
} else {
return (
<>
<Typography>{`Hacknet Servers owned: ${player.hacknetNodes.length} / ${HacknetServerConstants.MaxServers}`}</Typography>
<br />
</>
);
}
}
function Intelligence(): React.ReactElement {
const player = use.Player();
if (player.intelligence > 0 && (player.bitNodeN === 5 || SourceFileFlags[5] > 0)) {
return (
<TableRow>
<TableCell>
<Typography>Intelligence:&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography>{numeralWrapper.formatSkill(player.intelligence)}&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>({numeralWrapper.formatExp(player.intelligence_exp)} exp)</Typography>
</TableCell>
</TableRow>
);
}
return <></>;
}
function MultiplierTable(props: any): React.ReactElement {
function bn5Stat(r: any): JSX.Element {
if (SourceFileFlags[5] > 0 && r.length > 2 && r[1] != r[2]) {
return (
<TableCell key="2" align="right">
<Typography noWrap>({numeralWrapper.formatPercentage(r[2])})</Typography>
</TableCell>
);
}
return <></>;
}
return (
<>
<Table size="small" padding="none">
<TableBody> <TableBody>
{props.rows.map((r: any) => ( {props.rows.map((data) => {
<TableRow key={r[0]}> const mult = data[0] as string,
<TableCell key="0"> value = data[1] as number,
<Typography noWrap>{`${r[0]} multiplier:`}&nbsp;</Typography> modded = data[2] as number | null;
</TableCell>
<TableCell key="1" align="right"> if (modded && modded !== value && SourceFileFlags[5] > 0) {
<Typography noWrap>{numeralWrapper.formatPercentage(r[1])}</Typography> return (
</TableCell> <StatsRow key={mult} name={mult} color={props.color} data={{}}>
{bn5Stat(r)} <>
</TableRow> <Typography color={props.color}>
))} <span style={{ opacity: 0.5 }}>{numeralWrapper.formatPercentage(value)}</span>{" "}
{numeralWrapper.formatPercentage(modded)}
</Typography>
</>
</StatsRow>
);
}
return (
<StatsRow
key={mult}
name={mult}
color={props.color}
data={{ content: numeralWrapper.formatPercentage(value) }}
/>
);
})}
</TableBody> </TableBody>
</Table> </Table>
</>
);
}
function BladeburnerMults(): React.ReactElement {
const player = use.Player();
if (!player.canAccessBladeburner()) return <></>;
return (
<MultiplierTable
rows={[
["Bladeburner Success Chance", player.bladeburner_success_chance_mult],
["Bladeburner Max Stamina", player.bladeburner_max_stamina_mult],
["Bladeburner Stamina Gain", player.bladeburner_stamina_gain_mult],
["Bladeburner Field Analysis", player.bladeburner_analysis_mult],
]}
/>
); );
} }
@ -146,15 +82,17 @@ function CurrentBitNode(): React.ReactElement {
const player = use.Player(); const player = use.Player();
if (player.sourceFiles.length > 0) { if (player.sourceFiles.length > 0) {
const index = "BitNode" + player.bitNodeN; const index = "BitNode" + player.bitNodeN;
const currentSourceFile = player.sourceFiles.find((sourceFile) => sourceFile.n == player.bitNodeN);
const lvl = currentSourceFile ? currentSourceFile.lvl : 0;
return ( return (
<> <Box>
<Typography variant="h4"> <Paper sx={{ p: 1 }}>
BitNode {player.bitNodeN}: {BitNodes[index].name} <Typography variant="h5">
BitNode {player.bitNodeN}: {BitNodes[index].name} (Level {lvl})
</Typography> </Typography>
<Typography sx={{ mx: 2 }} style={{ whiteSpace: "pre-wrap", overflowWrap: "break-word" }}> <Typography sx={{ whiteSpace: "pre-wrap", overflowWrap: "break-word" }}>{BitNodes[index].info}</Typography>
{BitNodes[index].info} </Paper>
</Typography> </Box>
</>
); );
} }
@ -262,6 +200,7 @@ function MoneyModal({ open, onClose }: IMoneyModalProps): React.ReactElement {
export function CharacterStats(): React.ReactElement { export function CharacterStats(): React.ReactElement {
const player = use.Player(); const player = use.Player();
const [moneyOpen, setMoneyOpen] = useState(false); const [moneyOpen, setMoneyOpen] = useState(false);
const [employersOpen, setEmployersOpen] = useState(false);
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
function rerender(): void { function rerender(): void {
setRerender((old) => !old); setRerender((old) => !old);
@ -273,111 +212,151 @@ export function CharacterStats(): React.ReactElement {
}, []); }, []);
const timeRows = [ const timeRows = [
["Time played since last Augmentation:", convertTimeMsToTimeElapsedString(player.playtimeSinceLastAug)], ["Since last Augmentation installation", convertTimeMsToTimeElapsedString(player.playtimeSinceLastAug)],
]; ];
if (player.sourceFiles.length > 0) { if (player.sourceFiles.length > 0) {
timeRows.push([ timeRows.push(["Since last Bitnode destroyed", convertTimeMsToTimeElapsedString(player.playtimeSinceLastBitnode)]);
"Time played since last Bitnode destroyed:",
convertTimeMsToTimeElapsedString(player.playtimeSinceLastBitnode),
]);
} }
timeRows.push(["Total Time played:", convertTimeMsToTimeElapsedString(player.totalPlaytime)]); timeRows.push(["Total", convertTimeMsToTimeElapsedString(player.totalPlaytime)]);
return ( return (
<> <Container maxWidth="lg" disableGutters sx={{ mx: 0 }}>
<Typography variant="h4">General</Typography>
<Box sx={{ mx: 2 }}>
<Typography>Current City: {player.city}</Typography>
<LastEmployer />
<LastJob />
<Employers />
<Typography>
Money: <Money money={player.money} />
<IconButton onClick={() => setMoneyOpen(true)}>
<MoreHorizIcon color="info" />
</IconButton>
</Typography>
</Box>
<br />
<Typography variant="h4">Stats</Typography> <Typography variant="h4">Stats</Typography>
<Box sx={{ mx: 2 }}> <Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr", minWidth: "fit-content", mb: 1, gap: 1 }}>
<Table size="small" padding="none"> <Paper sx={{ p: 1 }}>
<Typography variant="h5">General</Typography>
<Table>
<TableBody> <TableBody>
<TableRow> <StatsRow name="Current City" color={Settings.theme.primary} data={{ content: player.city }} />
<TableCell> <StatsRow name="Money" color={Settings.theme.money} data={{}}>
<Typography noWrap>Hacking:&nbsp;</Typography> <>
</TableCell> <Money money={player.money} />
<TableCell align="right"> <IconButton onClick={() => setMoneyOpen(true)} sx={{ p: 0 }}>
<Typography noWrap>{numeralWrapper.formatSkill(player.hacking)}&nbsp;</Typography> <MoreHoriz color="info" />
</TableCell> </IconButton>
<TableCell align="right"> </>
<Typography noWrap>({numeralWrapper.formatExp(player.hacking_exp)} exp)</Typography> </StatsRow>
</TableCell> {player.companyName ? (
</TableRow> <>
<TableRow> <StatsRow
<TableCell> name="Last Employer"
<Typography noWrap>Strength:&nbsp;</Typography> color={Settings.theme.primary}
</TableCell> data={{ content: player.companyName }}
<TableCell align="right"> />
<Typography noWrap>{numeralWrapper.formatSkill(player.strength)}&nbsp;</Typography> <StatsRow
</TableCell> name="Last Job"
<TableCell align="right"> color={Settings.theme.primary}
<Typography noWrap>({numeralWrapper.formatExp(player.strength_exp)} exp)</Typography> data={{ content: player.jobs[player.companyName] }}
</TableCell> />
</TableRow> </>
<TableRow> ) : (
<TableCell> <></>
<Typography noWrap>Defense:&nbsp;</Typography> )}
</TableCell> {player.jobs && Object.keys(player.jobs).length !== 0 ? (
<TableCell align="right"> <StatsRow name="All Employers" color={Settings.theme.primary} data={{}}>
<Typography noWrap>{numeralWrapper.formatSkill(player.defense)}&nbsp;</Typography> <>
</TableCell> <span style={{ color: Settings.theme.primary }}>{Object.keys(player.jobs).length} total</span>
<TableCell align="right"> <IconButton onClick={() => setEmployersOpen(true)} sx={{ p: 0 }}>
<Typography noWrap>({numeralWrapper.formatExp(player.defense_exp)} exp)</Typography> <MoreHoriz color="info" />
</TableCell> </IconButton>
</TableRow> </>
<TableRow> </StatsRow>
<TableCell> ) : (
<Typography noWrap>Dexterity:&nbsp;</Typography> <></>
</TableCell> )}
<TableCell align="right"> <StatsRow
<Typography noWrap>{numeralWrapper.formatSkill(player.dexterity)}&nbsp;</Typography> name="Servers Owned"
</TableCell> color={Settings.theme.primary}
<TableCell align="right"> data={{ content: `${player.purchasedServers.length} / ${getPurchaseServerLimit()}` }}
<Typography noWrap>({numeralWrapper.formatExp(player.dexterity_exp)} exp)</Typography> />
</TableCell> <StatsRow
</TableRow> name={`Hacknet ${player.bitNodeN === 9 || SourceFileFlags[9] > 0 ? "Servers" : "Nodes"} owned`}
<TableRow> color={Settings.theme.primary}
<TableCell> data={{
<Typography noWrap>Agility:&nbsp;</Typography> content: `${player.hacknetNodes.length}${
</TableCell> player.bitNodeN === 9 || SourceFileFlags[9] > 0 ? ` / ${HacknetServerConstants.MaxServers}` : ""
<TableCell align="right"> }`,
<Typography noWrap>{numeralWrapper.formatSkill(player.agility)}&nbsp;</Typography> }}
</TableCell> />
<TableCell align="right"> <StatsRow
<Typography noWrap>({numeralWrapper.formatExp(player.agility_exp)} exp)</Typography> name="Augmentations Installed"
</TableCell> color={Settings.theme.primary}
</TableRow> data={{ content: String(player.augmentations.length) }}
<TableRow> />
<TableCell>
<Typography noWrap>Charisma:&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>{numeralWrapper.formatSkill(player.charisma)}&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>({numeralWrapper.formatExp(player.charisma_exp)} exp)</Typography>
</TableCell>
</TableRow>
<Intelligence />
</TableBody> </TableBody>
</Table> </Table>
<br /> </Paper>
<Paper sx={{ p: 1 }}>
<Typography variant="h5">Skills</Typography>
<Table>
<TableBody>
<StatsRow
name="Hacking"
color={Settings.theme.hack}
data={{ level: player.hacking, exp: player.hacking_exp }}
/>
<StatsRow
name="Strength"
color={Settings.theme.combat}
data={{ level: player.strength, exp: player.strength_exp }}
/>
<StatsRow
name="Defense"
color={Settings.theme.combat}
data={{ level: player.defense, exp: player.defense_exp }}
/>
<StatsRow
name="Dexterity"
color={Settings.theme.combat}
data={{ level: player.dexterity, exp: player.dexterity_exp }}
/>
<StatsRow
name="Agility"
color={Settings.theme.combat}
data={{ level: player.agility, exp: player.agility_exp }}
/>
<StatsRow
name="Charisma"
color={Settings.theme.cha}
data={{ level: player.charisma, exp: player.charisma_exp }}
/>
{player.intelligence > 0 && (player.bitNodeN === 5 || SourceFileFlags[5] > 0) && (
<StatsRow
name="Intelligence"
color={Settings.theme.int}
data={{ level: player.intelligence, exp: player.intelligence_exp }}
/>
)}
</TableBody>
</Table>
</Paper>
</Box> </Box>
<Box sx={{ mb: 1 }}>
<Paper sx={{ p: 1 }}>
<Typography variant="h5" color="primary" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
Multipliers
{SourceFileFlags[5] > 0 && (
<Tooltip
title={
<Typography>
Displays your current multipliers.
<br /> <br />
<Typography variant="h4">Multipliers</Typography> <br />
<Box sx={{ mx: 2 }}> When there is a dim number next to a multiplier, that means that the multiplier in question is being
affected by BitNode multipliers.
<br />
<br />
The dim number is the raw multiplier, and the undimmed number is the effective multiplier, as
dictated by the BitNode.
</Typography>
}
>
<Info sx={{ ml: 1, mb: 0.5 }} color="info" />
</Tooltip>
)}
</Typography>
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 1 }}>
<Box>
<MultiplierTable <MultiplierTable
rows={[ rows={[
["Hacking Chance", player.hacking_chance_mult], ["Hacking Chance", player.hacking_chance_mult],
@ -393,32 +372,45 @@ export function CharacterStats(): React.ReactElement {
player.hacking_grow_mult * BitNodeMultipliers.ServerGrowthRate, player.hacking_grow_mult * BitNodeMultipliers.ServerGrowthRate,
], ],
]} ]}
color={Settings.theme.hack}
/> />
<br />
<MultiplierTable <MultiplierTable
rows={[ rows={[
["Hacking Level", player.hacking_mult, player.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier], [
["Hacking Experience", player.hacking_exp_mult, player.hacking_exp_mult * BitNodeMultipliers.HackExpGain], "Hacking Level",
player.hacking_mult,
player.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier,
],
[
"Hacking Experience",
player.hacking_exp_mult,
player.hacking_exp_mult * BitNodeMultipliers.HackExpGain,
],
]} ]}
color={Settings.theme.hack}
/> />
<br />
<MultiplierTable <MultiplierTable
rows={[ rows={[
["Strength Level", player.strength_mult, player.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier], [
"Strength Level",
player.strength_mult,
player.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier,
],
["Strength Experience", player.strength_exp_mult], ["Strength Experience", player.strength_exp_mult],
]} ]}
color={Settings.theme.combat}
/> />
<br />
<MultiplierTable <MultiplierTable
rows={[ rows={[
["Defense Level", player.defense_mult, player.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier], [
"Defense Level",
player.defense_mult,
player.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier,
],
["Defense Experience", player.defense_exp_mult], ["Defense Experience", player.defense_exp_mult],
]} ]}
color={Settings.theme.combat}
/> />
<br />
<MultiplierTable <MultiplierTable
rows={[ rows={[
[ [
@ -428,25 +420,34 @@ export function CharacterStats(): React.ReactElement {
], ],
["Dexterity Experience", player.dexterity_exp_mult], ["Dexterity Experience", player.dexterity_exp_mult],
]} ]}
color={Settings.theme.combat}
/> />
<br />
<MultiplierTable <MultiplierTable
rows={[ rows={[
["Agility Level", player.agility_mult, player.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier], [
"Agility Level",
player.agility_mult,
player.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier,
],
["Agility Experience", player.agility_exp_mult], ["Agility Experience", player.agility_exp_mult],
]} ]}
color={Settings.theme.combat}
/> />
<br />
<MultiplierTable <MultiplierTable
rows={[ rows={[
["Charisma Level", player.charisma_mult, player.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier], [
"Charisma Level",
player.charisma_mult,
player.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier,
],
["Charisma Experience", player.charisma_exp_mult], ["Charisma Experience", player.charisma_exp_mult],
]} ]}
color={Settings.theme.cha}
noMargin
/> />
<br /> </Box>
<Box>
<MultiplierTable <MultiplierTable
rows={[ rows={[
[ [
@ -459,9 +460,8 @@ export function CharacterStats(): React.ReactElement {
["Hacknet Node Core purchase cost", player.hacknet_node_core_cost_mult], ["Hacknet Node Core purchase cost", player.hacknet_node_core_cost_mult],
["Hacknet Node level upgrade cost", player.hacknet_node_level_cost_mult], ["Hacknet Node level upgrade cost", player.hacknet_node_level_cost_mult],
]} ]}
color={Settings.theme.primary}
/> />
<br />
<MultiplierTable <MultiplierTable
rows={[ rows={[
["Company reputation gain", player.company_rep_mult], ["Company reputation gain", player.company_rep_mult],
@ -472,30 +472,47 @@ export function CharacterStats(): React.ReactElement {
], ],
["Salary", player.work_money_mult, player.work_money_mult * BitNodeMultipliers.CompanyWorkMoney], ["Salary", player.work_money_mult, player.work_money_mult * BitNodeMultipliers.CompanyWorkMoney],
]} ]}
color={Settings.theme.money}
/> />
<br />
<MultiplierTable <MultiplierTable
rows={[ rows={[
["Crime success", player.crime_success_mult], ["Crime success", player.crime_success_mult],
["Crime money", player.crime_money_mult, player.crime_money_mult * BitNodeMultipliers.CrimeMoney], ["Crime money", player.crime_money_mult, player.crime_money_mult * BitNodeMultipliers.CrimeMoney],
]} ]}
color={Settings.theme.combat}
/> />
<br /> {player.canAccessBladeburner() && (
<BladeburnerMults /> <MultiplierTable
rows={[
["Bladeburner Success Chance", player.bladeburner_success_chance_mult],
["Bladeburner Max Stamina", player.bladeburner_max_stamina_mult],
["Bladeburner Stamina Gain", player.bladeburner_stamina_gain_mult],
["Bladeburner Field Analysis", player.bladeburner_analysis_mult],
]}
color={Settings.theme.primary}
noMargin
/>
)}
</Box>
</Box>
</Paper>
</Box> </Box>
<br />
<Typography variant="h4">Misc</Typography> <Box sx={{ mb: 1 }}>
<Box sx={{ mx: 2 }}> <Paper sx={{ p: 1 }}>
<Typography>{`Servers owned: ${player.purchasedServers.length} / ${getPurchaseServerLimit()}`}</Typography> <Typography variant="h5">Time Played</Typography>
<Hacknet /> <Table>
<Typography>{`Augmentations installed: ${player.augmentations.length}`}</Typography> <TableBody>
<StatsTable rows={timeRows} /> {timeRows.map(([name, content]) => (
<StatsRow key={name} name={name} color={Settings.theme.primary} data={{ content: content }} />
))}
</TableBody>
</Table>
</Paper>
</Box> </Box>
<br />
<CurrentBitNode /> <CurrentBitNode />
<MoneyModal open={moneyOpen} onClose={() => setMoneyOpen(false)} /> <MoneyModal open={moneyOpen} onClose={() => setMoneyOpen(false)} />
</> <EmployersModal open={employersOpen} onClose={() => setEmployersOpen(false)} />
</Container>
); );
} }

@ -491,7 +491,6 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
}} }}
installAugmentationsFn={() => { installAugmentationsFn={() => {
installAugmentations(); installAugmentations();
Router.toTerminal();
}} }}
/> />
); );

@ -64,6 +64,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
const importInput = useRef<HTMLInputElement>(null); const importInput = useRef<HTMLInputElement>(null);
const [execTime, setExecTime] = useState(Settings.CodeInstructionRunTime); const [execTime, setExecTime] = useState(Settings.CodeInstructionRunTime);
const [recentScriptsSize, setRecentScriptsSize] = useState(Settings.MaxRecentScriptsCapacity);
const [logSize, setLogSize] = useState(Settings.MaxLogCapacity); const [logSize, setLogSize] = useState(Settings.MaxLogCapacity);
const [portSize, setPortSize] = useState(Settings.MaxPortCapacity); const [portSize, setPortSize] = useState(Settings.MaxPortCapacity);
const [terminalSize, setTerminalSize] = useState(Settings.MaxTerminalCapacity); const [terminalSize, setTerminalSize] = useState(Settings.MaxTerminalCapacity);
@ -79,6 +80,11 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
Settings.CodeInstructionRunTime = newValue as number; Settings.CodeInstructionRunTime = newValue as number;
} }
function handleRecentScriptsSizeChange(event: any, newValue: number | number[]): void {
setRecentScriptsSize(newValue as number);
Settings.MaxRecentScriptsCapacity = newValue as number;
}
function handleLogSizeChange(event: any, newValue: number | number[]): void { function handleLogSizeChange(event: any, newValue: number | number[]): void {
setLogSize(newValue as number); setLogSize(newValue as number);
Settings.MaxLogCapacity = newValue as number; Settings.MaxLogCapacity = newValue as number;
@ -176,6 +182,24 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
max={100} max={100}
valueLabelDisplay="auto" valueLabelDisplay="auto"
/> />
<Tooltip
title={
<Typography>
The maximum number of recently killed script entries being tracked. Setting this too high can
cause the game to use a lot of memory.
</Typography>
}
>
<Typography>Recently killed scripts size</Typography>
</Tooltip>
<Slider
value={recentScriptsSize}
onChange={handleRecentScriptsSizeChange}
step={25}
min={0}
max={500}
valueLabelDisplay="auto"
/>
<Tooltip <Tooltip
title={ title={
<Typography> <Typography>

@ -17,9 +17,10 @@ interface IProps {
color: string; color: string;
classes?: any; classes?: any;
data: ITableRowData; data: ITableRowData;
children?: React.ReactElement;
} }
export const StatsRow = ({ name, color, classes = useStyles(), data }: IProps): React.ReactElement => { export const StatsRow = ({ name, color, classes = useStyles(), children, data }: IProps): React.ReactElement => {
let content; let content;
if (data.content !== undefined) { if (data.content !== undefined) {
@ -36,7 +37,8 @@ export const StatsRow = ({ name, color, classes = useStyles(), data }: IProps):
<Typography style={{ color: color }}>{name}</Typography> <Typography style={{ color: color }}>{name}</Typography>
</TableCell> </TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}> <TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography style={{ color: color }}>{content}</Typography> {content ? <Typography style={{ color: color }}>{content}</Typography> : <></>}
{children}
</TableCell> </TableCell>
</TableRow> </TableRow>
); );

@ -42,10 +42,10 @@ describe("determineAllPossibilitiesForTabCompletion", function () {
requiredHackingSkill: 1, requiredHackingSkill: 1,
serverGrowth: 3000, serverGrowth: 3000,
}); });
Player.getHomeComputer().serversOnNetwork.push(closeServer.ip); Player.getHomeComputer().serversOnNetwork.push(closeServer.hostname);
closeServer.serversOnNetwork.push(Player.getHomeComputer().ip); closeServer.serversOnNetwork.push(Player.getHomeComputer().hostname);
closeServer.serversOnNetwork.push(farServer.ip); closeServer.serversOnNetwork.push(farServer.hostname);
farServer.serversOnNetwork.push(closeServer.ip); farServer.serversOnNetwork.push(closeServer.hostname);
AddToAllServers(closeServer); AddToAllServers(closeServer);
AddToAllServers(farServer); AddToAllServers(farServer);
}); });

22
tools/package-electron.sh Normal file

@ -0,0 +1,22 @@
#!/bin/sh
set -euxo pipefail
# Clear out any files remaining from old builds
rm -rf .package
mkdir -p .package/dist/ || true
cp index.html .package
cp favicon.ico .package
cp -r electron/* .package
cp -r dist .package
# Install electron sub-dependencies
cd electron
npm install
cd ..
BUILD_PLATFORM="${1:-"all"}"
# And finally build the app.
npm run electron:packager-$BUILD_PLATFORM

@ -11,7 +11,7 @@ module.exports = (env, argv) => {
const runInContainer = (env || {}).runInContainer === true; const runInContainer = (env || {}).runInContainer === true;
const isDevelopment = argv.mode === "development"; const isDevelopment = argv.mode === "development";
const isFastRefresh = argv.fast === "true"; const isFastRefresh = argv.fast === "true";
const outputDirectory = isDevServer ? "dist-dev" : "dist"; const outputDirectory = "dist";
const entry = "./src/index.tsx"; const entry = "./src/index.tsx";
const statsConfig = { const statsConfig = {
@ -46,21 +46,7 @@ module.exports = (env, argv) => {
// https://stackoverflow.com/a/38401256 // https://stackoverflow.com/a/38401256
const commitHash = require("child_process").execSync("git rev-parse --short HEAD").toString().trim(); const commitHash = require("child_process").execSync("git rev-parse --short HEAD").toString().trim();
return { const htmlConfig = {
plugins: [
new webpack.DefinePlugin({
"process.env.NODE_ENV": isDevelopment ? '"development"' : '"production"',
}),
// http://stackoverflow.com/questions/29080148/expose-jquery-to-real-window-object-with-webpack
new webpack.ProvidePlugin({
// Automtically detect jQuery and $ as free var in modules
// and inject the jquery library
// This is required by many jquery plugins
jquery: "jquery",
jQuery: "jquery",
$: "jquery",
}),
new HtmlWebpackPlugin({
title: "Bitburner", title: "Bitburner",
template: "src/index.html", template: "src/index.html",
favicon: "favicon.ico", favicon: "favicon.ico",
@ -97,7 +83,26 @@ module.exports = (env, argv) => {
sortClassName: false, sortClassName: false,
useShortDoctype: false, useShortDoctype: false,
}, },
};
if (!isDevelopment) {
htmlConfig.filename = "../index.html";
}
return {
plugins: [
new webpack.DefinePlugin({
"process.env.NODE_ENV": isDevelopment ? '"development"' : '"production"',
}), }),
// http://stackoverflow.com/questions/29080148/expose-jquery-to-real-window-object-with-webpack
new webpack.ProvidePlugin({
// Automtically detect jQuery and $ as free var in modules
// and inject the jquery library
// This is required by many jquery plugins
jquery: "jquery",
jQuery: "jquery",
$: "jquery",
}),
new HtmlWebpackPlugin(htmlConfig),
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({
filename: "[name].css", filename: "[name].css",
}), }),
@ -136,7 +141,7 @@ module.exports = (env, argv) => {
// }, // },
entry: entry, entry: entry,
output: { output: {
path: path.resolve(__dirname, "./"), path: path.resolve(__dirname, outputDirectory),
filename: "[name].bundle.js", filename: "[name].bundle.js",
}, },
module: { module: {
@ -161,7 +166,7 @@ module.exports = (env, argv) => {
loader: "file-loader", loader: "file-loader",
options: { options: {
name: "[contenthash].[ext]", name: "[contenthash].[ext]",
outputPath: "dist/images", outputPath: "images",
}, },
}, },
], ],
@ -184,7 +189,7 @@ module.exports = (env, argv) => {
cacheGroups: { cacheGroups: {
vendor: { vendor: {
test: /[\\/]node_modules[\\/]/, test: /[\\/]node_modules[\\/]/,
name: `${outputDirectory}/vendor`, name: `vendor`,
chunks: "all", chunks: "all",
}, },
}, },