Merge pull request #3626 from nickofolas/improvement/bitnode-multipliers

UI: Refactors, redesigns, and new section to stats page
This commit is contained in:
hydroflame 2022-05-13 14:33:01 -04:00 committed by GitHub
commit 4993a02421
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 754 additions and 853 deletions

@ -45,13 +45,13 @@ function BitNodeModifiedStats(props: IBitNodeModifiedStatsProps): React.ReactEle
);
}
type MultiplierListItemData = [
multiplier: string,
currentValue: number,
augmentedValue: number,
bitNodeMultiplier: number,
color: string,
];
interface MultiplierListItemData {
mult: string;
current: number;
augmented: number;
bnMult?: number;
color?: string;
}
interface IMultiplierListProps {
rows: MultiplierListItemData[];
@ -60,23 +60,23 @@ interface IMultiplierListProps {
function MultiplierList(props: IMultiplierListProps): React.ReactElement {
const listItems = props.rows
.map((data) => {
const [multiplier, currentValue, augmentedValue, bitNodeMultiplier, color] = data;
const { mult, current, augmented, bnMult = 1, color = Settings.theme.primary } = data;
if (!isNaN(augmentedValue)) {
if (!isNaN(augmented)) {
return (
<ListItem key={multiplier} disableGutters sx={{ py: 0 }}>
<ListItem key={mult} disableGutters sx={{ py: 0 }}>
<ListItemText
sx={{ my: 0.1 }}
primary={
<Typography color={color}>
<b>{multiplier}</b>
<b>{mult}</b>
</Typography>
}
secondary={
<span style={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
<BitNodeModifiedStats base={currentValue} mult={bitNodeMultiplier} color={color} />
<BitNodeModifiedStats base={current} mult={bnMult} color={color} />
<DoubleArrow fontSize="small" color="success" sx={{ mb: 0.5, mx: 1 }} />
<BitNodeModifiedStats base={augmentedValue} mult={bitNodeMultiplier} color={Settings.theme.success} />
<BitNodeModifiedStats base={augmented} mult={bnMult} color={Settings.theme.success} />
</span>
}
disableTypography
@ -94,177 +94,205 @@ function MultiplierList(props: IMultiplierListProps): React.ReactElement {
export function PlayerMultipliers(): React.ReactElement {
const mults = calculateAugmentedStats();
// 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])),
{
mult: "Hacking Chance",
current: Player.hacking_chance_mult,
augmented: Player.hacking_chance_mult * mults.hacking_chance_mult,
},
{
mult: "Hacking Speed",
current: Player.hacking_speed_mult,
augmented: Player.hacking_speed_mult * mults.hacking_speed_mult,
},
{
mult: "Hacking Money",
current: Player.hacking_money_mult,
augmented: Player.hacking_money_mult * mults.hacking_money_mult,
bnMult: BitNodeMultipliers.ScriptHackMoney,
},
{
mult: "Hacking Growth",
current: Player.hacking_grow_mult,
augmented: Player.hacking_grow_mult * mults.hacking_grow_mult,
},
{
mult: "Hacking Level",
current: Player.hacking_mult,
augmented: Player.hacking_mult * mults.hacking_mult,
bnMult: BitNodeMultipliers.HackingLevelMultiplier,
},
{
mult: "Hacking Experience",
current: Player.hacking_exp_mult,
augmented: Player.hacking_exp_mult * mults.hacking_exp_mult,
bnMult: BitNodeMultipliers.HackExpGain,
},
].map((data: MultiplierListItemData) =>
Object.defineProperty(data, "color", {
value: 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,
],
{
mult: "Strength Level",
current: Player.strength_mult,
augmented: Player.strength_mult * mults.strength_mult,
bnMult: BitNodeMultipliers.StrengthLevelMultiplier,
},
{
mult: "Strength Experience",
current: Player.strength_exp_mult,
augmented: Player.strength_exp_mult * mults.strength_exp_mult,
},
{
mult: "Defense Level",
current: Player.defense_mult,
augmented: Player.defense_mult * mults.defense_mult,
bnMult: BitNodeMultipliers.DefenseLevelMultiplier,
},
{
mult: "Defense Experience",
current: Player.defense_exp_mult,
augmented: Player.defense_exp_mult * mults.defense_exp_mult,
},
{
mult: "Dexterity Level",
current: Player.dexterity_mult,
augmented: Player.dexterity_mult * mults.dexterity_mult,
bnMult: BitNodeMultipliers.DexterityLevelMultiplier,
},
{
mult: "Dexterity Experience",
current: Player.dexterity_exp_mult,
augmented: Player.dexterity_exp_mult * mults.dexterity_exp_mult,
},
{
mult: "Agility Level",
current: Player.agility_mult,
augmented: Player.agility_mult * mults.agility_mult,
bnMult: BitNodeMultipliers.AgilityLevelMultiplier,
},
{
mult: "Agility Experience",
current: Player.agility_exp_mult,
augmented: Player.agility_exp_mult * mults.agility_exp_mult,
},
].map((data: MultiplierListItemData) =>
Object.defineProperty(data, "color", {
value: Settings.theme.combat,
}),
),
{
mult: "Charisma Level",
current: Player.charisma_mult,
augmented: Player.charisma_mult * mults.charisma_mult,
bnMult: BitNodeMultipliers.CharismaLevelMultiplier,
color: Settings.theme.cha,
},
{
mult: "Charisma Experience",
current: Player.charisma_exp_mult,
augmented: Player.charisma_exp_mult * mults.charisma_exp_mult,
color: 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,
],
{
mult: "Hacknet Node Production",
current: Player.hacknet_node_money_mult,
augmented: Player.hacknet_node_money_mult * mults.hacknet_node_money_mult,
bnMult: BitNodeMultipliers.HacknetNodeMoney,
},
{
mult: "Hacknet Node Purchase Cost",
current: Player.hacknet_node_purchase_cost_mult,
augmented: Player.hacknet_node_purchase_cost_mult * mults.hacknet_node_purchase_cost_mult,
},
{
mult: "Hacknet Node RAM Upgrade Cost",
current: Player.hacknet_node_ram_cost_mult,
augmented: Player.hacknet_node_ram_cost_mult * mults.hacknet_node_ram_cost_mult,
},
{
mult: "Hacknet Node Core Purchase Cost",
current: Player.hacknet_node_core_cost_mult,
augmented: Player.hacknet_node_core_cost_mult * mults.hacknet_node_core_cost_mult,
},
{
mult: "Hacknet Node Level Upgrade Cost",
current: Player.hacknet_node_level_cost_mult,
augmented: Player.hacknet_node_level_cost_mult * mults.hacknet_node_level_cost_mult,
},
{
mult: "Company Reputation Gain",
current: Player.company_rep_mult,
augmented: Player.company_rep_mult * mults.company_rep_mult,
},
{
mult: "Faction Reputation Gain",
current: Player.faction_rep_mult,
augmented: Player.faction_rep_mult * mults.faction_rep_mult,
bnMult: BitNodeMultipliers.FactionWorkRepGain,
},
{
mult: "Salary",
current: Player.work_money_mult,
augmented: Player.work_money_mult * mults.work_money_mult,
bnMult: BitNodeMultipliers.CompanyWorkMoney,
color: Settings.theme.money,
},
{
mult: "Crime Success Chance",
current: Player.crime_success_mult,
augmented: Player.crime_success_mult * mults.crime_success_mult,
color: Settings.theme.combat,
},
{
mult: "Crime Money",
current: Player.crime_money_mult,
augmented: Player.crime_money_mult * mults.crime_money_mult,
bnMult: BitNodeMultipliers.CrimeMoney,
color: 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])),
{
mult: "Bladeburner Success Chance",
current: Player.bladeburner_success_chance_mult,
augmented: Player.bladeburner_success_chance_mult * mults.bladeburner_success_chance_mult,
},
{
mult: "Bladeburner Max Stamina",
current: Player.bladeburner_max_stamina_mult,
augmented: Player.bladeburner_max_stamina_mult * mults.bladeburner_max_stamina_mult,
},
{
mult: "Bladeburner Stamina Gain",
current: Player.bladeburner_stamina_gain_mult,
augmented: Player.bladeburner_stamina_gain_mult * mults.bladeburner_stamina_gain_mult,
},
{
mult: "Bladeburner Field Analysis",
current: Player.bladeburner_analysis_mult,
augmented: Player.bladeburner_analysis_mult * mults.bladeburner_analysis_mult,
},
);
}
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)`,
display: "flex",
flexDirection: "column",
flexWrap: "wrap",
gap: 1,
}}
>
<MultiplierList rows={leftColData} />

@ -504,6 +504,8 @@ export const defaultMultipliers: IBitNodeMultipliers = {
WorldDaemonDifficulty: 1,
};
Object.freeze(defaultMultipliers);
export function getBitNodeMultipliers(n: number, lvl: number): IBitNodeMultipliers {
const mults = Object.assign({}, defaultMultipliers);
switch (n) {

@ -252,4 +252,4 @@ export interface IBitNodeMultipliers {
* The multipliers that are influenced by current Bitnode progression.
*/
// tslint:disable-next-line:variable-name
export const BitNodeMultipliers = defaultMultipliers;
export const BitNodeMultipliers = Object.assign({}, defaultMultipliers);

@ -1,554 +1,331 @@
import ExpandMore from "@mui/icons-material/ExpandMore";
import ExpandLess from "@mui/icons-material/ExpandLess";
import { Box, Collapse, ListItemButton, ListItemText, Paper, Typography } from "@mui/material";
import ExpandMore from "@mui/icons-material/ExpandMore";
import { Box, Collapse, ListItemButton, ListItemText, Paper, Table, TableBody, Typography } from "@mui/material";
import { uniqueId } from "lodash";
import React from "react";
import { SpecialServers } from "../../Server/data/SpecialServers";
import { Settings } from "../../Settings/Settings";
import { use } from "../../ui/Context";
import { StatsRow } from "../../ui/React/StatsRow";
import { defaultMultipliers, getBitNodeMultipliers } from "../BitNode";
import { IBitNodeMultipliers } from "../BitNodeMultipliers";
import { SpecialServers } from "../../Server/data/SpecialServers";
interface IProps {
n: number;
level?: number;
}
export function BitnodeMultiplierDescription({ n }: IProps): React.ReactElement {
const player = use.Player();
export function BitnodeMultiplierDescription({ n, level }: IProps): React.ReactElement {
const [open, setOpen] = React.useState(false);
const mults = getBitNodeMultipliers(n, player.sourceFileLvl(n));
if (n === 1) return <></>;
return (
<>
<br />
<Box component={Paper}>
<ListItemButton onClick={() => setOpen((old) => !old)}>
<ListItemText primary={<Typography>Bitnode multipliers:</Typography>} />
{open ? <ExpandLess color="primary" /> : <ExpandMore color="primary" />}
</ListItemButton>
<Box mx={2}>
<Collapse in={open}>
<GeneralMults n={n} mults={mults} />
<FactionMults n={n} mults={mults} />
<AugmentationMults n={n} mults={mults} />
<StockMults n={n} mults={mults} />
<SkillMults n={n} mults={mults} />
<HackingMults n={n} mults={mults} />
<PurchasedServersMults n={n} mults={mults} />
<CrimeMults n={n} mults={mults} />
<InfiltrationMults n={n} mults={mults} />
<CompanyMults n={n} mults={mults} />
<GangMults n={n} mults={mults} />
<CorporationMults n={n} mults={mults} />
<BladeburnerMults n={n} mults={mults} />
<StanekMults n={n} mults={mults} />
<br />
</Collapse>
</Box>
</Box>
</>
<Box component={Paper} sx={{ mt: 1, p: 1 }}>
<ListItemButton disableGutters onClick={() => setOpen((old) => !old)}>
<ListItemText primary={<Typography variant="h6">Bitnode Multipliers</Typography>} />
{open ? <ExpandLess color="primary" /> : <ExpandMore color="primary" />}
</ListItemButton>
<Collapse in={open}>
<BitNodeMultipliersDisplay n={n} level={level} />
</Collapse>
</Box>
);
}
export const BitNodeMultipliersDisplay = ({ n, level }: IProps): React.ReactElement => {
const player = use.Player();
// If a level argument has been provided, use that as the multiplier level
// If not, then we have to assume that we want the next level up from the
// current node's source file, so we get the min of that, the SF's max level,
// or if it's BN12, ∞
const maxSfLevel = n === 12 ? Infinity : 3;
const mults = getBitNodeMultipliers(n, level ?? Math.min(player.sourceFileLvl(n) + 1, maxSfLevel));
return (
<Box sx={{ columnCount: 2, columnGap: 1, mb: -2 }}>
<GeneralMults n={n} mults={mults} />
<SkillMults n={n} mults={mults} />
<FactionMults n={n} mults={mults} />
<AugmentationMults n={n} mults={mults} />
<HackingMults n={n} mults={mults} />
<PurchasedServersMults n={n} mults={mults} />
<StockMults n={n} mults={mults} />
<CrimeMults n={n} mults={mults} />
<InfiltrationMults n={n} mults={mults} />
<CompanyMults n={n} mults={mults} />
<GangMults n={n} mults={mults} />
<CorporationMults n={n} mults={mults} />
<BladeburnerMults n={n} mults={mults} />
<StanekMults n={n} mults={mults} />
</Box>
);
};
interface IBNMultRows {
[mult: string]: {
name: string;
content?: string;
color?: string;
};
}
interface IBNMultTableProps {
sectionName: string;
rowData: IBNMultRows;
mults: IBitNodeMultipliers;
}
const BNMultTable = (props: IBNMultTableProps): React.ReactElement => {
const rowsArray = Object.entries(props.rowData)
.filter(([key, _value]) => props.mults[key] !== defaultMultipliers[key])
.map(([key, value]) => (
<StatsRow
key={uniqueId()}
name={value.name}
data={{ content: value.content ?? `${(props.mults[key] * 100).toFixed(3)}%` }}
color={value.color ?? Settings.theme.primary}
/>
));
return rowsArray.length > 0 ? (
<span style={{ display: "inline-block", width: "100%", marginBottom: "16px" }}>
<Typography variant="h6">{props.sectionName}</Typography>
<Table>
<TableBody>{rowsArray}</TableBody>
</Table>
</span>
) : (
<></>
);
};
interface IMultsProps {
n: number;
mults: IBitNodeMultipliers;
}
function GeneralMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.ClassGymExpGain === defaultMultipliers.ClassGymExpGain &&
mults.CodingContractMoney === defaultMultipliers.CodingContractMoney &&
mults.DaedalusAugsRequirement === defaultMultipliers.DaedalusAugsRequirement &&
mults.WorldDaemonDifficulty === defaultMultipliers.WorldDaemonDifficulty &&
mults.HacknetNodeMoney === defaultMultipliers.HacknetNodeMoney
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>General:</Typography>
<Box mx={1}>
{mults.WorldDaemonDifficulty !== defaultMultipliers.WorldDaemonDifficulty ? (
<Typography>
{SpecialServers.WorldDaemon} difficulty: x{mults.WorldDaemonDifficulty.toFixed(3)}
</Typography>
) : (
<></>
)}
{mults.DaedalusAugsRequirement !== defaultMultipliers.DaedalusAugsRequirement ? (
<Typography>Daedalus aug req.: {mults.DaedalusAugsRequirement}</Typography>
) : (
<></>
)}
{mults.HacknetNodeMoney !== defaultMultipliers.HacknetNodeMoney ? (
<Typography>Hacknet production: x{mults.HacknetNodeMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.CodingContractMoney !== defaultMultipliers.CodingContractMoney ? (
<Typography>Coding contract reward: x{mults.CodingContractMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ClassGymExpGain !== defaultMultipliers.ClassGymExpGain ? (
<Typography>Class/Gym exp: x{mults.ClassGymExpGain.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
WorldDaemonDifficulty: { name: `${SpecialServers.WorldDaemon} Difficulty` },
DaedalusAugsRequirement: {
name: "Daedalus Augs Requirement",
content: String(mults.DaedalusAugsRequirement),
},
HacknetNodeMoney: { name: "Hacknet Production" },
CodingContractMoney: { name: "Coding Contract Reward" },
ClassGymExpGain: { name: "Class/Gym Exp" },
};
return <BNMultTable sectionName="General" rowData={rows} mults={mults} />;
}
function AugmentationMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.AugmentationMoneyCost === defaultMultipliers.AugmentationMoneyCost &&
mults.AugmentationRepCost === defaultMultipliers.AugmentationRepCost
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Augmentations:</Typography>
<Box mx={1}>
{mults.AugmentationMoneyCost !== defaultMultipliers.AugmentationMoneyCost ? (
<Typography>Cost: x{mults.AugmentationMoneyCost.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.AugmentationRepCost !== defaultMultipliers.AugmentationRepCost ? (
<Typography>Reputation: x{mults.AugmentationRepCost.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
AugmentationMoneyCost: { name: "Money Cost" },
AugmentationRepCost: {
name: "Reputation Cost",
color: Settings.theme.rep,
},
};
return <BNMultTable sectionName="Augmentations" rowData={rows} mults={mults} />;
}
function CompanyMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.CompanyWorkExpGain === defaultMultipliers.CompanyWorkExpGain &&
mults.CompanyWorkMoney === defaultMultipliers.CompanyWorkMoney
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Company:</Typography>
<Box mx={1}>
{mults.CompanyWorkMoney !== defaultMultipliers.CompanyWorkMoney ? (
<Typography>Money: x{mults.CompanyWorkMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.CompanyWorkExpGain !== defaultMultipliers.CompanyWorkExpGain ? (
<Typography>Exp: x{mults.CompanyWorkExpGain.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
CompanyWorkMoney: {
name: "Work Money",
color: Settings.theme.money,
},
CompanyWorkExpGain: { name: "Work Exp" },
};
return <BNMultTable sectionName="Company" rowData={rows} mults={mults} />;
}
function StockMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.FourSigmaMarketDataApiCost === defaultMultipliers.FourSigmaMarketDataApiCost &&
mults.FourSigmaMarketDataCost === defaultMultipliers.FourSigmaMarketDataCost
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Stock market:</Typography>
<Box mx={1}>
{mults.FourSigmaMarketDataCost !== defaultMultipliers.FourSigmaMarketDataCost ? (
<Typography>Market data cost: x{mults.FourSigmaMarketDataCost.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.FourSigmaMarketDataApiCost !== defaultMultipliers.FourSigmaMarketDataApiCost ? (
<Typography>Market data API cost: x{mults.FourSigmaMarketDataApiCost.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
FourSigmaMarketDataCost: { name: "Market Data Cost" },
FourSigmaMarketDataApiCost: { name: "Market Data API Cost" },
};
return <BNMultTable sectionName="Stock Market" rowData={rows} mults={mults} />;
}
function FactionMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.FactionPassiveRepGain === defaultMultipliers.FactionPassiveRepGain &&
mults.FactionWorkExpGain === defaultMultipliers.FactionWorkExpGain &&
mults.FactionWorkRepGain === defaultMultipliers.FactionWorkRepGain &&
mults.RepToDonateToFaction === defaultMultipliers.RepToDonateToFaction
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Faction:</Typography>
<Box mx={1}>
{mults.RepToDonateToFaction !== defaultMultipliers.RepToDonateToFaction ? (
<Typography>Favor to donate: x{mults.RepToDonateToFaction.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.FactionWorkRepGain !== defaultMultipliers.FactionWorkRepGain ? (
<Typography>Work rep: x{mults.FactionWorkRepGain.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.FactionWorkExpGain !== defaultMultipliers.FactionWorkExpGain ? (
<Typography>Work exp: x{mults.FactionWorkExpGain.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.FactionPassiveRepGain !== defaultMultipliers.FactionPassiveRepGain ? (
<Typography>Passive rep: x{mults.FactionPassiveRepGain.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
RepToDonateToFaction: { name: "Favor to Donate" },
FactionWorkRepGain: {
name: "Work Reputation",
color: Settings.theme.rep,
},
FactionWorkExpGain: { name: "Work Exp" },
FactionPassiveRepGain: {
name: "Passive Rep",
color: Settings.theme.rep,
},
};
return <BNMultTable sectionName="Faction" rowData={rows} mults={mults} />;
}
function CrimeMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (mults.CrimeExpGain === defaultMultipliers.CrimeExpGain && mults.CrimeMoney === defaultMultipliers.CrimeMoney)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Crime:</Typography>
<Box mx={1}>
{mults.CrimeExpGain !== defaultMultipliers.CrimeExpGain ? (
<Typography>Exp: x{mults.CrimeExpGain.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.CrimeMoney !== defaultMultipliers.CrimeMoney ? (
<Typography>Money: x{mults.CrimeMoney.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
CrimeExpGain: {
name: "Crime Exp",
color: Settings.theme.combat,
},
CrimeMoney: {
name: "Crime Money",
color: Settings.theme.combat,
},
};
return <BNMultTable sectionName="Crime" rowData={rows} mults={mults} />;
}
function SkillMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.HackingLevelMultiplier === defaultMultipliers.HackingLevelMultiplier &&
mults.AgilityLevelMultiplier === defaultMultipliers.AgilityLevelMultiplier &&
mults.DefenseLevelMultiplier === defaultMultipliers.DefenseLevelMultiplier &&
mults.DexterityLevelMultiplier === defaultMultipliers.DexterityLevelMultiplier &&
mults.StrengthLevelMultiplier === defaultMultipliers.StrengthLevelMultiplier &&
mults.CharismaLevelMultiplier === defaultMultipliers.CharismaLevelMultiplier
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Skills:</Typography>
<Box mx={1}>
{mults.HackingLevelMultiplier !== defaultMultipliers.HackingLevelMultiplier ? (
<Typography>Hacking: x{mults.HackingLevelMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.AgilityLevelMultiplier !== defaultMultipliers.AgilityLevelMultiplier ? (
<Typography>Agility: x{mults.AgilityLevelMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.DefenseLevelMultiplier !== defaultMultipliers.DefenseLevelMultiplier ? (
<Typography>Defense: x{mults.DefenseLevelMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.DexterityLevelMultiplier !== defaultMultipliers.DexterityLevelMultiplier ? (
<Typography>Dexterity: x{mults.DexterityLevelMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.StrengthLevelMultiplier !== defaultMultipliers.StrengthLevelMultiplier ? (
<Typography>Strength: x{mults.StrengthLevelMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.CharismaLevelMultiplier !== defaultMultipliers.CharismaLevelMultiplier ? (
<Typography>Charisma: x{mults.CharismaLevelMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
HackingLevelMultiplier: {
name: "Hacking Level",
color: Settings.theme.hack,
},
StrengthLevelMultiplier: {
name: "Strength Level",
color: Settings.theme.combat,
},
DefenseLevelMultiplier: {
name: "Defense Level",
color: Settings.theme.combat,
},
DexterityLevelMultiplier: {
name: "Dexterity Level",
color: Settings.theme.combat,
},
CharismaLevelMultiplier: {
name: "Charisma Level",
color: Settings.theme.cha,
},
};
return <BNMultTable sectionName="Skills" rowData={rows} mults={mults} />;
}
function HackingMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.ServerGrowthRate === defaultMultipliers.ServerGrowthRate &&
mults.ServerMaxMoney === defaultMultipliers.ServerMaxMoney &&
mults.ServerStartingMoney === defaultMultipliers.ServerStartingMoney &&
mults.ServerStartingSecurity === defaultMultipliers.ServerStartingSecurity &&
mults.ServerWeakenRate === defaultMultipliers.ServerWeakenRate &&
mults.ManualHackMoney === defaultMultipliers.ManualHackMoney &&
mults.ScriptHackMoney === defaultMultipliers.ScriptHackMoney &&
mults.ScriptHackMoneyGain === defaultMultipliers.ScriptHackMoneyGain &&
mults.HackExpGain === defaultMultipliers.HackExpGain
)
return <></>;
const rows: IBNMultRows = {
HackExpGain: {
name: "Hacking Exp",
color: Settings.theme.hack,
},
ServerGrowthRate: { name: "Server Growth Rate" },
ServerMaxMoney: { name: "Server Max Money" },
ServerStartingMoney: { name: "Server Starting Money" },
ServerStartingSecurity: { name: "Server Starting Security" },
ServerWeakenRate: { name: "Server Weaken Rate" },
ManualHackMoney: {
name: "Manual Hack Money",
color: Settings.theme.money,
},
ScriptHackMoney: {
name: "Script Hack Money",
color: Settings.theme.money,
},
ScriptHackMoneyGain: {
name: "Money Gained From Hack",
color: Settings.theme.money,
},
};
return (
<>
<br />
<Typography variant={"h5"}>Hacking:</Typography>
<Box mx={1}>
{mults.HackExpGain !== defaultMultipliers.HackExpGain ? (
<Typography>Exp: x{mults.HackExpGain.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ServerGrowthRate !== defaultMultipliers.ServerGrowthRate ? (
<Typography>Growth rate: x{mults.ServerGrowthRate.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ServerMaxMoney !== defaultMultipliers.ServerMaxMoney ? (
<Typography>Max money: x{mults.ServerMaxMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ServerStartingMoney !== defaultMultipliers.ServerStartingMoney ? (
<Typography>Starting money: x{mults.ServerStartingMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ServerStartingSecurity !== defaultMultipliers.ServerStartingSecurity ? (
<Typography>Starting security: x{mults.ServerStartingSecurity.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ServerWeakenRate !== defaultMultipliers.ServerWeakenRate ? (
<Typography>Weaken rate: x{mults.ServerWeakenRate.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ManualHackMoney !== defaultMultipliers.ManualHackMoney ? (
<Typography>Manual hack money: x{mults.ManualHackMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ScriptHackMoney !== defaultMultipliers.ScriptHackMoney ? (
<Typography>Hack money stolen: x{mults.ScriptHackMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ScriptHackMoneyGain !== defaultMultipliers.ScriptHackMoneyGain ? (
<Typography>Money gained from hack: x{mults.ScriptHackMoneyGain.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
return <BNMultTable sectionName="Hacking" rowData={rows} mults={mults} />;
}
function PurchasedServersMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.PurchasedServerCost === defaultMultipliers.PurchasedServerCost &&
mults.PurchasedServerSoftcap === defaultMultipliers.PurchasedServerSoftcap &&
mults.PurchasedServerLimit === defaultMultipliers.PurchasedServerLimit &&
mults.PurchasedServerMaxRam === defaultMultipliers.PurchasedServerMaxRam &&
mults.HomeComputerRamCost === defaultMultipliers.HomeComputerRamCost
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Purchased servers:</Typography>
<Box mx={1}>
{mults.PurchasedServerCost !== defaultMultipliers.PurchasedServerCost ? (
<Typography>Base cost: {mults.PurchasedServerCost.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.PurchasedServerSoftcap !== defaultMultipliers.PurchasedServerSoftcap ? (
<Typography>Softcap cost: {mults.PurchasedServerSoftcap.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.PurchasedServerLimit !== defaultMultipliers.PurchasedServerLimit ? (
<Typography>Limit: x{mults.PurchasedServerLimit.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.PurchasedServerMaxRam !== defaultMultipliers.PurchasedServerMaxRam ? (
<Typography>Max ram: x{mults.PurchasedServerMaxRam.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.HomeComputerRamCost !== defaultMultipliers.HomeComputerRamCost ? (
<Typography>Home ram cost: x{mults.HomeComputerRamCost.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
PurchasedServerCost: {
name: "Base Cost",
content: mults.PurchasedServerCost.toFixed(3),
},
PurchasedServerSoftcap: {
name: "Softcap Cost",
content: mults.PurchasedServerSoftcap.toFixed(3),
},
PurchasedServerLimit: { name: "Server Limit" },
PurchasedServerMaxRam: { name: "Max RAM" },
HomeComputerRamCost: { name: "Home RAM Cost" },
};
return <BNMultTable sectionName="Purchased Servers" rowData={rows} mults={mults} />;
}
function InfiltrationMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.InfiltrationMoney === defaultMultipliers.InfiltrationMoney &&
mults.InfiltrationRep === defaultMultipliers.InfiltrationRep
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Infiltration:</Typography>
<Box mx={1}>
{mults.InfiltrationMoney !== defaultMultipliers.InfiltrationMoney ? (
<Typography>Money: {mults.InfiltrationMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.InfiltrationRep !== defaultMultipliers.InfiltrationRep ? (
<Typography>Reputation: x{mults.InfiltrationRep.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
InfiltrationMoney: {
name: "Infiltration Money",
color: Settings.theme.money,
},
InfiltrationRep: {
name: "Infiltration Reputation",
color: Settings.theme.rep,
},
};
return <BNMultTable sectionName="Infiltration" rowData={rows} mults={mults} />;
}
function BladeburnerMults({ n, mults }: IMultsProps): React.ReactElement {
function BladeburnerMults({ mults }: IMultsProps): React.ReactElement {
const player = use.Player();
// access check
if (n !== 6 && n !== 7 && player.sourceFileLvl(6) === 0) return <></>;
//default mults check
if (mults.BladeburnerRank === 1 && mults.BladeburnerSkillCost === 1) return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Bladeburner:</Typography>
<Box mx={1}>
{mults.BladeburnerRank !== 1 ? <Typography>Rank gain: x{mults.BladeburnerRank.toFixed(3)}</Typography> : <></>}
{mults.BladeburnerSkillCost !== 1 ? (
<Typography>Skill cost: x{mults.BladeburnerSkillCost.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
if (!player.canAccessBladeburner()) return <></>;
const rows: IBNMultRows = {
BladeburnerRank: { name: "Rank Gain" },
BladeburnerSkillCost: { name: "Skill Cost" },
};
return <BNMultTable sectionName="Bladeburner" rowData={rows} mults={mults} />;
}
function StanekMults({ n, mults }: IMultsProps): React.ReactElement {
function StanekMults({ mults }: IMultsProps): React.ReactElement {
const player = use.Player();
// access check
if (n !== 13 && player.sourceFileLvl(13) === 0) return <></>;
//default mults check
if (
mults.StaneksGiftExtraSize === defaultMultipliers.StaneksGiftExtraSize &&
mults.StaneksGiftPowerMultiplier === defaultMultipliers.StaneksGiftPowerMultiplier
)
return <></>;
if (!player.canAccessCotMG()) return <></>;
const s = mults.StaneksGiftExtraSize;
return (
<>
<br />
<Typography variant={"h5"}>Stanek's Gift:</Typography>
<Box mx={1}>
{mults.StaneksGiftPowerMultiplier !== defaultMultipliers.StaneksGiftPowerMultiplier ? (
<Typography>Gift power: x{mults.StaneksGiftPowerMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
{s !== defaultMultipliers.StaneksGiftExtraSize ? (
<Typography>Base size modifier: {s > defaultMultipliers.StaneksGiftExtraSize ? `+${s}` : s}</Typography>
) : (
<></>
)}
</Box>
</>
);
const extraSize = mults.StaneksGiftExtraSize.toFixed(3);
const rows: IBNMultRows = {
StnakesGiftPowerMultiplier: { name: "Gift Power" },
StaneksGiftExtraSize: {
name: "Base Size Modifier",
content: `${mults.StaneksGiftExtraSize > defaultMultipliers.StaneksGiftExtraSize ? `+${extraSize}` : extraSize}`,
},
};
return <BNMultTable sectionName="Stanek's Gift" rowData={rows} mults={mults} />;
}
function GangMults({ n, mults }: IMultsProps): React.ReactElement {
function GangMults({ mults }: IMultsProps): React.ReactElement {
const player = use.Player();
// access check
if (n !== 2 && player.sourceFileLvl(2) === 0) return <></>;
// is it empty check
if (
mults.GangSoftcap === defaultMultipliers.GangSoftcap &&
mults.GangUniqueAugs === defaultMultipliers.GangUniqueAugs
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Gang:</Typography>
<Box mx={1}>
{mults.GangSoftcap !== defaultMultipliers.GangSoftcap ? (
<Typography>Softcap: {mults.GangSoftcap.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.GangUniqueAugs !== defaultMultipliers.GangUniqueAugs ? (
<Typography>Unique augs: x{mults.GangUniqueAugs.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
if (player.bitNodeN !== 2 && player.sourceFileLvl(2) <= 0) return <></>;
const rows: IBNMultRows = {
GangSoftcap: {
name: "Gang Softcap",
content: mults.GangSoftcap.toFixed(3),
},
GangUniqueAugs: { name: "Unique Augmentations" },
};
return <BNMultTable sectionName="Gang" rowData={rows} mults={mults} />;
}
function CorporationMults({ n, mults }: IMultsProps): React.ReactElement {
function CorporationMults({ mults }: IMultsProps): React.ReactElement {
const player = use.Player();
// access check
if (n !== 3 && player.sourceFileLvl(3) === 0) return <></>;
// is it empty check
if (
mults.CorporationSoftcap === defaultMultipliers.CorporationSoftcap &&
mults.CorporationValuation === defaultMultipliers.CorporationValuation
)
return <></>;
if (!player.canAccessCorporation()) return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Corporation:</Typography>
<Box mx={1}>
{mults.CorporationSoftcap !== defaultMultipliers.CorporationSoftcap ? (
<Typography>Softcap: {mults.CorporationSoftcap.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.CorporationValuation !== defaultMultipliers.CorporationValuation ? (
<Typography>Valuation: x{mults.CorporationValuation.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
CorporationSoftcap: {
name: "Corporation Softcap",
content: mults.CorporationSoftcap.toFixed(3),
},
CorporationValuation: { name: "Valuation" },
};
return <BNMultTable sectionName="Corporation" rowData={rows} mults={mults} />;
}

@ -41,7 +41,7 @@ export function PortalModal(props: IProps): React.ReactElement {
<br />
<br />
<Typography>{bitNode.info}</Typography>
<BitnodeMultiplierDescription n={props.n} />
<BitnodeMultiplierDescription n={props.n} level={newLevel} />
<br />
<br />
<Button

@ -67,8 +67,18 @@ SourceFiles["SourceFile5"] = new SourceFile(
This Source-File grants a special new stat called Intelligence. Intelligence is unique because it is permanent and
persistent (it never gets reset back to 1). However, gaining Intelligence experience is much slower than other
stats. Higher Intelligence levels will boost your production for many actions in the game. In addition, this
Source-File will unlock the getBitNodeMultipliers() Netscript function and let you start with Formulas.exe, and
will raise all of your hacking-related multipliers by:
Source-File will unlock:
<br />
<ul>
<li>
The <code>getBitNodeMultipliers()</code> Netscript function
</li>
<li>Permanent access to Formulas.exe</li>
<li>
Access to BitNode multiplier information on the <b>Stats</b> page
</li>
</ul>
It will also raise all of your hacking-related multipliers by:
<br />
<br />
Level 1: 8%

@ -1,8 +1,9 @@
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 { BitNodes, defaultMultipliers, getBitNodeMultipliers } from "../BitNode/BitNode";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { BitNodeMultipliersDisplay } from "../BitNode/ui/BitnodeMultipliersDescription";
import { HacknetServerConstants } from "../Hacknet/data/Constants";
import { getPurchaseServerLimit } from "../Server/ServerPurchases";
import { Settings } from "../Settings/Settings";
@ -14,6 +15,7 @@ import { Modal } from "./React/Modal";
import { Money } from "./React/Money";
import { StatsRow } from "./React/StatsRow";
import { StatsTable } from "./React/StatsTable";
import { isEqual } from "lodash";
interface EmployersModalProps {
open: boolean;
@ -36,8 +38,22 @@ const EmployersModal = ({ open, onClose }: EmployersModalProps): React.ReactElem
);
};
interface IMultRow {
// The name of the multiplier
mult: string;
// The player's raw multiplier value
value: number;
// The player's effective multiplier value, affected by BitNode mults
effValue?: number;
// The text color for the row
color?: string;
}
interface MultTableProps {
rows: (string | number)[][];
rows: IMultRow[];
color: string;
noMargin?: boolean;
}
@ -48,29 +64,22 @@ function MultiplierTable(props: MultTableProps): React.ReactElement {
<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;
const { mult, value, effValue = null, color = props.color } = data;
if (modded && modded !== value && player.sourceFileLvl(5) > 0) {
if (effValue !== null && effValue !== value && player.sourceFileLvl(5) > 0) {
return (
<StatsRow key={mult} name={mult} color={props.color} data={{}}>
<StatsRow key={mult} name={mult} color={color} data={{}}>
<>
<Typography color={props.color}>
<Typography color={color}>
<span style={{ opacity: 0.5 }}>{numeralWrapper.formatPercentage(value)}</span>{" "}
{numeralWrapper.formatPercentage(modded)}
{numeralWrapper.formatPercentage(effValue)}
</Typography>
</>
</StatsRow>
);
}
return (
<StatsRow
key={mult}
name={mult}
color={props.color}
data={{ content: numeralWrapper.formatPercentage(value) }}
/>
<StatsRow key={mult} name={mult} color={color} data={{ content: numeralWrapper.formatPercentage(value) }} />
);
})}
</TableBody>
@ -82,16 +91,14 @@ function CurrentBitNode(): React.ReactElement {
const player = use.Player();
if (player.sourceFiles.length > 0) {
const index = "BitNode" + player.bitNodeN;
const lvl = player.sourceFileLvl(player.bitNodeN) + 1;
const lvl = Math.min(player.sourceFileLvl(player.bitNodeN) + 1, player.bitNodeN === 12 ? Infinity : 3);
return (
<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>
<Paper sx={{ mb: 1, 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>
);
}
@ -218,6 +225,14 @@ export function CharacterStats(): React.ReactElement {
}
timeRows.push(["Total", convertTimeMsToTimeElapsedString(player.totalPlaytime)]);
let showBitNodeMults = false;
if (player.sourceFileLvl(5) > 0) {
const n = player.bitNodeN;
const maxSfLevel = n === 12 ? Infinity : 3;
const mults = getBitNodeMultipliers(n, Math.min(player.sourceFileLvl(n) + 1, maxSfLevel));
showBitNodeMults = !isEqual(mults, defaultMultipliers);
}
return (
<Container maxWidth="lg" disableGutters sx={{ mx: 0 }}>
<Typography variant="h4">Stats</Typography>
@ -332,186 +347,255 @@ export function CharacterStats(): React.ReactElement {
</Table>
</Paper>
</Box>
<Box sx={{ mb: 1 }}>
<Paper sx={{ p: 1 }}>
<Typography variant="h5" color="primary" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
Multipliers
{player.sourceFileLvl(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>
<Box>
<Paper sx={{ p: 1, mb: 1 }}>
<Typography variant="h5" color="primary" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
Multipliers
{player.sourceFileLvl(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={[
{
mult: "Hacking Chance",
value: player.hacking_chance_mult,
},
{
mult: "Hacking Speed",
value: player.hacking_speed_mult,
},
{
mult: "Hacking Money",
value: player.hacking_money_mult,
effValue: player.hacking_money_mult * BitNodeMultipliers.ScriptHackMoney,
},
{
mult: "Hacking Growth",
value: player.hacking_grow_mult,
effValue: player.hacking_grow_mult * BitNodeMultipliers.ServerGrowthRate,
},
]}
color={Settings.theme.hack}
/>
<MultiplierTable
rows={[
{
mult: "Hacking Level",
value: player.hacking_mult,
effValue: player.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier,
},
{
mult: "Hacking Experience",
value: player.hacking_exp_mult,
effValue: player.hacking_exp_mult * BitNodeMultipliers.HackExpGain,
},
]}
color={Settings.theme.hack}
/>
<MultiplierTable
rows={[
{
mult: "Strength Level",
value: player.strength_mult,
effValue: player.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier,
},
{
mult: "Strength Experience",
value: player.strength_exp_mult,
},
]}
color={Settings.theme.combat}
/>
<MultiplierTable
rows={[
{
mult: "Defense Level",
value: player.defense_mult,
effValue: player.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier,
},
{
mult: "Defense Experience",
value: player.defense_exp_mult,
},
]}
color={Settings.theme.combat}
/>
<MultiplierTable
rows={[
{
mult: "Dexterity Level",
value: player.dexterity_mult,
effValue: player.dexterity_mult * BitNodeMultipliers.DexterityLevelMultiplier,
},
{
mult: "Dexterity Experience",
value: player.dexterity_exp_mult,
},
]}
color={Settings.theme.combat}
/>
<MultiplierTable
rows={[
{
mult: "Agility Level",
value: player.agility_mult,
effValue: player.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier,
},
{
mult: "Agility Experience",
value: player.agility_exp_mult,
},
]}
color={Settings.theme.combat}
/>
<MultiplierTable
rows={[
{
mult: "Charisma Level",
value: player.charisma_mult,
effValue: player.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier,
},
{
mult: "Charisma Experience",
value: player.charisma_exp_mult,
},
]}
color={Settings.theme.cha}
noMargin
/>
</Box>
<Box>
<MultiplierTable
rows={[
{
mult: "Hacknet Node Production",
value: player.hacknet_node_money_mult,
effValue: player.hacknet_node_money_mult * BitNodeMultipliers.HacknetNodeMoney,
},
{
mult: "Hacknet Node Purchase Cost",
value: player.hacknet_node_purchase_cost_mult,
},
{
mult: "Hacknet Node RAM Upgrade Cost",
value: player.hacknet_node_ram_cost_mult,
},
{
mult: "Hacknet Node Core Purchase Cost",
value: player.hacknet_node_core_cost_mult,
},
{
mult: "Hacknet Node Level Upgrade Cost",
value: player.hacknet_node_level_cost_mult,
},
]}
color={Settings.theme.primary}
/>
<MultiplierTable
rows={[
{
mult: "Company Reputation Gain",
value: player.company_rep_mult,
color: Settings.theme.rep,
},
{
mult: "Faction Reputation Gain",
value: player.faction_rep_mult,
effValue: player.faction_rep_mult * BitNodeMultipliers.FactionWorkRepGain,
color: Settings.theme.rep,
},
{
mult: "Salary",
value: player.work_money_mult,
effValue: player.work_money_mult * BitNodeMultipliers.CompanyWorkMoney,
color: Settings.theme.money,
},
]}
color={Settings.theme.money}
/>
<MultiplierTable
rows={[
{
mult: "Crime Success Chance",
value: player.crime_success_mult,
},
{
mult: "Crime Money",
value: player.crime_money_mult,
effValue: player.crime_money_mult * BitNodeMultipliers.CrimeMoney,
color: Settings.theme.money,
},
]}
color={Settings.theme.combat}
/>
{player.canAccessBladeburner() && (
<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],
{
mult: "Bladeburner Success Chance",
value: player.bladeburner_success_chance_mult,
},
{
mult: "Bladeburner Max Stamina",
value: player.bladeburner_max_stamina_mult,
},
{
mult: "Bladeburner Stamina Gain",
value: player.bladeburner_stamina_gain_mult,
},
{
mult: "Bladeburner Field Analysis",
value: player.bladeburner_analysis_mult,
},
]}
color={Settings.theme.primary}
noMargin
/>
<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>
</Box>
</Paper>
<Paper sx={{ p: 1, mb: 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 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>
<CurrentBitNode />
{showBitNodeMults && (
<Paper sx={{ p: 1, mb: 1 }}>
<Typography variant="h5">BitNode Multipliers</Typography>
<BitNodeMultipliersDisplay n={player.bitNodeN} />
</Paper>
)}
<MoneyModal open={moneyOpen} onClose={() => setMoneyOpen(false)} />
<EmployersModal open={employersOpen} onClose={() => setEmployersOpen(false)} />
</Container>