Merge pull request #3390 from nickofolas/improvement/stats-augs-ui

[Improvement] Overhaul Stats and Augmentations UI
This commit is contained in:
hydroflame 2022-04-12 13:43:09 -04:00 committed by GitHub
commit 243cdceb8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1058 additions and 817 deletions

@ -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,14 +102,22 @@ export function AugmentationsRoot(props: IProps): React.ReactElement {
}
return (
<>
<Container disableGutters maxWidth="lg" sx={{ mx: 0 }}>
<Typography variant="h4">Augmentations</Typography>
<Box mx={2}>
<Box sx={{ mb: 1 }}>
<Paper sx={{ p: 1 }}>
<Typography variant="h5" color="primary" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
Purchased Augmentations
<Tooltip
title={
<>
<Typography>
Below is a list of all Augmentations you have purchased but not yet installed. Click the button below to
install them.
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>
<Typography>WARNING: Installing your Augmentations resets most of your progress, including:</Typography>
<br />
<Typography>- Stats/Skill levels and Experience</Typography>
<Typography>- Money</Typography>
@ -73,22 +128,16 @@ export function AugmentationsRoot(props: IProps): React.ReactElement {
<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)
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>
<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>
</>
}
>
<Info sx={{ ml: 1, mb: 0.5 }} color="info" />
</Tooltip>
</Typography>
<ConfirmationModal
open={installOpen}
onClose={() => setInstallOpen(false)}
@ -109,27 +158,57 @@ export function AugmentationsRoot(props: IProps): React.ReactElement {
<br />- home ram and cores
<br />
<br />
It is recommended to install several Augmentations at once. Preferably everything from any faction of your
choosing.
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={{ mx: 2 }} onClick={doExport} color="error">
<Button sx={{ width: "100%" }} onClick={doExport} color="error">
Backup Save {exportBonusStr()}
</Button>
</Tooltip>
<PurchasedAugmentations />
</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>
</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>
<Box
sx={{
my: 1,
display: "grid",
gridTemplateColumns: `repeat(${
+!!((player.augmentations.find((e) => e.name === AugmentationNames.NeuroFluxGovernor)?.level ?? 0) > 0) +
+!!(player.entropy > 0)
}, 1fr)`,
gap: 1,
}}
>
<NeuroFluxDisplay player={player} />
<EntropyDisplay player={player} />
</Box>
<Box>
<InstalledAugmentations />
</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 (
<>
<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 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 sx={{ width: "100%" }} onClick={sortInOrder}>
Sort in Order
</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 }} />
)}
<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>
);
})()}
{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>
</Box>
<Box sx={{ m: 1 }}>
<Typography variant="h6" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
{selectedAug.name}
</Typography>
<Typography sx={{ maxHeight: 350, overflowY: "scroll" }}>
{(() => {
const aug = Augmentations[selectedAug.name];
const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info;
const tooltip = (
<>
{info}
<br />
<br />
{aug.stats}
</>
);
return tooltip;
})()}
</Typography>
</Box>
</Paper>
) : (
<Paper sx={{ p: 1 }}>
<Typography>No Augmentations have been installed yet</Typography>
</Paper>
)}
</Box>
);
}

