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 ie <= 11

@ -353,6 +353,7 @@ module.exports = {
"no-useless-constructor": [
"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-ignore": "off",
"@typescript-eslint/camelcase": "off",

11
dist/bitburner.d.ts vendored

@ -2101,7 +2101,7 @@ export declare interface Hacknet {
* // NS1:
* var upgradeName = "Sell for Corporation Funds";
* if (hacknet.numHashes() > hacknet.hashCost(upgradeName)) {
* hacknet.spendHashes(upgName);
* hacknet.spendHashes(upgradeName);
* }
* ```
* @example
@ -2109,7 +2109,7 @@ export declare interface Hacknet {
* // NS2:
* const upgradeName = "Sell for Corporation Funds";
* 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.
@ -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.
*
* @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.
*/
hackAnalyzeSecurity(threads: number): number;
hackAnalyzeSecurity(threads: number, hostname?: string): number;
/**
* Get the chance of successfully hacking a server.
@ -4671,8 +4672,10 @@ export declare interface Office {
maxMor: number;
/** Name of all the employees */
employees: string[];
/** Positions of the employees */
/** Production of the employees */
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>
<body>
<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>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

26
package-lock.json generated

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

@ -115,15 +115,15 @@
"start:container": "webpack-dev-server --progress --env.devServer --mode development --env.runInContainer",
"build": "webpack --mode production",
"build:dev": "webpack --mode development",
"lint": "eslint --fix . --ext js,jsx,ts,tsx",
"lint:report": "eslint --ext js,jsx,ts,tsx .",
"lint": "eslint --fix --ext js,jsx,ts,tsx --max-warnings 0 .",
"lint:report": "eslint --ext js,jsx,ts,tsx --max-warnings 0 .",
"preinstall": "node ./tools/engines-check/engines-check.js",
"postinstall": "cd electron && npm install",
"test": "jest",
"test:watch": "jest --watch",
"watch": "webpack --watch --mode production",
"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-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",

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

@ -7,7 +7,7 @@ import React, { useState, useEffect } from "react";
import { InstalledAugmentations } from "./InstalledAugmentations";
import { PlayerMultipliers } from "./PlayerMultipliers";
import { PurchasedAugmentations } from "./PurchasedAugmentations";
import { SourceFiles } from "./SourceFiles";
import { SourceFilesElement } from "./SourceFiles";
import { canGetBonus } from "../../ExportBonus";
import { use } from "../../ui/Context";
@ -16,8 +16,55 @@ import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box";
import Paper from "@mui/material/Paper";
import Container from "@mui/material/Container";
import { Settings } from "../../Settings/Settings";
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 {
exportGameFn: () => void;
@ -55,81 +102,113 @@ export function AugmentationsRoot(props: IProps): React.ReactElement {
}
return (
<>
<Container disableGutters maxWidth="lg" sx={{ mx: 0 }}>
<Typography variant="h4">Augmentations</Typography>
<Box mx={2}>
<Typography>
Below is a list of all Augmentations you have purchased but not yet installed. Click the button below to
install them.
</Typography>
<Typography>WARNING: Installing your Augmentations resets most of your progress, including:</Typography>
<br />
<Typography>- Stats/Skill levels and Experience</Typography>
<Typography>- Money</Typography>
<Typography>- Scripts on every computer but your home computer</Typography>
<Typography>- Purchased servers</Typography>
<Typography>- Hacknet Nodes</Typography>
<Typography>- Faction/Company reputation</Typography>
<Typography>- Stocks</Typography>
<br />
<Typography>
Installing Augmentations lets you start over with the perks and benefits granted by all of the Augmentations
you have ever installed. Also, you will keep any scripts and RAM/Core upgrades on your home computer (but you
will lose all programs besides NUKE.exe)
</Typography>
<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>
Below is a list of all Augmentations you have purchased but not yet installed. Click the button
below to install them.
</Typography>
<Typography>
WARNING: Installing your Augmentations resets most of your progress, including:
</Typography>
<br />
<Typography>- Stats/Skill levels and Experience</Typography>
<Typography>- Money</Typography>
<Typography>- Scripts on every computer but your home computer</Typography>
<Typography>- Purchased servers</Typography>
<Typography>- Hacknet Nodes</Typography>
<Typography>- Faction/Company reputation</Typography>
<Typography>- Stocks</Typography>
<br />
<Typography>
Installing Augmentations lets you start over with the perks and benefits granted by all of the
Augmentations you have ever installed. Also, you will keep any scripts and RAM/Core upgrades on your
home computer (but you will lose all programs besides NUKE.exe)
</Typography>
</>
}
>
<Info sx={{ ml: 1, mb: 0.5 }} color="info" />
</Tooltip>
</Typography>
<ConfirmationModal
open={installOpen}
onClose={() => setInstallOpen(false)}
onConfirm={props.installAugmentationsFn}
confirmationText={
<>
Installing will reset
<br />
<br />- money
<br />- skill / experience
<br />- every server except home
<br />- factions and reputation
<br />
<br />
You will keep:
<br />
<br />- All scripts on home
<br />- home ram and cores
<br />
<br />
It is recommended to install several Augmentations at once. Preferably everything from any faction of
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>}>
<Button sx={{ width: "100%" }} onClick={doExport} color="error">
Backup Save {exportBonusStr()}
</Button>
</Tooltip>
</Box>
</Paper>
{player.queuedAugmentations.length > 0 ? (
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 3fr" }}>
<PurchasedAugmentations />
<PlayerMultipliers />
</Box>
) : (
<Paper sx={{ p: 1 }}>
<Typography>No Augmentations have been purchased yet</Typography>
</Paper>
)}
</Box>
<Typography variant="h4" color="primary">
Purchased Augmentations
</Typography>
<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>
<ConfirmationModal
open={installOpen}
onClose={() => setInstallOpen(false)}
onConfirm={props.installAugmentationsFn}
confirmationText={
<>
Installing will reset
<br />
<br />- money
<br />- skill / experience
<br />- every server except home
<br />- factions and reputation
<br />
<br />
You will keep:
<br />
<br />- All scripts on home
<br />- home ram and cores
<br />
<br />
It is recommended to install several Augmentations at once. Preferably everything from any faction of your
choosing.
</>
}
/>
<Tooltip title={<Typography>It's always a good idea to backup/export your save!</Typography>}>
<Button sx={{ mx: 2 }} onClick={doExport} color="error">
Backup Save {exportBonusStr()}
</Button>
</Tooltip>
<PurchasedAugmentations />
<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>
<Typography variant="h4">Installed Augmentations</Typography>
<Box mx={2}>
<Typography>
List of all Augmentations that have been installed. You have gained the effects of these.
</Typography>
<Box>
<InstalledAugmentations />
</Box>
<PlayerMultipliers />
<SourceFiles />
</>
<SourceFilesElement />
</Container>
);
}

@ -5,28 +5,23 @@
* It also contains 'configuration' buttons that allow you to change how the
* 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 { AugmentationAccordion } from "../../ui/React/AugmentationAccordion";
import { Augmentations } from "../Augmentations";
import { AugmentationNames } from "../data/AugmentationNames";
import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums";
import { Settings } from "../../Settings/Settings";
import { use } from "../../ui/Context";
import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums";
import Button from "@mui/material/Button";
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";
import { Augmentations } from "../Augmentations";
import { AugmentationNames } from "../data/AugmentationNames";
export function InstalledAugmentations(): React.ReactElement {
const setRerender = useState(true)[1];
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) {
sourceAugs.sort((aug1, aug2) => {
@ -49,59 +44,60 @@ export function InstalledAugmentations(): React.ReactElement {
}
return (
<>
<Tooltip title={"Sorts the Augmentations alphabetically in numeral order"}>
<Button onClick={sortInOrder}>Sort in Order</Button>
</Tooltip>
<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>
</Tooltip>
<List dense>
{player.entropy > 0 &&
(() => {
const [open, setOpen] = useState(false);
return (
<Box component={Paper}>
<ListItemButton onClick={() => setOpen((old) => !old)}>
<ListItemText
primary={
<Typography color={Settings.theme.hp} style={{ whiteSpace: "pre-wrap" }}>
Entropy Virus - Level {player.entropy}
</Typography>
}
/>
{open ? (
<ExpandLess sx={{ color: Settings.theme.hp }} />
) : (
<ExpandMore sx={{ color: Settings.theme.hp }} />
)}
<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"}>
<Button sx={{ width: "100%" }} onClick={sortInOrder}>
Sort in Order
</Button>
</Tooltip>
<Tooltip title={"Sorts the Augmentations based on when you acquired them (same as default)"}>
<Button sx={{ width: "100%" }} onClick={sortByAcquirementTime}>
Sort by Time of Acquirement
</Button>
</Tooltip>
</Box>
</Paper>
{sourceAugs.length > 0 ? (
<Paper sx={{ display: "grid", gridTemplateColumns: "1fr 3fr" }}>
<Box>
<List sx={{ height: 400, overflowY: "scroll", borderRight: `1px solid ${Settings.theme.welllight}` }}>
{sourceAugs.map((k, i) => (
<ListItemButton key={i + 1} onClick={() => setSelectedAug(k)} selected={selectedAug === k}>
<Typography>{k.name}</Typography>
</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>
);
})()}
))}
</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];
{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>
</>
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
*/
import { DoubleArrow } from "@mui/icons-material";
import { List, ListItem, ListItemText, Paper, Typography } from "@mui/material";
import * as React from "react";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { Player } from "../../Player";
import { Settings } from "../../Settings/Settings";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { numeralWrapper } from "../../ui/numeralFormat";
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 {
const augP: any = {};
interface IAugmentedStats {
[index: string]: number;
}
function calculateAugmentedStats(): IAugmentedStats {
const augP: IAugmentedStats = {};
for (const aug of Player.queuedAugmentations) {
const augObj = Augmentations[aug.name];
for (const mult of Object.keys(augObj.mults)) {
@ -25,268 +27,249 @@ function calculateAugmentedStats(): any {
return augP;
}
function Improvements({ r, m }: { r: number; m: number }): React.ReactElement {
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 {
interface IBitNodeModifiedStatsProps {
base: number;
mult: number;
color: string;
}
function BN5Stat(props: IBN5StatsProps): React.ReactElement {
if (props.mult === 1) return <></>;
return <>({numeralWrapper.formatPercentage(props.base * props.mult)})</>;
}
function BitNodeModifiedStats(props: IBitNodeModifiedStatsProps): React.ReactElement {
// If player doesn't have SF5 or if the property isn't affected by BitNode mults
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 (
<Table size="small" padding="none">
<TableBody>
{rows.map((r: any) => (
<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>
</TableCell>
<Improvements r={r[2]} m={r[3]} />
</TableRow>
))}
</TableBody>
</Table>
<Typography color={props.color}>
<span style={{ opacity: 0.5 }}>{numeralWrapper.formatPercentage(props.base)}</span>{" "}
{numeralWrapper.formatPercentage(props.base * props.mult)}
</Typography>
);
}
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 {
const mults = calculateAugmentedStats();
function BladeburnerMults(): React.ReactElement {
if (!Player.canAccessBladeburner()) return <></>;
return (
<>
<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 />
</>
// Column data is a bit janky, so it's set up here to allow for
// easier logic in setting up the layout
const leftColData: MultiplierListItemData[] = [
...[
["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 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 Level ",
Player.hacking_mult,
Player.hacking_mult * mults.hacking_mult,
BitNodeMultipliers.HackingLevelMultiplier,
],
[
"Hacking Experience ",
Player.hacking_exp_mult,
Player.hacking_exp_mult * mults.hacking_exp_mult,
BitNodeMultipliers.HackExpGain,
],
].map((data): MultiplierListItemData => (data as any).concat([Settings.theme.hack])),
...[
[
"Strength Level ",
Player.strength_mult,
Player.strength_mult * mults.strength_mult,
BitNodeMultipliers.StrengthLevelMultiplier,
],
["Strength Experience ", Player.strength_exp_mult, Player.strength_exp_mult * mults.strength_exp_mult, 1],
[
"Defense Level ",
Player.defense_mult,
Player.defense_mult * mults.defense_mult,
BitNodeMultipliers.DefenseLevelMultiplier,
],
["Defense Experience ", Player.defense_exp_mult, Player.defense_exp_mult * mults.defense_exp_mult, 1],
[
"Dexterity Level ",
Player.dexterity_mult,
Player.dexterity_mult * mults.dexterity_mult,
BitNodeMultipliers.DexterityLevelMultiplier,
],
["Dexterity Experience ", Player.dexterity_exp_mult, Player.dexterity_exp_mult * mults.dexterity_exp_mult, 1],
[
"Agility Level ",
Player.agility_mult,
Player.agility_mult * mults.agility_mult,
BitNodeMultipliers.AgilityLevelMultiplier,
],
["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])),
[
"Charisma Level ",
Player.charisma_mult,
Player.charisma_mult * mults.charisma_mult,
BitNodeMultipliers.CharismaLevelMultiplier,
Settings.theme.cha,
],
[
"Charisma Experience ",
Player.charisma_exp_mult,
Player.charisma_exp_mult * mults.charisma_exp_mult,
1,
Settings.theme.cha,
],
];
const rightColData: MultiplierListItemData[] = [
...[
[
"Hacknet Node production ",
Player.hacknet_node_money_mult,
Player.hacknet_node_money_mult * mults.hacknet_node_money_mult,
BitNodeMultipliers.HacknetNodeMoney,
],
[
"Hacknet Node purchase cost ",
Player.hacknet_node_purchase_cost_mult,
Player.hacknet_node_purchase_cost_mult * mults.hacknet_node_purchase_cost_mult,
1,
],
[
"Hacknet Node RAM upgrade cost ",
Player.hacknet_node_ram_cost_mult,
Player.hacknet_node_ram_cost_mult * mults.hacknet_node_ram_cost_mult,
1,
],
[
"Hacknet Node Core purchase cost ",
Player.hacknet_node_core_cost_mult,
Player.hacknet_node_core_cost_mult * mults.hacknet_node_core_cost_mult,
1,
],
[
"Hacknet Node level upgrade cost ",
Player.hacknet_node_level_cost_mult,
Player.hacknet_node_level_cost_mult * mults.hacknet_node_level_cost_mult,
1,
],
["Company reputation gain ", Player.company_rep_mult, Player.company_rep_mult * mults.company_rep_mult, 1],
[
"Faction reputation gain ",
Player.faction_rep_mult,
Player.faction_rep_mult * mults.faction_rep_mult,
BitNodeMultipliers.FactionWorkRepGain,
],
].map((data): MultiplierListItemData => (data as any).concat([Settings.theme.primary])),
[
"Salary ",
Player.work_money_mult,
Player.work_money_mult * mults.work_money_mult,
BitNodeMultipliers.CompanyWorkMoney,
Settings.theme.money,
],
[
"Crime success ",
Player.crime_success_mult,
Player.crime_success_mult * mults.crime_success_mult,
1,
Settings.theme.combat,
],
[
"Crime money ",
Player.crime_money_mult,
Player.crime_money_mult * mults.crime_money_mult,
BitNodeMultipliers.CrimeMoney,
Settings.theme.money,
],
];
if (Player.canAccessBladeburner()) {
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 (
<>
<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 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 Growth ", Player.hacking_grow_mult, Player.hacking_grow_mult * mults.hacking_grow_mult, 1],
]}
/>
<br />
<MultiplierTable
rows={[
[
"Hacking Level ",
Player.hacking_mult,
Player.hacking_mult * mults.hacking_mult,
BitNodeMultipliers.HackingLevelMultiplier,
],
[
"Hacking Experience ",
Player.hacking_exp_mult,
Player.hacking_exp_mult * mults.hacking_exp_mult,
BitNodeMultipliers.HackExpGain,
],
]}
/>
<br />
<MultiplierTable
rows={[
[
"Strength Level ",
Player.strength_mult,
Player.strength_mult * mults.strength_mult,
BitNodeMultipliers.StrengthLevelMultiplier,
],
["Strength Experience ", Player.strength_exp_mult, Player.strength_exp_mult * mults.strength_exp_mult, 1],
]}
/>
<br />
<MultiplierTable
rows={[
[
"Defense Level ",
Player.defense_mult,
Player.defense_mult * mults.defense_mult,
BitNodeMultipliers.DefenseLevelMultiplier,
],
["Defense Experience ", Player.defense_exp_mult, Player.defense_exp_mult * mults.defense_exp_mult, 1],
]}
/>
<br />
<MultiplierTable
rows={[
[
"Dexterity Level ",
Player.dexterity_mult,
Player.dexterity_mult * mults.dexterity_mult,
BitNodeMultipliers.DexterityLevelMultiplier,
],
[
"Dexterity Experience ",
Player.dexterity_exp_mult,
Player.dexterity_exp_mult * mults.dexterity_exp_mult,
1,
],
]}
/>
<br />
<MultiplierTable
rows={[
[
"Agility Level ",
Player.agility_mult,
Player.agility_mult * mults.agility_mult,
BitNodeMultipliers.AgilityLevelMultiplier,
],
["Agility Experience ", Player.agility_exp_mult, Player.agility_exp_mult * mults.agility_exp_mult, 1],
]}
/>
<br />
<MultiplierTable
rows={[
[
"Charisma Level ",
Player.charisma_mult,
Player.charisma_mult * mults.charisma_mult,
BitNodeMultipliers.CharismaLevelMultiplier,
],
["Charisma Experience ", Player.charisma_exp_mult, Player.charisma_exp_mult * mults.charisma_exp_mult, 1],
]}
/>
<br />
<MultiplierTable
rows={[
[
"Hacknet Node production ",
Player.hacknet_node_money_mult,
Player.hacknet_node_money_mult * mults.hacknet_node_money_mult,
BitNodeMultipliers.HacknetNodeMoney,
],
[
"Hacknet Node purchase cost ",
Player.hacknet_node_purchase_cost_mult,
Player.hacknet_node_purchase_cost_mult * mults.hacknet_node_purchase_cost_mult,
1,
],
[
"Hacknet Node RAM upgrade cost ",
Player.hacknet_node_ram_cost_mult,
Player.hacknet_node_ram_cost_mult * mults.hacknet_node_ram_cost_mult,
1,
],
[
"Hacknet Node Core purchase cost ",
Player.hacknet_node_core_cost_mult,
Player.hacknet_node_core_cost_mult * mults.hacknet_node_core_cost_mult,
1,
],
[
"Hacknet Node level upgrade cost ",
Player.hacknet_node_level_cost_mult,
Player.hacknet_node_level_cost_mult * mults.hacknet_node_level_cost_mult,
1,
],
]}
/>
<br />
<MultiplierTable
rows={[
["Company reputation gain ", Player.company_rep_mult, Player.company_rep_mult * mults.company_rep_mult, 1],
[
"Faction reputation gain ",
Player.faction_rep_mult,
Player.faction_rep_mult * mults.faction_rep_mult,
BitNodeMultipliers.FactionWorkRepGain,
],
[
"Salary ",
Player.work_money_mult,
Player.work_money_mult * mults.work_money_mult,
BitNodeMultipliers.CompanyWorkMoney,
],
]}
/>
<br />
<MultiplierTable
rows={[
["Crime success ", Player.crime_success_mult, Player.crime_success_mult * mults.crime_success_mult, 1],
[
"Crime money ",
Player.crime_money_mult,
Player.crime_money_mult * mults.crime_money_mult,
BitNodeMultipliers.CrimeMoney,
],
]}
/>
<br />
<BladeburnerMults />
</Box>
</>
<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)
* Augmentations on the Augmentations UI.
*/
import { List, ListItemText, Paper, Tooltip, Typography } from "@mui/material";
import * as React from "react";
import { Player } from "../../Player";
import { Augmentations } from "../Augmentations";
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 {
const augs: React.ReactElement[] = [];
@ -23,14 +20,48 @@ export function PurchasedAugmentations(): React.ReactElement {
}
for (let i = 0; i < Player.queuedAugmentations.length; i++) {
const ownedAug = Player.queuedAugmentations[i];
let displayName = ownedAug.name;
if (ownedAug.name === AugmentationNames.NeuroFluxGovernor && i !== nfgIndex) continue;
const aug = Augmentations[ownedAug.name];
let level = null;
if (ownedAug.name === AugmentationNames.NeuroFluxGovernor) {
level = ownedAug.level;
displayName += ` - Level ${level}`;
}
augs.push(<AugmentationAccordion key={aug.name} aug={aug} level={level} />);
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 <List dense>{augs}</List>;
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 { SourceFileMinus1 } from "./SourceFileMinus1";
import { OwnedSourceFiles } from "./OwnedSourceFiles";
import List from "@mui/material/List";
import Typography from "@mui/material/Typography";
import { ListItemButton, ListItemText, Paper } from "@mui/material";
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";
interface SfMinus1 {
info: React.ReactElement;
n: number;
name: string;
lvl: number;
}
const safeGetSf = (sfNum: number): SourceFile | SfMinus1 | null => {
if (sfNum === -1) {
const sfMinus1: SfMinus1 = {
info: (
<>
This Source-File can only be acquired with obscure knowledge of the game, javascript, and the web ecosystem.
<br />
<br />
It increases all of the player's multipliers by 0.1%
<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]);
export function SourceFiles(): React.ReactElement {
return (
<>
<Typography variant="h4">Source Files</Typography>
<Box mx={2}>
<List dense>
<SourceFileMinus1 />
<OwnedSourceFiles />
</List>
</Box>
</>
<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>
</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,
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 = {}) {
this.loc = params.loc ? params.loc : "";
@ -48,6 +58,8 @@ export class OfficeSpace {
}
}
this.calculateTotalEmployees();
// Process Office properties
this.maxEne = 100;
this.maxHap = 100;
@ -101,6 +113,19 @@ export class OfficeSpace {
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 {
//Reset
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 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 Typography from "@mui/material/Typography";
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";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
player: IPlayer;
@ -39,50 +38,46 @@ export function Augmentations(props: IProps): React.ReactElement {
props.player.augmentations = [];
}
function clearQueuedAugs(): void {
props.player.queuedAugmentations = [];
}
return (
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<AccordionSummary expandIcon={<ExpandMore />}>
<Typography>Augmentations</Typography>
</AccordionSummary>
<AccordionDetails>
<table>
<tbody>
<tr>
<td>
<Typography>Aug:</Typography>
</td>
<td>
<Select
onChange={setAugmentationDropdown}
value={augmentation}
startAdornment={
<>
<IconButton onClick={queueAllAugs} size="large">
<ReplyAllIcon />
</IconButton>
<IconButton onClick={queueAug} size="large">
<ReplyIcon />
</IconButton>
</>
}
endAdornment={
<>
<IconButton onClick={clearAugs} size="large">
<ClearIcon />
</IconButton>
</>
}
>
{Object.values(AugmentationNames).map((aug) => (
<MenuItem key={aug} value={aug}>
{aug}
</MenuItem>
))}
</Select>
</td>
</tr>
</tbody>
</table>
<Select
onChange={setAugmentationDropdown}
value={augmentation}
startAdornment={
<>
<IconButton onClick={queueAllAugs} size="large">
<ReplyAll />
</IconButton>
<IconButton onClick={queueAug} size="large">
<Reply />
</IconButton>
</>
}
endAdornment={
<>
<IconButton onClick={clearAugs} size="large">
<Clear />
</IconButton>
</>
}
>
{Object.values(AugmentationNames).map((aug) => (
<MenuItem key={aug} value={aug}>
{aug}
</MenuItem>
))}
</Select>
<Button sx={{ display: "block" }} onClick={clearQueuedAugs}>
Clear Queued Augmentations
</Button>
</AccordionDetails>
</Accordion>
);

@ -81,7 +81,6 @@ export function hasAugmentationPrereqs(aug: Augmentation): boolean {
}
export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = false): string {
const factionInfo = fac.getInfo();
const hasPrereqs = hasAugmentationPrereqs(aug);
if (!hasPrereqs) {
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 {
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;
if (sing) {
return txt;
@ -102,14 +101,14 @@ export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = fal
return 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);
if (aug.name == AugmentationNames.NeuroFluxGovernor) {
queuedAugmentation.level = getNextNeurofluxLevel();
}
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 (aug.name == AugmentationNames.NeuroFluxGovernor) {

@ -2,20 +2,20 @@ import React from "react";
import { IMap } from "../types";
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
*/
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.
*/
@ -31,11 +31,6 @@ export class FactionInfo {
*/
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.
*/
@ -56,32 +51,19 @@ export class FactionInfo {
*/
special: boolean;
constructor(
infoText: JSX.Element,
enemies: string[],
offerHackingMission: boolean,
offerHackingWork: boolean,
offerFieldWork: boolean,
offerSecurityWork: boolean,
special: boolean,
keep: boolean,
) {
this.infoText = infoText;
this.enemies = enemies;
this.offerHackingMission = offerHackingMission;
this.offerHackingWork = offerHackingWork;
this.offerFieldWork = offerFieldWork;
this.offerSecurityWork = offerSecurityWork;
constructor(params: FactionInfoParams) {
this.infoText = params.infoText ?? <></>;
this.enemies = params.enemies ?? [];
this.offerHackingWork = params.offerHackingWork ?? false;
this.offerFieldWork = params.offerFieldWork ?? false;
this.offerSecurityWork = params.offerSecurityWork ?? false;
// These are always all 1 for now.
this.augmentationPriceMult = 1;
this.augmentationRepRequirementMult = 1;
this.keep = keep;
this.special = special;
this.keep = params.keepOnInstall ?? false;
this.special = params.special ?? false;
}
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
export const FactionInfos: IMap<FactionInfo> = {
// 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
from this chaos, we are the invisible hand that guides them to order.{" "}
</>
),
[],
true,
true,
true,
false,
false,
false,
),
offerHackingWork: true,
offerFieldWork: true,
}),
[FactionNames.Daedalus]: new FactionInfo(
<>Yesterday we obeyed kings and bent our necks to emperors. Today we kneel only to truth.</>,
[],
true,
true,
true,
false,
false,
false,
),
[FactionNames.Daedalus]: new FactionInfo({
infoText: <>Yesterday we obeyed kings and bent our necks to emperors. Today we kneel only to truth.</>,
offerHackingWork: true,
offerFieldWork: true,
}),
[FactionNames.TheCovenant]: new FactionInfo(
(
[FactionNames.TheCovenant]: new FactionInfo({
infoText: (
<>
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.
@ -128,35 +100,27 @@ export const FactionInfos: IMap<FactionInfo> = {
Only then can you discover immortality.
</>
),
[],
true,
true,
true,
false,
false,
false,
),
offerHackingWork: true,
offerFieldWork: true,
}),
// 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
our wide range of Internet-related software and commercial hardware, {FactionNames.ECorp} makes the world's
information universally accessible.
</>
),
[],
true,
true,
true,
true,
false,
true,
),
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
keepOnInstall: 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
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.
</>
),
[],
true,
true,
true,
true,
false,
true,
),
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
keepOnInstall: true,
}),
[FactionNames.BachmanAssociates]: new FactionInfo(
(
[FactionNames.BachmanAssociates]: new FactionInfo({
infoText: (
<>
Where Law and Business meet - thats where we are.
<br />
@ -185,112 +146,87 @@ export const FactionInfos: IMap<FactionInfo> = {
Legal Insight - Business Instinct - Innovative Experience.
</>
),
[],
true,
true,
true,
true,
false,
true,
),
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
keepOnInstall: true,
}),
[FactionNames.BladeIndustries]: new FactionInfo(
<>Augmentation is Salvation.</>,
[],
true,
true,
true,
true,
false,
true,
),
[FactionNames.BladeIndustries]: new FactionInfo({
infoText: <>Augmentation is Salvation.</>,
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
keepOnInstall: 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
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.
</>
),
[],
true,
true,
true,
true,
false,
true,
),
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
keepOnInstall: true,
}),
[FactionNames.ClarkeIncorporated]: new FactionInfo(
<>The Power of the Genome - Unlocked.</>,
[],
true,
true,
true,
true,
false,
true,
),
[FactionNames.ClarkeIncorporated]: new FactionInfo({
infoText: <>The Power of the Genome - Unlocked.</>,
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
keepOnInstall: true,
}),
[FactionNames.OmniTekIncorporated]: new FactionInfo(
<>Simply put, our mission is to design and build robots that make a difference.</>,
[],
true,
true,
true,
true,
false,
true,
),
[FactionNames.OmniTekIncorporated]: new FactionInfo({
infoText: <>Simply put, our mission is to design and build robots that make a difference.</>,
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
keepOnInstall: 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
deep learning and innovative ideas. And improved by iteration. That's {FactionNames.FourSigma}.
</>
),
[],
true,
true,
true,
true,
false,
true,
),
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
keepOnInstall: true,
}),
[FactionNames.KuaiGongInternational]: new FactionInfo(
<>Dream big. Work hard. Make history.</>,
[],
true,
true,
true,
true,
false,
true,
),
[FactionNames.KuaiGongInternational]: new FactionInfo({
infoText: <>Dream big. Work hard. Make history.</>,
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
keepOnInstall: true,
}),
// 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
would be necessary to create them. And now we can.
</>
),
[],
true,
true,
false,
true,
false,
true,
),
offerHackingWork: true,
offerSecurityWork: true,
keepOnInstall: true,
}),
// 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
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.
</>
),
[],
true,
true,
false,
false,
false,
false,
),
offerHackingWork: true,
}),
[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.
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.
</>
),
[],
true,
true,
true,
false,
false,
false,
),
offerHackingWork: true,
offerFieldWork: true,
}),
// prettier-ignore
[FactionNames.NiteSec]: new FactionInfo(<>
[FactionNames.NiteSec]: new FactionInfo({
infoText:(<>
{" __..__ "}<br />
{" _.nITESECNIt. "}<br />
{" .-'NITESECNITESEc. "}<br />
@ -367,105 +293,87 @@ export const FactionInfos: IMap<FactionInfo> = {
{" / .d/$/$; , ; "}<br />
{" d .dNITESEC $ | "}<br />
{" :bp.__.gNITESEC/$ :$ ; "}<br />
{" NITESECNITESECNIT /$b : "}<br /></>,
[],
true,
true,
false,
false,
false,
false,
),
{" NITESECNITESECNIT /$b : "}<br /></>),
offerHackingWork: true,
offerFieldWork: false,
offerSecurityWork: false,
special: false,
keepOnInstall: false,
}),
// City factions, essentially governments
[FactionNames.Aevum]: new FactionInfo(
<>The Silicon City.</>,
[FactionNames.Chongqing, FactionNames.NewTokyo, FactionNames.Ishima, FactionNames.Volhaven],
true,
true,
true,
true,
false,
false,
),
[FactionNames.Chongqing]: new FactionInfo(
<>Serve the People.</>,
[FactionNames.Sector12, FactionNames.Aevum, FactionNames.Volhaven],
true,
true,
true,
true,
false,
false,
),
[FactionNames.Ishima]: new FactionInfo(
<>The East Asian Order of the Future.</>,
[FactionNames.Sector12, FactionNames.Aevum, FactionNames.Volhaven],
true,
true,
true,
true,
false,
false,
),
[FactionNames.NewTokyo]: new FactionInfo(
<>Asia's World City.</>,
[FactionNames.Sector12, FactionNames.Aevum, FactionNames.Volhaven],
true,
true,
true,
true,
false,
false,
),
[FactionNames.Sector12]: new FactionInfo(
<>The City of the Future.</>,
[FactionNames.Chongqing, FactionNames.NewTokyo, FactionNames.Ishima, FactionNames.Volhaven],
true,
true,
true,
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,
),
[FactionNames.Aevum]: new FactionInfo({
infoText: <>The Silicon City.</>,
enemies: [FactionNames.Chongqing, FactionNames.NewTokyo, FactionNames.Ishima, FactionNames.Volhaven],
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
}),
[FactionNames.Chongqing]: new FactionInfo({
infoText: <>Serve the People.</>,
enemies: [FactionNames.Sector12, FactionNames.Aevum, FactionNames.Volhaven],
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
}),
[FactionNames.Ishima]: new FactionInfo({
infoText: <>The East Asian Order of the Future.</>,
enemies: [FactionNames.Sector12, FactionNames.Aevum, FactionNames.Volhaven],
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
}),
[FactionNames.NewTokyo]: new FactionInfo({
infoText: <>Asia's World City.</>,
enemies: [FactionNames.Sector12, FactionNames.Aevum, FactionNames.Volhaven],
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
}),
[FactionNames.Sector12]: new FactionInfo({
infoText: <>The City of the Future.</>,
enemies: [FactionNames.Chongqing, FactionNames.NewTokyo, FactionNames.Ishima, FactionNames.Volhaven],
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
}),
[FactionNames.Volhaven]: new FactionInfo({
infoText: <>Benefit, Honor, and Glory.</>,
enemies: [
FactionNames.Chongqing,
FactionNames.Sector12,
FactionNames.NewTokyo,
FactionNames.Aevum,
FactionNames.Ishima,
],
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
}),
// Criminal Organizations/Gangs
[FactionNames.SpeakersForTheDead]: new FactionInfo(
<>It is better to reign in Hell than to serve in Heaven.</>,
[],
true,
true,
true,
true,
false,
false,
),
[FactionNames.SpeakersForTheDead]: new FactionInfo({
infoText: <>It is better to reign in Hell than to serve in Heaven.</>,
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
}),
[FactionNames.TheDarkArmy]: new FactionInfo(
<>The World doesn't care about right or wrong. It only cares about power.</>,
[],
true,
true,
true,
false,
false,
false,
),
[FactionNames.TheDarkArmy]: new FactionInfo({
infoText: <>The World doesn't care about right or wrong. It only cares about power.</>,
offerHackingWork: true,
offerFieldWork: true,
}),
[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
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.
</>
),
[],
true,
true,
true,
false,
false,
false,
),
offerHackingWork: true,
offerFieldWork: true,
}),
[FactionNames.Tetrads]: new FactionInfo(
<>Following the mandate of Heaven and carrying out the way.</>,
[],
false,
false,
true,
true,
false,
false,
),
[FactionNames.Tetrads]: new FactionInfo({
infoText: <>Following the mandate of Heaven and carrying out the way.</>,
[FactionNames.SlumSnakes]: new FactionInfo(
<>{FactionNames.SlumSnakes} rule!</>,
[],
false,
false,
true,
true,
false,
false,
),
offerFieldWork: true,
offerSecurityWork: true,
}),
[FactionNames.SlumSnakes]: new FactionInfo({
infoText: <>{FactionNames.SlumSnakes} rule!</>,
offerFieldWork: true,
offerSecurityWork: true,
}),
// Earlygame factions - factions the player will prestige with early on that don't belong in other categories.
[FactionNames.Netburners]: new FactionInfo(
<>{"~~//*>H4CK||3T 8URN3R5**>?>\\~~"}</>,
[],
true,
true,
false,
false,
false,
false,
),
[FactionNames.Netburners]: new FactionInfo({
infoText: <>{"~~//*>H4CK||3T 8URN3R5**>?>\\~~"}</>,
offerHackingWork: true,
}),
[FactionNames.TianDiHui]: new FactionInfo(
<>Obey Heaven and work righteously.</>,
[],
true,
true,
false,
true,
false,
false,
),
[FactionNames.TianDiHui]: new FactionInfo({
infoText: <>Obey Heaven and work righteously.</>,
offerHackingWork: true,
[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
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.
</>
),
[],
true,
true,
false,
false,
false,
false,
),
offerHackingWork: true,
}),
// Special Factions
[FactionNames.Bladeburners]: new FactionInfo(
(
[FactionNames.Bladeburners]: new FactionInfo({
infoText: (
<>
It's too bad they won't live. But then again, who does?
<br />
<br />
Note that for this faction, reputation can only be gained through {FactionNames.Bladeburners} actions.
Completing {FactionNames.Bladeburners}
contracts/operations will increase your reputation.
Note that for this faction, reputation can only be gained through {FactionNames.Bladeburners} actions.{" "}
Completing {FactionNames.Bladeburners} contracts/operations will increase your reputation.
</>
),
[],
false,
false,
false,
false,
true,
false,
),
special: true,
}),
// prettier-ignore
[FactionNames.ChurchOfTheMachineGod]: new FactionInfo(<>
[FactionNames.ChurchOfTheMachineGod]: new FactionInfo({
infoText:(<>
{" `` "}<br />
{" -odmmNmds: "}<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
Armageddon that will end the world; but we disagree.
<br /><br />Note that for this faction, reputation can
only be gained by charging Stanek's gift.</>,
[],
false,
false,
false,
false,
true,
true,
),
only be gained by charging Stanek's gift.</>),
offerHackingWork: false,
offerFieldWork: false,
offerSecurityWork: false,
special: true,
keepOnInstall: true,
}),
};

@ -78,10 +78,10 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
const augs = getAugs();
function canBuy(augName: string): boolean {
const aug = Augmentations[augName];
const repCost = aug.baseRepRequirement * props.faction.getInfo().augmentationRepRequirementMult;
const repCost = aug.baseRepRequirement;
const hasReq = props.faction.playerReputation >= repCost;
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;
}
const buy = augs.filter(canBuy).sort((augName1, augName2) => {

@ -20,7 +20,6 @@ interface IProps {
export function PurchaseAugmentationModal(props: IProps): React.ReactElement {
const player = use.Player();
const factionInfo = props.faction.getInfo();
function buy(): void {
if (!isRepeatableAug(props.aug) && player.hasAugmentation(props.aug)) {
@ -43,7 +42,7 @@ export function PurchaseAugmentationModal(props: IProps): React.ReactElement {
<br />
<br />
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 />
</Typography>

@ -84,11 +84,11 @@ export function PurchaseableAugmentation(props: IProps): React.ReactElement {
return <></>;
}
const moneyCost = aug.baseCost * props.faction.getInfo().augmentationPriceMult;
const repCost = aug.baseRepRequirement * props.faction.getInfo().augmentationRepRequirementMult;
const moneyCost = aug.baseCost;
const repCost = aug.baseRepRequirement;
const hasReq = hasAugmentationPrereqs(aug);
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
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 });
}
router.toFaction(faction);
router.toStaneksGift();
}
function renderCotMG(): React.ReactElement {
const toStanek = <Button onClick={() => router.toStaneksGift()}>Open Stanek's Gift</Button>;
// prettier-ignore
const symbol = <Typography sx={{ lineHeight: '1em', whiteSpace: 'pre' }}>
{" `` "}<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?
</i>
</Typography>
<br />
{toStanek}
<br />
{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.
</i>
</Typography>
<br />
{toStanek}
<br />
{symbol}
</>
);
@ -242,6 +249,9 @@ export function SpecialLocation(props: IProps): React.ReactElement {
<Typography>
<i>Allison "Mother" Stanek: Welcome back my child!</i>
</Typography>
<br />
{toStanek}
<br />
{symbol}
</>
);

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

@ -1,27 +1,24 @@
import { RunningScript } from "src/Script/RunningScript";
import { Settings } from "../Settings/Settings";
import { WorkerScript } from "./WorkerScript";
export const recentScripts: RecentScript[] = [];
export function AddRecentScript(workerScript: WorkerScript): void {
if (recentScripts.find((r) => r.pid === workerScript.pid)) return;
recentScripts.unshift({
filename: workerScript.name,
args: workerScript.args,
pid: workerScript.pid,
timestamp: new Date(),
if (recentScripts.find((r) => r.runningScript.pid === workerScript.pid)) return;
const killedTime = new Date();
recentScripts.unshift({
timeOfDeath: killedTime,
runningScript: workerScript.scriptRef,
});
while (recentScripts.length > 50) {
while (recentScripts.length > Settings.MaxRecentScriptsCapacity) {
recentScripts.pop();
}
}
export interface RecentScript {
filename: string;
args: string[];
pid: number;
timestamp: Date;
timeOfDeath: Date;
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;
/**
* Status message in case of script error. Currently unused I think
* Status message in case of script error.
*/
errorMessage = "";

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

@ -1,15 +1,20 @@
import { isString } from "./utils/helpers/isString";
import { GetServer } from "./Server/AllServers";
import { ScriptDeath } from "./Netscript/ScriptDeath";
import { WorkerScript } from "./Netscript/WorkerScript";
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();
return new Promise(function (resolve, reject) {
workerScript.delay = window.setTimeout(() => {
workerScript.delay = null;
workerScript.delayReject = undefined;
if (workerScript.env.stopFlag) reject(workerScript);
if (workerScript.env.stopFlag) reject(new ScriptDeath(workerScript));
else resolve();
}, time);
workerScript.delayReject = reject;

@ -79,6 +79,8 @@ import {
Gang as IGang,
Bladeburner as IBladeburner,
Stanek as IStanek,
RunningScript as IRunningScript,
RecentScript as IRecentScript,
SourceFileLvl,
BasicHGWOptions,
ProcessInfo,
@ -98,6 +100,7 @@ import { SnackbarEvents } from "./ui/React/Snackbar";
import { Flags } from "./NetscriptFunctions/Flags";
import { calculateIntelligenceBonus } from "./PersonObjects/formulas/intelligence";
import { CalculateShareMult, StartSharing } from "./NetworkShare/Share";
import { recentScripts } from "./Netscript/RecentScripts";
import { CityName } from "./Locations/data/CityNames";
import { wrapAPI } from "./Netscript/APIWrapper";
@ -213,6 +216,32 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
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
* a nonexistent running script
@ -585,9 +614,25 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return calculatePercentMoneyHacked(server, Player);
},
hackAnalyzeSecurity: function (_threads: unknown): number {
hackAnalyzeSecurity: function (_threads: unknown, _hostname?: unknown): number {
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;
},
hackAnalyzeChance: function (_hostname: unknown): number {
@ -1415,6 +1460,13 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
allFiles.sort();
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[] {
updateDynamicRam("ps", getRamCost(Player, "ps"));
const hostname = helper.string("ps", "hostname", _hostname);
@ -2130,21 +2182,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
runningScript = getRunningScript(fn, hostname, "getRunningScript", args);
}
if (runningScript === null) return null;
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,
};
return createPublicRunningScript(runningScript);
},
getHackTime: function (_hostname: unknown = workerScript.hostname): number {
updateDynamicRam("getHackTime", getRamCost(Player, "getHackTime"));

@ -637,9 +637,6 @@ export function NetscriptCorporation(
const office = getOffice(divisionName, cityName);
if (!Object.values(EmployeePositions).includes(job)) throw new Error(`'${job}' is not a valid job.`);
return netscriptDelay(1000, workerScript).then(function () {
if (workerScript.env.stopFlag) {
return Promise.reject(workerScript);
}
return Promise.resolve(office.setEmployeeToJob(job, amount));
});
},
@ -753,6 +750,15 @@ export function NetscriptCorporation(
"Research & Development": office.employeeProd[EmployeePositions.RandD],
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 {

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

@ -12,7 +12,7 @@ import {
Stanek as IStanek,
} from "../ScriptEditor/NetscriptDefinitions";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { NetscriptContext, InternalAPI } from "src/Netscript/APIWrapper";
import { NetscriptContext, InternalAPI } from "../Netscript/APIWrapper";
export function NetscriptStanek(
player: IPlayer,
@ -26,12 +26,12 @@ export function NetscriptStanek(
}
return {
giftWidth: (_ctx: NetscriptContext) =>
giftWidth: () =>
function (): number {
checkStanekAPIAccess("giftWidth");
return staneksGift.width();
},
giftHeight: (_ctx: NetscriptContext) =>
giftHeight: () =>
function (): number {
checkStanekAPIAccess("giftHeight");
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
// 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));
// Push the blob URL onto the top of the stack.

@ -3,6 +3,7 @@
* that allows for scripts to run
*/
import { killWorkerScript } from "./Netscript/killWorkerScript";
import { ScriptDeath } from "./Netscript/ScriptDeath";
import { WorkerScript } from "./Netscript/WorkerScript";
import { workerScripts } from "./Netscript/WorkerScripts";
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
// 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.
function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Promise<WorkerScript> {
function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Promise<void> {
workerScript.running = true;
// 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
// stop flag in the evaluator.
if (workerScript.env.stopFlag) {
throw workerScript;
throw new ScriptDeath(workerScript);
}
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)";
if (runningFn) {
workerScript.errorMessage = makeRuntimeRejectMsg(workerScript, sprintf(msg, runningFn, propName));
throw workerScript;
throw new ScriptDeath(workerScript);
}
runningFn = propName;
@ -116,18 +117,29 @@ function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Pro
};
}
for (const prop of Object.keys(workerScript.env.vars)) {
if (typeof workerScript.env.vars[prop] !== "function") continue;
workerScript.env.vars[prop] = wrap(prop, workerScript.env.vars[prop]);
function wrapObject(vars: any, ...tree: string[]): void {
for (const prop of Object.keys(vars)) {
switch (typeof vars[prop]) {
case "function": {
vars[prop] = wrap([...tree, prop].join("."), vars[prop]);
break;
}
case "object": {
if (Array.isArray(vars[prop])) continue;
wrapObject(vars[prop], ...tree, prop);
break;
}
}
}
}
workerScript.env.vars.stanek.charge = wrap("stanek.charge", workerScript.env.vars.stanek.charge);
wrapObject(workerScript.env.vars);
// 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.
return new Promise<WorkerScript>((resolve, reject) => {
return new Promise<void>((resolve, reject) => {
executeJSScript(player, workerScript.getServer().scripts, workerScript)
.then(() => {
resolve(workerScript);
resolve();
})
.catch((e) => reject(e));
}).catch((e) => {
@ -140,20 +152,21 @@ function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Pro
e.message + ((e.stack && "\nstack:\n" + e.stack.toString()) || ""),
);
}
throw workerScript;
throw new ScriptDeath(workerScript);
} else if (isScriptErrorMessage(e)) {
workerScript.errorMessage = e;
throw workerScript;
} else if (e instanceof WorkerScript) {
throw new ScriptDeath(workerScript);
} else if (e instanceof ScriptDeath) {
throw e;
}
workerScript.errorMessage = makeRuntimeRejectMsg(workerScript, e);
throw workerScript; // Don't know what to do with it, let's rethrow.
// Don't know what to do with it, let's try making an error message out of it
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;
workerScript.running = true;
@ -168,7 +181,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
workerScript.env.stopFlag = true;
workerScript.running = false;
killWorkerScript(workerScript);
return Promise.resolve(workerScript);
return Promise.resolve();
}
const interpreterInitialization = function (int: any, scope: any): void {
@ -201,7 +214,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
})
.catch(function (err: any) {
// workerscript is when you cancel a delay
if (!(err instanceof WorkerScript)) {
if (!(err instanceof ScriptDeath)) {
console.error(err);
const errorTextArray = err.split("|DELIMITER|");
const hostname = errorTextArray[1];
@ -214,7 +227,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
workerScript.env.stopFlag = true;
workerScript.running = false;
killWorkerScript(workerScript);
return Promise.resolve(workerScript);
return Promise.resolve();
}
});
};
@ -277,14 +290,14 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
workerScript.env.stopFlag = true;
workerScript.running = false;
killWorkerScript(workerScript);
return Promise.resolve(workerScript);
return Promise.resolve();
}
return new Promise(function (resolve, reject) {
function runInterpreter(): void {
try {
if (workerScript.env.stopFlag) {
return reject(workerScript);
return reject(new ScriptDeath(workerScript));
}
let more = true;
@ -297,7 +310,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
if (more) {
setTimeout(runInterpreter, Settings.CodeInstructionRunTime);
} else {
resolve(workerScript);
resolve();
}
} catch (e: any) {
e = e.toString();
@ -305,7 +318,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
e = makeRuntimeRejectMsg(workerScript, 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) {
if (isString(e)) {
workerScript.errorMessage = e;
return reject(workerScript);
} else if (e instanceof WorkerScript) {
return reject(new ScriptDeath(workerScript));
} else if (e instanceof ScriptDeath) {
return reject(e);
} else {
return reject(workerScript);
console.error(e);
return reject(new ScriptDeath(workerScript));
}
}
});
@ -541,83 +555,85 @@ function createAndAddWorkerScript(
// Create the WorkerScript. NOTE: WorkerScript ctor will set the underlying
// RunningScript's PID as well
const s = new WorkerScript(runningScriptObj, pid, NetscriptFunctions);
s.ramUsage = oneRamUsage;
const workerScript = new WorkerScript(runningScriptObj, pid, NetscriptFunctions);
workerScript.ramUsage = oneRamUsage;
// Add the WorkerScript to the global pool
workerScripts.set(pid, s);
workerScripts.set(pid, workerScript);
WorkerScriptStartStopEventEmitter.emit();
// Start the script's execution
let p: Promise<WorkerScript> | null = null; // Script's resulting promise
if (s.name.endsWith(".js") || s.name.endsWith(".ns")) {
p = startNetscript2Script(player, s);
let scriptExecution: Promise<void> | null = null; // Script's resulting promise
if (workerScript.name.endsWith(".js") || workerScript.name.endsWith(".ns")) {
scriptExecution = startNetscript2Script(player, workerScript);
} else {
p = startNetscript1Script(s);
if (!(p instanceof Promise)) {
scriptExecution = startNetscript1Script(workerScript);
if (!(scriptExecution instanceof Promise)) {
return false;
}
}
// Once the code finishes (either resolved or rejected, doesnt matter), set its
// running status to false
p.then(function (w: WorkerScript) {
w.running = false;
w.env.stopFlag = true;
// On natural death, the earnings are transfered to the parent if it still exists.
if (parent !== undefined) {
if (parent.running) {
parent.scriptRef.onlineExpGained += runningScriptObj.onlineExpGained;
parent.scriptRef.onlineMoneyMade += runningScriptObj.onlineMoneyMade;
scriptExecution
.then(function () {
workerScript.running = false;
workerScript.env.stopFlag = true;
// On natural death, the earnings are transfered to the parent if it still exists.
if (parent !== undefined) {
if (parent.running) {
parent.scriptRef.onlineExpGained += runningScriptObj.onlineExpGained;
parent.scriptRef.onlineMoneyMade += runningScriptObj.onlineMoneyMade;
}
}
}
killWorkerScript(s);
w.log("", () => "Script finished running");
}).catch(function (w) {
if (w instanceof Error) {
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());
return;
} else if (w instanceof WorkerScript) {
if (isScriptErrorMessage(w.errorMessage)) {
const errorTextArray = w.errorMessage.split("|DELIMITER|");
if (errorTextArray.length != 4) {
console.error("ERROR: Something wrong with Error text in evaluator...");
console.error("Error text: " + w.errorMessage);
return;
killWorkerScript(workerScript);
workerScript.log("", () => "Script finished running");
})
.catch(function (e) {
if (e instanceof Error) {
dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
console.error("Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + e.toString());
return;
} else if (e instanceof ScriptDeath) {
if (isScriptErrorMessage(workerScript.errorMessage)) {
const errorTextArray = workerScript.errorMessage.split("|DELIMITER|");
if (errorTextArray.length != 4) {
console.error("ERROR: Something wrong with Error text in evaluator...");
console.error("Error text: " + workerScript.errorMessage);
return;
}
const hostname = errorTextArray[1];
const scriptName = errorTextArray[2];
const errorMsg = errorTextArray[3];
let msg = `RUNTIME ERROR<br>${scriptName}@${hostname} (PID - ${workerScript.pid})<br>`;
if (workerScript.args.length > 0) {
msg += `Args: ${arrayToString(workerScript.args)}<br>`;
}
msg += "<br>";
msg += errorMsg;
dialogBoxCreate(msg);
workerScript.log("", () => "Script crashed with runtime error");
} else {
workerScript.log("", () => "Script killed");
return; // Already killed, so stop here
}
const hostname = errorTextArray[1];
const scriptName = errorTextArray[2];
const errorMsg = errorTextArray[3];
let msg = `RUNTIME ERROR<br>${scriptName}@${hostname}<br>`;
if (w.args.length > 0) {
msg += `Args: ${arrayToString(w.args)}<br>`;
}
msg += "<br>";
msg += errorMsg;
dialogBoxCreate(msg);
w.log("", () => "Script crashed with runtime error");
} else if (isScriptErrorMessage(e)) {
dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
console.error(
"ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " +
e.toString(),
);
return;
} else {
w.log("", () => "Script killed");
return; // Already killed, so stop here
dialogBoxCreate("An unknown script died for an unknown reason. This is a bug please contact game dev");
console.error(e);
}
} else if (isScriptErrorMessage(w)) {
dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
console.error(
"ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " +
w.toString(),
);
return;
} else {
dialogBoxCreate("An unknown script died for an unknown reason. This is a bug please contact game dev");
console.error(w);
}
killWorkerScript(s);
});
killWorkerScript(workerScript);
});
return true;
}

@ -94,7 +94,7 @@ export const GraftingRoot = (): React.ReactElement => {
<Typography variant="h5">Graft Augmentations</Typography>
{getAvailableAugs(player).length > 0 ? (
<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) => (
<ListItemButton key={i + 1} onClick={() => setSelectedAug(k)} selected={selectedAug === k}>
<Typography>{k}</Typography>

@ -101,24 +101,46 @@ interface Player {
/**
* @public
*/
interface RunningScript {
export interface RunningScript {
/** Arguments the script was called with */
args: string[];
/** Filename of the script */
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[];
/** Total amount of hacking experience earned from this script when offline */
offlineExpGained: number;
/** Total amount of money made by this script when offline */
offlineMoneyMade: number;
/** Offline running time of the script, in seconds **/
/** Number of seconds that the script has been running offline */
offlineRunningTime: number;
/** Total amount of hacking experience earned from this script when online */
onlineExpGained: number;
/** Total amount of money made by this script when online */
onlineMoneyMade: number;
/** Online running time of the script, in seconds **/
/** Number of seconds that this script has been running online */
onlineRunningTime: number;
/** Process ID. Must be an integer */
pid: number;
/** How much RAM this script uses for ONE thread */
ramUsage: number;
/** Hostname of the server on which this script runs */
server: string;
/** Number of threads that this script runs with */
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.
* @public
@ -2618,7 +2640,7 @@ export interface Hacknet {
* // NS1:
* var upgradeName = "Sell for Corporation Funds";
* if (hacknet.numHashes() > hacknet.hashCost(upgradeName)) {
* hacknet.spendHashes(upgName);
* hacknet.spendHashes(upgradeName);
* }
* ```
* @example
@ -2626,7 +2648,7 @@ export interface Hacknet {
* // NS2:
* const upgradeName = "Sell for Corporation Funds";
* 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.
@ -4555,9 +4577,10 @@ export interface NS extends Singularity {
* 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 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.
*/
hackAnalyzeSecurity(threads: number): number;
hackAnalyzeSecurity(threads: number, hostname?: string): number;
/**
* 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[];
/**
* 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.
* @remarks
@ -6965,8 +7009,10 @@ interface Office {
maxMor: number;
/** Name of all the employees */
employees: string[];
/** Positions of the employees */
/** Production of the employees */
employeeProd: EmployeeJobs;
/** Positions of the employees */
employeeJobs: EmployeeJobs;
}
/**

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

@ -63,6 +63,11 @@ interface IDefaultSettings {
*/
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.
*/
@ -191,6 +196,7 @@ export const defaultSettings: IDefaultSettings = {
EnableBashHotkeys: false,
TimestampsFormat: "",
Locale: "en",
MaxRecentScriptsCapacity: 50,
MaxLogCapacity: 50,
MaxPortCapacity: 50,
MaxTerminalCapacity: 500,
@ -228,6 +234,7 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
EnableBashHotkeys: defaultSettings.EnableBashHotkeys,
TimestampsFormat: defaultSettings.TimestampsFormat,
Locale: "en",
MaxRecentScriptsCapacity: defaultSettings.MaxRecentScriptsCapacity,
MaxLogCapacity: defaultSettings.MaxLogCapacity,
MaxPortCapacity: defaultSettings.MaxPortCapacity,
MaxTerminalCapacity: defaultSettings.MaxTerminalCapacity,

@ -191,8 +191,8 @@ export const HelpTexts: IMap<string[]> = {
"Usage: connect [hostname]",
" ",
"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 ",
"see which servers can be connected to, use the 'scan' command.",
"to this command. Note that only servers that are immediately adjacent to the current server in the network and the ones that have",
"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.", " "],

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

@ -1,4 +1,4 @@
import { WorkerScript } from "./Netscript/WorkerScript";
import { ScriptDeath } from "./Netscript/ScriptDeath";
import { isScriptErrorMessage } from "./NetscriptEvaluator";
import { dialogBoxCreate } from "./ui/React/DialogBox";
@ -14,9 +14,9 @@ export function setupUncaughtPromiseHandler(): void {
msg += "<br>";
msg += errorMsg;
dialogBoxCreate(msg);
} else if (e.reason instanceof WorkerScript) {
} else if (e.reason instanceof ScriptDeath) {
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 ?`;
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 ",
"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",
"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 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",

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

@ -13,7 +13,7 @@ export function RecentScriptsPage(): React.ReactElement {
<>
<Typography>List of all recently killed scripts.</Typography>
{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 { numeralWrapper } from "./numeralFormat";
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 { Paper, Table, TableBody, Box, IconButton, Typography, Container, Tooltip } from "@mui/material";
import { MoreHoriz, Info } from "@mui/icons-material";
import React, { useEffect, useState } from "react";
import { BitNodes } from "../BitNode/BitNode";
import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box";
import IconButton from "@mui/material/IconButton";
import MoreHorizIcon from "@mui/icons-material/MoreHoriz";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { HacknetServerConstants } from "../Hacknet/data/Constants";
import { getPurchaseServerLimit } from "../Server/ServerPurchases";
import { Settings } from "../Settings/Settings";
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 { Money } from "./React/Money";
import { StatsRow } from "./React/StatsRow";
import { StatsTable } from "./React/StatsTable";
import TableBody from "@mui/material/TableBody";
import { Table, TableCell } from "./React/Table";
import TableRow from "@mui/material/TableRow";
function LastEmployer(): React.ReactElement {
const player = use.Player();
if (player.companyName) {
return <Typography>Employer at which you last worked: {player.companyName}</Typography>;
}
return <></>;
interface EmployersModalProps {
open: boolean;
onClose: () => void;
}
function LastJob(): React.ReactElement {
const EmployersModal = ({ open, onClose }: EmployersModalProps): 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>
{Object.keys(player.jobs).map((j) => (
<Typography key={j}> * {j}</Typography>
<Typography key={j}>* {j}</Typography>
))}
</ul>
</>
);
return <></>;
}
function Hacknet(): React.ReactElement {
const player = use.Player();
// Can't import HacknetHelpers for some reason.
if (!(player.bitNodeN === 9 || SourceFileFlags[9] > 0)) {
return (
<>
<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>
{props.rows.map((r: any) => (
<TableRow key={r[0]}>
<TableCell key="0">
<Typography noWrap>{`${r[0]} multiplier:`}&nbsp;</Typography>
</TableCell>
<TableCell key="1" align="right">
<Typography noWrap>{numeralWrapper.formatPercentage(r[1])}</Typography>
</TableCell>
{bn5Stat(r)}
</TableRow>
))}
</TableBody>
</Table>
</>
</Modal>
);
};
interface MultTableProps {
rows: (string | number)[][];
color: string;
noMargin?: boolean;
}
function BladeburnerMults(): React.ReactElement {
const player = use.Player();
if (!player.canAccessBladeburner()) return <></>;
function MultiplierTable(props: MultTableProps): React.ReactElement {
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],
]}
/>
<Table sx={{ display: "table", width: "100%", mb: (props.noMargin ?? false) === true ? 0 : 2 }}>
<TableBody>
{props.rows.map((data) => {
const mult = data[0] as string,
value = data[1] as number,
modded = data[2] as number | null;
if (modded && modded !== value && SourceFileFlags[5] > 0) {
return (
<StatsRow key={mult} name={mult} color={props.color} data={{}}>
<>
<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>
</Table>
);
}
@ -146,15 +82,17 @@ function CurrentBitNode(): React.ReactElement {
const player = use.Player();
if (player.sourceFiles.length > 0) {
const index = "BitNode" + player.bitNodeN;
const currentSourceFile = player.sourceFiles.find((sourceFile) => sourceFile.n == player.bitNodeN);
const lvl = currentSourceFile ? currentSourceFile.lvl : 0;
return (
<>
<Typography variant="h4">
BitNode {player.bitNodeN}: {BitNodes[index].name}
</Typography>
<Typography sx={{ mx: 2 }} style={{ whiteSpace: "pre-wrap", overflowWrap: "break-word" }}>
{BitNodes[index].info}
</Typography>
</>
<Box>
<Paper sx={{ p: 1 }}>
<Typography variant="h5">
BitNode {player.bitNodeN}: {BitNodes[index].name} (Level {lvl})
</Typography>
<Typography sx={{ whiteSpace: "pre-wrap", overflowWrap: "break-word" }}>{BitNodes[index].info}</Typography>
</Paper>
</Box>
);
}
@ -262,6 +200,7 @@ function MoneyModal({ open, onClose }: IMoneyModalProps): React.ReactElement {
export function CharacterStats(): React.ReactElement {
const player = use.Player();
const [moneyOpen, setMoneyOpen] = useState(false);
const [employersOpen, setEmployersOpen] = useState(false);
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
@ -273,229 +212,307 @@ export function CharacterStats(): React.ReactElement {
}, []);
const timeRows = [
["Time played since last Augmentation:", convertTimeMsToTimeElapsedString(player.playtimeSinceLastAug)],
["Since last Augmentation installation", convertTimeMsToTimeElapsedString(player.playtimeSinceLastAug)],
];
if (player.sourceFiles.length > 0) {
timeRows.push([
"Time played since last Bitnode destroyed:",
convertTimeMsToTimeElapsedString(player.playtimeSinceLastBitnode),
]);
timeRows.push(["Since last Bitnode destroyed", convertTimeMsToTimeElapsedString(player.playtimeSinceLastBitnode)]);
}
timeRows.push(["Total Time played:", convertTimeMsToTimeElapsedString(player.totalPlaytime)]);
timeRows.push(["Total", convertTimeMsToTimeElapsedString(player.totalPlaytime)]);
return (
<>
<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 />
<Container maxWidth="lg" disableGutters sx={{ mx: 0 }}>
<Typography variant="h4">Stats</Typography>
<Box sx={{ mx: 2 }}>
<Table size="small" padding="none">
<TableBody>
<TableRow>
<TableCell>
<Typography noWrap>Hacking:&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>{numeralWrapper.formatSkill(player.hacking)}&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>({numeralWrapper.formatExp(player.hacking_exp)} exp)</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Typography noWrap>Strength:&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>{numeralWrapper.formatSkill(player.strength)}&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>({numeralWrapper.formatExp(player.strength_exp)} exp)</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Typography noWrap>Defense:&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>{numeralWrapper.formatSkill(player.defense)}&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>({numeralWrapper.formatExp(player.defense_exp)} exp)</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Typography noWrap>Dexterity:&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>{numeralWrapper.formatSkill(player.dexterity)}&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>({numeralWrapper.formatExp(player.dexterity_exp)} exp)</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Typography noWrap>Agility:&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>{numeralWrapper.formatSkill(player.agility)}&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>({numeralWrapper.formatExp(player.agility_exp)} exp)</Typography>
</TableCell>
</TableRow>
<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>
</Table>
<br />
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr", minWidth: "fit-content", mb: 1, gap: 1 }}>
<Paper sx={{ p: 1 }}>
<Typography variant="h5">General</Typography>
<Table>
<TableBody>
<StatsRow name="Current City" color={Settings.theme.primary} data={{ content: player.city }} />
<StatsRow name="Money" color={Settings.theme.money} data={{}}>
<>
<Money money={player.money} />
<IconButton onClick={() => setMoneyOpen(true)} sx={{ p: 0 }}>
<MoreHoriz color="info" />
</IconButton>
</>
</StatsRow>
{player.companyName ? (
<>
<StatsRow
name="Last Employer"
color={Settings.theme.primary}
data={{ content: player.companyName }}
/>
<StatsRow
name="Last Job"
color={Settings.theme.primary}
data={{ content: player.jobs[player.companyName] }}
/>
</>
) : (
<></>
)}
{player.jobs && Object.keys(player.jobs).length !== 0 ? (
<StatsRow name="All Employers" color={Settings.theme.primary} data={{}}>
<>
<span style={{ color: Settings.theme.primary }}>{Object.keys(player.jobs).length} total</span>
<IconButton onClick={() => setEmployersOpen(true)} sx={{ p: 0 }}>
<MoreHoriz color="info" />
</IconButton>
</>
</StatsRow>
) : (
<></>
)}
<StatsRow
name="Servers Owned"
color={Settings.theme.primary}
data={{ content: `${player.purchasedServers.length} / ${getPurchaseServerLimit()}` }}
/>
<StatsRow
name={`Hacknet ${player.bitNodeN === 9 || SourceFileFlags[9] > 0 ? "Servers" : "Nodes"} owned`}
color={Settings.theme.primary}
data={{
content: `${player.hacknetNodes.length}${
player.bitNodeN === 9 || SourceFileFlags[9] > 0 ? ` / ${HacknetServerConstants.MaxServers}` : ""
}`,
}}
/>
<StatsRow
name="Augmentations Installed"
color={Settings.theme.primary}
data={{ content: String(player.augmentations.length) }}
/>
</TableBody>
</Table>
</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>
<br />
<Typography variant="h4">Multipliers</Typography>
<Box sx={{ mx: 2 }}>
<MultiplierTable
rows={[
["Hacking Chance", player.hacking_chance_mult],
["Hacking Speed", player.hacking_speed_mult],
[
"Hacking Money",
player.hacking_money_mult,
player.hacking_money_mult * BitNodeMultipliers.ScriptHackMoney,
],
[
"Hacking Growth",
player.hacking_grow_mult,
player.hacking_grow_mult * BitNodeMultipliers.ServerGrowthRate,
],
]}
/>
<br />
<MultiplierTable
rows={[
["Hacking Level", player.hacking_mult, player.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier],
["Hacking Experience", player.hacking_exp_mult, player.hacking_exp_mult * BitNodeMultipliers.HackExpGain],
]}
/>
<br />
<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 />
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
rows={[
["Hacking Chance", player.hacking_chance_mult],
["Hacking Speed", player.hacking_speed_mult],
[
"Hacking Money",
player.hacking_money_mult,
player.hacking_money_mult * BitNodeMultipliers.ScriptHackMoney,
],
[
"Hacking Growth",
player.hacking_grow_mult,
player.hacking_grow_mult * BitNodeMultipliers.ServerGrowthRate,
],
]}
color={Settings.theme.hack}
/>
<MultiplierTable
rows={[
[
"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}
/>
<MultiplierTable
rows={[
[
"Strength Level",
player.strength_mult,
player.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier,
],
["Strength Experience", player.strength_exp_mult],
]}
color={Settings.theme.combat}
/>
<MultiplierTable
rows={[
[
"Defense Level",
player.defense_mult,
player.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier,
],
["Defense Experience", player.defense_exp_mult],
]}
color={Settings.theme.combat}
/>
<MultiplierTable
rows={[
[
"Dexterity Level",
player.dexterity_mult,
player.dexterity_mult * BitNodeMultipliers.DexterityLevelMultiplier,
],
["Dexterity Experience", player.dexterity_exp_mult],
]}
color={Settings.theme.combat}
/>
<MultiplierTable
rows={[
[
"Agility Level",
player.agility_mult,
player.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier,
],
["Agility Experience", player.agility_exp_mult],
]}
color={Settings.theme.combat}
/>
<MultiplierTable
rows={[
[
"Charisma Level",
player.charisma_mult,
player.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier,
],
["Charisma Experience", player.charisma_exp_mult],
]}
color={Settings.theme.cha}
noMargin
/>
</Box>
<MultiplierTable
rows={[
["Strength Level", player.strength_mult, player.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier],
["Strength Experience", player.strength_exp_mult],
]}
/>
<br />
<MultiplierTable
rows={[
["Defense Level", player.defense_mult, player.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier],
["Defense Experience", player.defense_exp_mult],
]}
/>
<br />
<MultiplierTable
rows={[
[
"Dexterity Level",
player.dexterity_mult,
player.dexterity_mult * BitNodeMultipliers.DexterityLevelMultiplier,
],
["Dexterity Experience", player.dexterity_exp_mult],
]}
/>
<br />
<MultiplierTable
rows={[
["Agility Level", player.agility_mult, player.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier],
["Agility Experience", player.agility_exp_mult],
]}
/>
<br />
<MultiplierTable
rows={[
["Charisma Level", player.charisma_mult, player.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier],
["Charisma Experience", player.charisma_exp_mult],
]}
/>
<br />
<MultiplierTable
rows={[
[
"Hacknet Node production",
player.hacknet_node_money_mult,
player.hacknet_node_money_mult * BitNodeMultipliers.HacknetNodeMoney,
],
["Hacknet Node purchase cost", player.hacknet_node_purchase_cost_mult],
["Hacknet Node RAM upgrade cost", player.hacknet_node_ram_cost_mult],
["Hacknet Node Core purchase cost", player.hacknet_node_core_cost_mult],
["Hacknet Node level upgrade cost", player.hacknet_node_level_cost_mult],
]}
/>
<br />
<MultiplierTable
rows={[
["Company reputation gain", player.company_rep_mult],
[
"Faction reputation gain",
player.faction_rep_mult,
player.faction_rep_mult * BitNodeMultipliers.FactionWorkRepGain,
],
["Salary", player.work_money_mult, player.work_money_mult * BitNodeMultipliers.CompanyWorkMoney],
]}
/>
<br />
<MultiplierTable
rows={[
["Crime success", player.crime_success_mult],
["Crime money", player.crime_money_mult, player.crime_money_mult * BitNodeMultipliers.CrimeMoney],
]}
/>
<br />
<BladeburnerMults />
<Box>
<MultiplierTable
rows={[
[
"Hacknet Node production",
player.hacknet_node_money_mult,
player.hacknet_node_money_mult * BitNodeMultipliers.HacknetNodeMoney,
],
["Hacknet Node purchase cost", player.hacknet_node_purchase_cost_mult],
["Hacknet Node RAM upgrade cost", player.hacknet_node_ram_cost_mult],
["Hacknet Node Core purchase cost", player.hacknet_node_core_cost_mult],
["Hacknet Node level upgrade cost", player.hacknet_node_level_cost_mult],
]}
color={Settings.theme.primary}
/>
<MultiplierTable
rows={[
["Company reputation gain", player.company_rep_mult],
[
"Faction reputation gain",
player.faction_rep_mult,
player.faction_rep_mult * BitNodeMultipliers.FactionWorkRepGain,
],
["Salary", player.work_money_mult, player.work_money_mult * BitNodeMultipliers.CompanyWorkMoney],
]}
color={Settings.theme.money}
/>
<MultiplierTable
rows={[
["Crime success", player.crime_success_mult],
["Crime money", player.crime_money_mult, player.crime_money_mult * BitNodeMultipliers.CrimeMoney],
]}
color={Settings.theme.combat}
/>
{player.canAccessBladeburner() && (
<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>
<br />
<Typography variant="h4">Misc</Typography>
<Box sx={{ mx: 2 }}>
<Typography>{`Servers owned: ${player.purchasedServers.length} / ${getPurchaseServerLimit()}`}</Typography>
<Hacknet />
<Typography>{`Augmentations installed: ${player.augmentations.length}`}</Typography>
<StatsTable rows={timeRows} />
<Box sx={{ mb: 1 }}>
<Paper sx={{ p: 1 }}>
<Typography variant="h5">Time Played</Typography>
<Table>
<TableBody>
{timeRows.map(([name, content]) => (
<StatsRow key={name} name={name} color={Settings.theme.primary} data={{ content: content }} />
))}
</TableBody>
</Table>
</Paper>
</Box>
<br />
<CurrentBitNode />
<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={() => {
installAugmentations();
Router.toTerminal();
}}
/>
);

@ -64,6 +64,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
const importInput = useRef<HTMLInputElement>(null);
const [execTime, setExecTime] = useState(Settings.CodeInstructionRunTime);
const [recentScriptsSize, setRecentScriptsSize] = useState(Settings.MaxRecentScriptsCapacity);
const [logSize, setLogSize] = useState(Settings.MaxLogCapacity);
const [portSize, setPortSize] = useState(Settings.MaxPortCapacity);
const [terminalSize, setTerminalSize] = useState(Settings.MaxTerminalCapacity);
@ -79,6 +80,11 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
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 {
setLogSize(newValue as number);
Settings.MaxLogCapacity = newValue as number;
@ -176,6 +182,24 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
max={100}
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
title={
<Typography>

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

@ -42,10 +42,10 @@ describe("determineAllPossibilitiesForTabCompletion", function () {
requiredHackingSkill: 1,
serverGrowth: 3000,
});
Player.getHomeComputer().serversOnNetwork.push(closeServer.ip);
closeServer.serversOnNetwork.push(Player.getHomeComputer().ip);
closeServer.serversOnNetwork.push(farServer.ip);
farServer.serversOnNetwork.push(closeServer.ip);
Player.getHomeComputer().serversOnNetwork.push(closeServer.hostname);
closeServer.serversOnNetwork.push(Player.getHomeComputer().hostname);
closeServer.serversOnNetwork.push(farServer.hostname);
farServer.serversOnNetwork.push(closeServer.hostname);
AddToAllServers(closeServer);
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 isDevelopment = argv.mode === "development";
const isFastRefresh = argv.fast === "true";
const outputDirectory = isDevServer ? "dist-dev" : "dist";
const outputDirectory = "dist";
const entry = "./src/index.tsx";
const statsConfig = {
@ -46,6 +46,48 @@ module.exports = (env, argv) => {
// https://stackoverflow.com/a/38401256
const commitHash = require("child_process").execSync("git rev-parse --short HEAD").toString().trim();
const htmlConfig = {
title: "Bitburner",
template: "src/index.html",
favicon: "favicon.ico",
googleAnalytics: {
trackingId: "UA-100157497-1",
},
meta: {},
minify: isDevelopment
? false
: {
collapseBooleanAttributes: true,
collapseInlineTagWhitespace: false,
collapseWhitespace: false,
conservativeCollapse: false,
html5: true,
includeAutoGeneratedTags: false,
keepClosingSlash: true,
minifyCSS: false,
minifyJS: false,
minifyURLs: false,
preserveLineBreaks: false,
preventAttributesEscaping: false,
processConditionalComments: false,
quoteCharacter: '"',
removeAttributeQuotes: false,
removeComments: false,
removeEmptyAttributes: false,
removeEmptyElements: false,
removeOptionalTags: false,
removeScriptTypeAttributes: false,
removeStyleLinkTypeAttributes: false,
removeTagWhitespace: false,
sortAttributes: false,
sortClassName: false,
useShortDoctype: false,
},
};
if (!isDevelopment) {
htmlConfig.filename = "../index.html";
}
return {
plugins: [
new webpack.DefinePlugin({
@ -60,44 +102,7 @@ module.exports = (env, argv) => {
jQuery: "jquery",
$: "jquery",
}),
new HtmlWebpackPlugin({
title: "Bitburner",
template: "src/index.html",
favicon: "favicon.ico",
googleAnalytics: {
trackingId: "UA-100157497-1",
},
meta: {},
minify: isDevelopment
? false
: {
collapseBooleanAttributes: true,
collapseInlineTagWhitespace: false,
collapseWhitespace: false,
conservativeCollapse: false,
html5: true,
includeAutoGeneratedTags: false,
keepClosingSlash: true,
minifyCSS: false,
minifyJS: false,
minifyURLs: false,
preserveLineBreaks: false,
preventAttributesEscaping: false,
processConditionalComments: false,
quoteCharacter: '"',
removeAttributeQuotes: false,
removeComments: false,
removeEmptyAttributes: false,
removeEmptyElements: false,
removeOptionalTags: false,
removeScriptTypeAttributes: false,
removeStyleLinkTypeAttributes: false,
removeTagWhitespace: false,
sortAttributes: false,
sortClassName: false,
useShortDoctype: false,
},
}),
new HtmlWebpackPlugin(htmlConfig),
new MiniCssExtractPlugin({
filename: "[name].css",
}),
@ -136,7 +141,7 @@ module.exports = (env, argv) => {
// },
entry: entry,
output: {
path: path.resolve(__dirname, "./"),
path: path.resolve(__dirname, outputDirectory),
filename: "[name].bundle.js",
},
module: {
@ -161,7 +166,7 @@ module.exports = (env, argv) => {
loader: "file-loader",
options: {
name: "[contenthash].[ext]",
outputPath: "dist/images",
outputPath: "images",
},
},
],
@ -184,7 +189,7 @@ module.exports = (env, argv) => {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: `${outputDirectory}/vendor`,
name: `vendor`,
chunks: "all",
},
},