mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-11 18:23:54 +01:00
commit
997f20aa68
@ -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
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
4
dist/main.bundle.js
vendored
File diff suppressed because one or more lines are too long
2
dist/main.bundle.js.map
vendored
2
dist/main.bundle.js.map
vendored
File diff suppressed because one or more lines are too long
42
dist/vendor.bundle.js
vendored
42
dist/vendor.bundle.js
vendored
File diff suppressed because one or more lines are too long
2
dist/vendor.bundle.js.map
vendored
2
dist/vendor.bundle.js.map
vendored
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
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",
|
||||
|
31
package.sh
31
package.sh
@ -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> {"=>"} </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: </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. It’s 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
|
||||
<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;
|
||||
}
|
||||
|
37
src/Netscript/ScriptDeath.ts
Normal file
37
src/Netscript/ScriptDeath.ts
Normal file
@ -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>
|
||||
|
60
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
60
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
@ -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: </Typography>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Typography>{numeralWrapper.formatSkill(player.intelligence)} </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:`} </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: </Typography>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Typography noWrap>{numeralWrapper.formatSkill(player.hacking)} </Typography>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Typography noWrap>({numeralWrapper.formatExp(player.hacking_exp)} exp)</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Typography noWrap>Strength: </Typography>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Typography noWrap>{numeralWrapper.formatSkill(player.strength)} </Typography>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Typography noWrap>({numeralWrapper.formatExp(player.strength_exp)} exp)</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Typography noWrap>Defense: </Typography>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Typography noWrap>{numeralWrapper.formatSkill(player.defense)} </Typography>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Typography noWrap>({numeralWrapper.formatExp(player.defense_exp)} exp)</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Typography noWrap>Dexterity: </Typography>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Typography noWrap>{numeralWrapper.formatSkill(player.dexterity)} </Typography>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Typography noWrap>({numeralWrapper.formatExp(player.dexterity_exp)} exp)</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Typography noWrap>Agility: </Typography>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Typography noWrap>{numeralWrapper.formatSkill(player.agility)} </Typography>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Typography noWrap>({numeralWrapper.formatExp(player.agility_exp)} exp)</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Typography noWrap>Charisma: </Typography>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Typography noWrap>{numeralWrapper.formatSkill(player.charisma)} </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
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",
|
||||
},
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user