@ -1,20 +1,22 @@
/**
* React component for displaying the player's multipliers on the Augmentation UI page
*/
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,112 +27,82 @@ function calculateAugmentedStats(): any {
return augP;
}
function Improvements({ r, m }: { r: number; m: number }): React.ReactElement {
if (r) {
return (
<>
<TableCell key="2">
<Typography>&nbsp;{"=>"}&nbsp;</Typography>
</TableCell>
<TableCell key="3">
<Typography>
{numeralWrapper.formatPercentage(r)} <BN5Stat base={r} mult={m} />
</Typography>
</TableCell>
</>
);
}
return <></>;
}
interface IBN5StatsProps {
interface IBitNodeModifiedStatsProps {
base: number;
mult: number;
color: string;
}
function BN5Stat(props: IBN5StatsProps): React.ReactElement {
if (props.mult === 1) return <></>;
return <>({numeralWrapper.formatPercentage(props.base * props.mult)})</>;
}
function BitNodeModifiedStats(props: IBitNodeModifiedStatsProps): React.ReactElement {
// If player doesn't have SF5 or if the property isn't affected by BitNode mults
if (props.mult === 1 || SourceFileFlags[5] === 0)
return <Typography color={props.color}>{numeralWrapper.formatPercentage(props.base)}</Typography>;
function MultiplierTable({ rows }: { rows: [string, number, number, number][] }): React.ReactElement {
return (
<Table size="small" padding="none">
<TableBody>
{rows.map((r: any) => (
<TableRow key={r[0]}>
<TableCell key="0">
<Typography noWrap>{r[0]} multiplier:&nbsp;</Typography>
</TableCell>
<TableCell key="1" style={{ textAlign: "right" }}>
<Typography noWrap>
{numeralWrapper.formatPercentage(r[1])} <BN5Stat base={r[1]} mult={r[3]} />
<Typography color={props.color}>
<span style={{ opacity: 0.5 }}>{numeralWrapper.formatPercentage(props.base)}</span>{" "}
{numeralWrapper.formatPercentage(props.base * props.mult)}
</Typography>
</TableCell>
<Improvements r={r[2]} m={r[3]} />
</TableRow>
))}
</TableBody>
</Table>
);
}
type MultiplierListItemData = [
multiplier: string,
currentValue: number,
augmentedValue: number,
bitNodeMultiplier: number,
color: string,
];
interface IMultiplierListProps {
rows: MultiplierListItemData[];
}
function MultiplierList(props: IMultiplierListProps): React.ReactElement {
const listItems = props.rows
.map((data) => {
const [multiplier, currentValue, augmentedValue, bitNodeMultiplier, color] = data;
if (!isNaN(augmentedValue)) {
return (
<ListItem key={multiplier} disableGutters sx={{ py: 0 }}>
<ListItemText
sx={{ my: 0.1 }}
primary={
<Typography color={color}>
<b>{multiplier}</b>
</Typography>
}
secondary={
<span style={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
<BitNodeModifiedStats base={currentValue} mult={bitNodeMultiplier} color={color} />
<DoubleArrow fontSize="small" color="success" sx={{ mb: 0.5, mx: 1 }} />
<BitNodeModifiedStats base={augmentedValue} mult={bitNodeMultiplier} color={Settings.theme.success} />
</span>
}
disableTypography
/>
</ListItem>
);
}
return;
})
.filter((i) => i !== undefined);
return listItems.length > 0 ? <List disablePadding>{listItems}</List> : <></>;
}
export function PlayerMultipliers(): React.ReactElement {
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 />
</>
);
}
return (
<>
<Typography variant="h4">Multipliers</Typography>
<Box mx={2}>
<MultiplierTable
rows={[
// 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],
]}
/>
<br />
<MultiplierTable
rows={[
[
"Hacking Level ",
Player.hacking_mult,
@ -143,12 +115,8 @@ export function PlayerMultipliers(): React.ReactElement {
Player.hacking_exp_mult * mults.hacking_exp_mult,
BitNodeMultipliers.HackExpGain,
],
]}
/>
<br />
<MultiplierTable
rows={[
].map((data): MultiplierListItemData => (data as any).concat([Settings.theme.hack])),
...[
[
"Strength Level ",
Player.strength_mult,
@ -156,12 +124,6 @@ export function PlayerMultipliers(): React.ReactElement {
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,
@ -169,30 +131,13 @@ export function PlayerMultipliers(): React.ReactElement {
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={[
["Dexterity Experience ", Player.dexterity_exp_mult, Player.dexterity_exp_mult * mults.dexterity_exp_mult, 1],
[
"Agility Level ",
Player.agility_mult,
@ -200,25 +145,24 @@ export function PlayerMultipliers(): React.ReactElement {
BitNodeMultipliers.AgilityLevelMultiplier,
],
["Agility Experience ", Player.agility_exp_mult, Player.agility_exp_mult * mults.agility_exp_mult, 1],
]}
/>
<br />
<MultiplierTable
rows={[
].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],
]}
/>
<br />
<MultiplierTable
rows={[
[
"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,
@ -249,12 +193,6 @@ export function PlayerMultipliers(): React.ReactElement {
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 ",
@ -262,31 +200,76 @@ export function PlayerMultipliers(): React.ReactElement {
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,
],
]}
/>
<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,
Settings.theme.money,
],
]}
/>
<br />
];
<BladeburnerMults />
</Box>
</>
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 (
<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;
}
augs.push(<AugmentationAccordion key={aug.name} aug={aug} level={level} />);
displayName += ` - Level ${level}`;
}
return <List dense>{augs}</List>;
augs.push(
<Tooltip
title={
<Typography>
{(() => {
const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info;
const tooltip = (
<>
{info}
<br />
<br />
{aug.stats}
</>
);
return tooltip;
})()}
</Typography>
}
enterNextDelay={500}
key={displayName}
>
<ListItemText sx={{ px: 2, py: 1 }} primary={displayName} />
</Tooltip>,
);
}
return (
<Paper sx={{ py: 1, maxHeight: 400, overflowY: "scroll" }}>
<List sx={{ height: 400, overflowY: "scroll" }} disablePadding>
{augs}
</List>
</Paper>
);
}

@ -1,21 +1,159 @@
import React from "react";
import { 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";
export function SourceFiles(): React.ReactElement {
return (
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: (
<>
<Typography variant="h4">Source Files</Typography>
<Box mx={2}>
<List dense>
<SourceFileMinus1 />
<OwnedSourceFiles />
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]);
return (
<Box sx={{ width: "100%", mt: 1 }}>
<Paper sx={{ p: 1 }}>
<Typography variant="h5">Source Files</Typography>
</Paper>
<Paper sx={{ display: "grid", gridTemplateColumns: "1fr 3fr" }}>
<Box>
<List
sx={{ height: 400, overflowY: "scroll", borderRight: `1px solid ${Settings.theme.welllight}` }}
disablePadding
>
{sourceSfs.map((e, i) => {
const sfObj = safeGetSf(e.n);
if (!sfObj) return;
const maxLevel = getMaxLevel(sfObj);
return (
<ListItemButton
key={i + 1}
onClick={() => setSelectedSf(e)}
selected={selectedSf.n === e.n}
sx={{ py: 0 }}
>
<ListItemText
disableTypography
primary={<Typography>{sfObj.name}</Typography>}
secondary={
<Typography>
Level {e.lvl} / {maxLevel}
</Typography>
}
/>
</ListItemButton>
);
})}
</List>
</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>
);
}

@ -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,36 +38,33 @@ 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 />
<ReplyAll />
</IconButton>
<IconButton onClick={queueAug} size="large">
<ReplyIcon />
<Reply />
</IconButton>
</>
}
endAdornment={
<>
<IconButton onClick={clearAugs} size="large">
<ClearIcon />
<Clear />
</IconButton>
</>
}
@ -79,10 +75,9 @@ export function Augmentations(props: IProps): React.ReactElement {
</MenuItem>
))}
</Select>
</td>
</tr>
</tbody>
</table>
<Button sx={{ display: "block" }} onClick={clearQueuedAugs}>
Clear Queued Augmentations
</Button>
</AccordionDetails>
</Accordion>
);

@ -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>

@ -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";
interface EmployersModalProps {
open: boolean;
onClose: () => void;
}
function LastEmployer(): React.ReactElement {
const EmployersModal = ({ open, onClose }: EmployersModalProps): React.ReactElement => {
const player = use.Player();
if (player.companyName) {
return <Typography>Employer at which you last worked: {player.companyName}</Typography>;
}
return <></>;
}
function LastJob(): React.ReactElement {
const player = use.Player();
if (player.companyName !== "") {
return <Typography>Job you last worked: {player.jobs[player.companyName]}</Typography>;
}
return <></>;
}
function Employers(): React.ReactElement {
const player = use.Player();
if (player.jobs && Object.keys(player.jobs).length !== 0)
return (
<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>
))}
</ul>
</>
</Modal>
);
return <></>;
};
interface MultTableProps {
rows: (string | number)[][];
color: string;
noMargin?: boolean;
}
function Hacknet(): React.ReactElement {
const player = use.Player();
// Can't import HacknetHelpers for some reason.
if (!(player.bitNodeN === 9 || SourceFileFlags[9] > 0)) {
function MultiplierTable(props: MultTableProps): React.ReactElement {
return (
<>
<Typography>{`Hacknet Nodes owned: ${player.hacknetNodes.length}`}</Typography>
<br />
</>
);
} else {
return (
<>
<Typography>{`Hacknet Servers owned: ${player.hacknetNodes.length} / ${HacknetServerConstants.MaxServers}`}</Typography>
<br />
</>
);
}
}
function Intelligence(): React.ReactElement {
const player = use.Player();
if (player.intelligence > 0 && (player.bitNodeN === 5 || SourceFileFlags[5] > 0)) {
return (
<TableRow>
<TableCell>
<Typography>Intelligence:&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography>{numeralWrapper.formatSkill(player.intelligence)}&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>({numeralWrapper.formatExp(player.intelligence_exp)} exp)</Typography>
</TableCell>
</TableRow>
);
}
return <></>;
}
function MultiplierTable(props: any): React.ReactElement {
function bn5Stat(r: any): JSX.Element {
if (SourceFileFlags[5] > 0 && r.length > 2 && r[1] != r[2]) {
return (
<TableCell key="2" align="right">
<Typography noWrap>({numeralWrapper.formatPercentage(r[2])})</Typography>
</TableCell>
);
}
return <></>;
}
return (
<>
<Table size="small" padding="none">
<Table sx={{ display: "table", width: "100%", mb: (props.noMargin ?? false) === true ? 0 : 2 }}>
<TableBody>
{props.rows.map((r: any) => (
<TableRow key={r[0]}>
<TableCell key="0">
<Typography noWrap>{`${r[0]} multiplier:`}&nbsp;</Typography>
</TableCell>
<TableCell key="1" align="right">
<Typography noWrap>{numeralWrapper.formatPercentage(r[1])}</Typography>
</TableCell>
{bn5Stat(r)}
</TableRow>
))}
{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>
</>
);
}
function BladeburnerMults(): React.ReactElement {
const player = use.Player();
if (!player.canAccessBladeburner()) return <></>;
return (
<MultiplierTable
rows={[
["Bladeburner Success Chance", player.bladeburner_success_chance_mult],
["Bladeburner Max Stamina", player.bladeburner_max_stamina_mult],
["Bladeburner Stamina Gain", player.bladeburner_stamina_gain_mult],
["Bladeburner Field Analysis", player.bladeburner_analysis_mult],
]}
/>
);
}
@ -146,15 +82,17 @@ function CurrentBitNode(): React.ReactElement {
const player = use.Player();
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}
<Box>
<Paper sx={{ p: 1 }}>
<Typography variant="h5">
BitNode {player.bitNodeN}: {BitNodes[index].name} (Level {lvl})
</Typography>
<Typography sx={{ mx: 2 }} style={{ whiteSpace: "pre-wrap", overflowWrap: "break-word" }}>
{BitNodes[index].info}
</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,111 +212,151 @@ 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">
<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>
<TableRow>
<TableCell>
<Typography noWrap>Hacking:&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>{numeralWrapper.formatSkill(player.hacking)}&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>({numeralWrapper.formatExp(player.hacking_exp)} exp)</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Typography noWrap>Strength:&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>{numeralWrapper.formatSkill(player.strength)}&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>({numeralWrapper.formatExp(player.strength_exp)} exp)</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Typography noWrap>Defense:&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>{numeralWrapper.formatSkill(player.defense)}&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>({numeralWrapper.formatExp(player.defense_exp)} exp)</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Typography noWrap>Dexterity:&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>{numeralWrapper.formatSkill(player.dexterity)}&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>({numeralWrapper.formatExp(player.dexterity_exp)} exp)</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Typography noWrap>Agility:&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>{numeralWrapper.formatSkill(player.agility)}&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>({numeralWrapper.formatExp(player.agility_exp)} exp)</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Typography noWrap>Charisma:&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>{numeralWrapper.formatSkill(player.charisma)}&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>({numeralWrapper.formatExp(player.charisma_exp)} exp)</Typography>
</TableCell>
</TableRow>
<Intelligence />
<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>
<br />
</Paper>
<Paper sx={{ p: 1 }}>
<Typography variant="h5">Skills</Typography>
<Table>
<TableBody>
<StatsRow
name="Hacking"
color={Settings.theme.hack}
data={{ level: player.hacking, exp: player.hacking_exp }}
/>
<StatsRow
name="Strength"
color={Settings.theme.combat}
data={{ level: player.strength, exp: player.strength_exp }}
/>
<StatsRow
name="Defense"
color={Settings.theme.combat}
data={{ level: player.defense, exp: player.defense_exp }}
/>
<StatsRow
name="Dexterity"
color={Settings.theme.combat}
data={{ level: player.dexterity, exp: player.dexterity_exp }}
/>
<StatsRow
name="Agility"
color={Settings.theme.combat}
data={{ level: player.agility, exp: player.agility_exp }}
/>
<StatsRow
name="Charisma"
color={Settings.theme.cha}
data={{ level: player.charisma, exp: player.charisma_exp }}
/>
{player.intelligence > 0 && (player.bitNodeN === 5 || SourceFileFlags[5] > 0) && (
<StatsRow
name="Intelligence"
color={Settings.theme.int}
data={{ level: player.intelligence, exp: player.intelligence_exp }}
/>
)}
</TableBody>
</Table>
</Paper>
</Box>
<Box 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 />
<Typography variant="h4">Multipliers</Typography>
<Box sx={{ mx: 2 }}>
<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],
@ -393,32 +372,45 @@ export function CharacterStats(): React.ReactElement {
player.hacking_grow_mult * BitNodeMultipliers.ServerGrowthRate,
],
]}
color={Settings.theme.hack}
/>
<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],
[
"Hacking Level",
player.hacking_mult,
player.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier,
],
[
"Hacking Experience",
player.hacking_exp_mult,
player.hacking_exp_mult * BitNodeMultipliers.HackExpGain,
],
]}
color={Settings.theme.hack}
/>
<br />
<MultiplierTable
rows={[
["Strength Level", player.strength_mult, player.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier],
[
"Strength Level",
player.strength_mult,
player.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier,
],
["Strength Experience", player.strength_exp_mult],
]}
color={Settings.theme.combat}
/>
<br />
<MultiplierTable
rows={[
["Defense Level", player.defense_mult, player.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier],
[
"Defense Level",
player.defense_mult,
player.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier,
],
["Defense Experience", player.defense_exp_mult],
]}
color={Settings.theme.combat}
/>
<br />
<MultiplierTable
rows={[
[
@ -428,25 +420,34 @@ export function CharacterStats(): React.ReactElement {
],
["Dexterity Experience", player.dexterity_exp_mult],
]}
color={Settings.theme.combat}
/>
<br />
<MultiplierTable
rows={[
["Agility Level", player.agility_mult, player.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier],
[
"Agility Level",
player.agility_mult,
player.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier,
],
["Agility Experience", player.agility_exp_mult],
]}
color={Settings.theme.combat}
/>
<br />
<MultiplierTable
rows={[
["Charisma Level", player.charisma_mult, player.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier],
[
"Charisma Level",
player.charisma_mult,
player.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier,
],
["Charisma Experience", player.charisma_exp_mult],
]}
color={Settings.theme.cha}
noMargin
/>
<br />
</Box>
<Box>
<MultiplierTable
rows={[
[
@ -459,9 +460,8 @@ export function CharacterStats(): React.ReactElement {
["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}
/>
<br />
<MultiplierTable
rows={[
["Company reputation gain", player.company_rep_mult],
@ -472,30 +472,47 @@ export function CharacterStats(): React.ReactElement {
],
["Salary", player.work_money_mult, player.work_money_mult * BitNodeMultipliers.CompanyWorkMoney],
]}
color={Settings.theme.money}
/>
<br />
<MultiplierTable
rows={[
["Crime success", player.crime_success_mult],
["Crime money", player.crime_money_mult, player.crime_money_mult * BitNodeMultipliers.CrimeMoney],
]}
color={Settings.theme.combat}
/>
<br />
<BladeburnerMults />
{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>
);
}

@ -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>
);