bitburner-src/src/ui/CharacterStats.tsx

513 lines
18 KiB
TypeScript
Raw Normal View History

2022-04-08 01:32:11 +02:00
import { Paper, Table, TableBody, Box, IconButton, Typography, Container, Tooltip } from "@mui/material";
import { MoreHoriz, Info } from "@mui/icons-material";
2022-04-07 18:31:06 +02:00
import React, { useEffect, useState } from "react";
import { BitNodes } from "../BitNode/BitNode";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { BitNodeMultipliersDisplay } from "../BitNode/ui/BitnodeMultipliersDescription";
import { HacknetServerConstants } from "../Hacknet/data/Constants";
2022-04-07 18:31:06 +02:00
import { getPurchaseServerLimit } from "../Server/ServerPurchases";
import { Settings } from "../Settings/Settings";
2021-09-20 05:29:02 +02:00
import { MoneySourceTracker } from "../utils/MoneySourceTracker";
2022-04-07 18:31:06 +02:00
import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
import { use } from "./Context";
import { numeralWrapper } from "./numeralFormat";
2021-09-18 19:29:01 +02:00
import { Modal } from "./React/Modal";
2022-04-07 18:31:06 +02:00
import { Money } from "./React/Money";
import { StatsRow } from "./React/StatsRow";
import { StatsTable } from "./React/StatsTable";
2021-09-10 21:08:51 +02:00
2022-04-07 18:50:23 +02:00
interface EmployersModalProps {
open: boolean;
onClose: () => void;
}
const EmployersModal = ({ open, onClose }: EmployersModalProps): React.ReactElement => {
2021-09-18 09:00:07 +02:00
const player = use.Player();
2022-04-07 18:50:23 +02:00
return (
<Modal open={open} onClose={onClose}>
2021-09-18 09:00:07 +02:00
<>
2022-04-08 01:32:11 +02:00
<Typography variant="h5">All Employers</Typography>
2021-09-18 09:00:07 +02:00
<ul>
{Object.keys(player.jobs).map((j) => (
2022-04-07 18:50:23 +02:00
<Typography key={j}>* {j}</Typography>
2021-09-18 09:00:07 +02:00
))}
</ul>
</>
2022-04-07 18:50:23 +02:00
</Modal>
);
};
2021-09-10 21:08:51 +02:00
2022-04-07 18:31:06 +02:00
interface MultTableProps {
rows: (string | number)[][];
color: string;
2022-04-08 02:06:42 +02:00
noMargin?: boolean;
2021-09-18 09:00:07 +02:00
}
2022-04-07 18:31:06 +02:00
function MultiplierTable(props: MultTableProps): React.ReactElement {
2022-04-14 07:22:50 +02:00
const player = use.Player();
2021-09-18 09:00:07 +02:00
return (
2022-04-08 02:06:42 +02:00
<Table sx={{ display: "table", width: "100%", mb: (props.noMargin ?? false) === true ? 0 : 2 }}>
2022-04-07 18:31:06 +02:00
<TableBody>
{props.rows.map((data) => {
const mult = data[0] as string,
value = data[1] as number,
modded = data[2] as number | null;
2022-04-14 07:22:50 +02:00
if (modded && modded !== value && player.sourceFileLvl(5) > 0) {
2022-04-07 18:31:06 +02:00
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>
2021-09-18 09:00:07 +02:00
);
}
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;
2021-09-18 09:00:07 +02:00
return (
2022-05-07 21:48:26 +02:00
<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>
2021-09-18 09:00:07 +02:00
);
2021-09-05 01:09:30 +02:00
}
2021-09-18 09:00:07 +02:00
return <></>;
}
2021-09-18 19:29:01 +02:00
interface IMoneyModalProps {
open: boolean;
onClose: () => void;
}
2021-09-18 09:00:07 +02:00
2021-09-18 19:29:01 +02:00
function MoneyModal({ open, onClose }: IMoneyModalProps): React.ReactElement {
const player = use.Player();
2021-09-09 05:47:34 +02:00
function convertMoneySourceTrackerToString(src: MoneySourceTracker): React.ReactElement {
2021-09-05 01:09:30 +02:00
const parts: any[][] = [[`Total:`, <Money money={src.total} />]];
2021-10-27 20:18:33 +02:00
if (src.augmentations) {
parts.push([`Augmentations:`, <Money money={src.augmentations} />]);
}
2021-09-05 01:09:30 +02:00
if (src.bladeburner) {
parts.push([`Bladeburner:`, <Money money={src.bladeburner} />]);
}
2021-10-27 20:18:33 +02:00
if (src.casino) {
parts.push([`Casino:`, <Money money={src.casino} />]);
}
2021-09-05 01:09:30 +02:00
if (src.codingcontract) {
parts.push([`Coding Contracts:`, <Money money={src.codingcontract} />]);
}
2021-09-05 01:09:30 +02:00
if (src.work) {
parts.push([`Company Work:`, <Money money={src.work} />]);
}
if (src.class) {
parts.push([`Class:`, <Money money={src.class} />]);
}
if (src.corporation) {
parts.push([`Corporation:`, <Money money={src.corporation} />]);
}
if (src.crime) {
parts.push([`Crimes:`, <Money money={src.crime} />]);
}
if (src.gang) {
parts.push([`Gang:`, <Money money={src.gang} />]);
}
if (src.hacking) {
parts.push([`Hacking:`, <Money money={src.hacking} />]);
}
2021-10-27 20:18:33 +02:00
if (src.hacknet) {
parts.push([`Hacknet Nodes:`, <Money money={src.hacknet} />]);
2021-09-05 01:09:30 +02:00
}
2021-11-06 02:01:23 +01:00
if (src.hacknet_expenses) {
parts.push([`Hacknet Nodes Expenses:`, <Money money={src.hacknet_expenses} />]);
}
2021-09-05 01:09:30 +02:00
if (src.hospitalization) {
parts.push([`Hospitalization:`, <Money money={src.hospitalization} />]);
}
if (src.infiltration) {
parts.push([`Infiltration:`, <Money money={src.infiltration} />]);
}
2021-10-27 20:18:33 +02:00
if (src.servers) {
parts.push([`Servers:`, <Money money={src.servers} />]);
}
2021-09-05 01:09:30 +02:00
if (src.stock) {
parts.push([`Stock Market:`, <Money money={src.stock} />]);
}
if (src.sleeves) {
parts.push([`Sleeves:`, <Money money={src.sleeves} />]);
}
2021-10-27 20:18:33 +02:00
if (src.other) {
parts.push([`Other:`, <Money money={src.other} />]);
}
2021-09-18 19:29:01 +02:00
return <StatsTable rows={parts} wide />;
2021-09-05 01:09:30 +02:00
}
2021-09-18 19:29:01 +02:00
let content = (
<>
<Typography variant="h6" color="primary">
Money earned since you last installed Augmentations
</Typography>
<br />
{convertMoneySourceTrackerToString(player.moneySourceA)}
</>
);
if (player.sourceFiles.length !== 0) {
content = (
2021-09-05 01:09:30 +02:00
<>
2021-09-18 19:29:01 +02:00
{content}
2021-09-05 01:09:30 +02:00
<br />
2021-09-18 19:29:01 +02:00
<br />
<Typography variant="h6" color="primary">
Money earned in this BitNode
</Typography>
<br />
{convertMoneySourceTrackerToString(player.moneySourceB)}
2021-09-05 01:09:30 +02:00
</>
);
2021-09-18 19:29:01 +02:00
}
2021-09-18 19:29:01 +02:00
return (
<Modal open={open} onClose={onClose}>
{content}
</Modal>
);
}
export function CharacterStats(): React.ReactElement {
const player = use.Player();
const [moneyOpen, setMoneyOpen] = useState(false);
2022-04-07 18:50:23 +02:00
const [employersOpen, setEmployersOpen] = useState(false);
2021-09-18 19:29:01 +02:00
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
2021-09-05 01:09:30 +02:00
}
2021-09-18 19:29:01 +02:00
useEffect(() => {
2021-09-25 19:31:42 +02:00
const id = setInterval(rerender, 200);
2021-09-18 19:29:01 +02:00
return () => clearInterval(id);
}, []);
2021-09-10 21:08:51 +02:00
const timeRows = [
2022-04-07 18:31:06 +02:00
["Since last Augmentation installation", convertTimeMsToTimeElapsedString(player.playtimeSinceLastAug)],
2021-09-10 21:08:51 +02:00
];
2021-09-18 09:00:07 +02:00
if (player.sourceFiles.length > 0) {
2022-04-07 18:31:06 +02:00
timeRows.push(["Since last Bitnode destroyed", convertTimeMsToTimeElapsedString(player.playtimeSinceLastBitnode)]);
2021-09-05 01:09:30 +02:00
}
2022-04-07 18:31:06 +02:00
timeRows.push(["Total", convertTimeMsToTimeElapsedString(player.totalPlaytime)]);
2021-09-05 01:09:30 +02:00
return (
2022-04-08 01:32:11 +02:00
<Container maxWidth="lg" disableGutters sx={{ mx: 0 }}>
<Typography variant="h4">Stats</Typography>
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr", minWidth: "fit-content", mb: 1, gap: 1 }}>
2022-04-07 18:31:06 +02:00
<Paper sx={{ p: 1 }}>
2022-04-08 01:32:11 +02:00
<Typography variant="h5">General</Typography>
2022-04-07 18:31:06 +02:00
<Table>
<TableBody>
<StatsRow name="Current City" color={Settings.theme.primary} data={{ content: player.city }} />
<StatsRow name="Money" color={Settings.theme.money} data={{}}>
<>
<Money money={player.money} />
<IconButton onClick={() => setMoneyOpen(true)} sx={{ p: 0 }}>
2022-04-08 01:32:11 +02:00
<MoreHoriz color="info" />
2022-04-07 18:31:06 +02:00
</IconButton>
</>
</StatsRow>
2022-04-09 16:06:32 +02:00
{player.companyName ? (
2022-04-07 18:31:06 +02:00
<>
<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] }}
/>
</>
2022-04-09 16:06:32 +02:00
) : (
<></>
2022-04-07 18:31:06 +02:00
)}
2022-04-09 16:06:32 +02:00
{player.jobs && Object.keys(player.jobs).length !== 0 ? (
2022-04-07 18:50:23 +02:00
<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 }}>
2022-04-08 01:32:11 +02:00
<MoreHoriz color="info" />
2022-04-07 18:50:23 +02:00
</IconButton>
</>
</StatsRow>
2022-04-09 16:06:32 +02:00
) : (
<></>
2022-04-07 18:50:23 +02:00
)}
2022-04-07 18:31:06 +02:00
<StatsRow
name="Servers Owned"
color={Settings.theme.primary}
data={{ content: `${player.purchasedServers.length} / ${getPurchaseServerLimit()}` }}
/>
<StatsRow
2022-04-14 07:22:50 +02:00
name={`Hacknet ${player.bitNodeN === 9 || player.sourceFileLvl(9) > 0 ? "Servers" : "Nodes"} owned`}
2022-04-07 18:31:06 +02:00
color={Settings.theme.primary}
data={{
content: `${player.hacknetNodes.length}${
2022-04-14 07:22:50 +02:00
player.bitNodeN === 9 || player.sourceFileLvl(9) > 0
? ` / ${HacknetServerConstants.MaxServers}`
: ""
2022-04-07 18:31:06 +02:00
}`,
}}
/>
<StatsRow
name="Augmentations Installed"
color={Settings.theme.primary}
data={{ content: String(player.augmentations.length) }}
/>
</TableBody>
</Table>
</Paper>
<Paper sx={{ p: 1 }}>
2022-04-08 01:32:11 +02:00
<Typography variant="h5">Skills</Typography>
2022-04-07 18:31:06 +02:00
<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 }}
/>
2022-04-14 07:22:50 +02:00
{player.intelligence > 0 && (player.bitNodeN === 5 || player.sourceFileLvl(5) > 0) && (
2022-04-07 18:31:06 +02:00
<StatsRow
name="Intelligence"
color={Settings.theme.int}
data={{ level: player.intelligence, exp: player.intelligence_exp }}
/>
)}
</TableBody>
</Table>
</Paper>
2021-09-18 19:29:01 +02:00
</Box>
2022-04-07 18:31:06 +02:00
2022-05-07 21:48:26 +02:00
<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={[
["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>
<MultiplierTable
rows={[
[
"Hacknet Node production",
player.hacknet_node_money_mult,
player.hacknet_node_money_mult * BitNodeMultipliers.HacknetNodeMoney,
],
["Hacknet Node purchase cost", player.hacknet_node_purchase_cost_mult],
["Hacknet Node RAM upgrade cost", player.hacknet_node_ram_cost_mult],
["Hacknet Node Core purchase cost", player.hacknet_node_core_cost_mult],
["Hacknet Node level upgrade cost", player.hacknet_node_level_cost_mult],
]}
color={Settings.theme.primary}
/>
<MultiplierTable
rows={[
["Company reputation gain", player.company_rep_mult],
[
"Faction reputation gain",
player.faction_rep_mult,
player.faction_rep_mult * BitNodeMultipliers.FactionWorkRepGain,
],
["Salary", player.work_money_mult, player.work_money_mult * BitNodeMultipliers.CompanyWorkMoney],
]}
color={Settings.theme.money}
/>
<MultiplierTable
rows={[
["Crime success", player.crime_success_mult],
["Crime money", player.crime_money_mult, player.crime_money_mult * BitNodeMultipliers.CrimeMoney],
]}
color={Settings.theme.combat}
/>
{player.canAccessBladeburner() && (
2022-04-07 18:31:06 +02:00
<MultiplierTable
rows={[
2022-05-07 21:48:26 +02:00
["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],
2022-04-07 18:31:06 +02:00
]}
color={Settings.theme.primary}
2022-05-07 21:48:26 +02:00
noMargin
2022-04-07 18:31:06 +02:00
/>
2022-05-07 21:48:26 +02:00
)}
2022-04-07 18:31:06 +02:00
</Box>
2022-05-07 21:48:26 +02:00
</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>
2021-09-18 19:29:01 +02:00
<CurrentBitNode />
{player.bitNodeN !== 1 && player.sourceFileLvl(5) > 0 && (
<Paper sx={{ p: 1, mb: 1 }}>
<Typography variant="h5">BitNode Multipliers</Typography>
<BitNodeMultipliersDisplay n={player.bitNodeN} />
</Paper>
)}
2021-09-18 19:29:01 +02:00
<MoneyModal open={moneyOpen} onClose={() => setMoneyOpen(false)} />
2022-04-07 18:50:23 +02:00
<EmployersModal open={employersOpen} onClose={() => setEmployersOpen(false)} />
2022-04-08 01:32:11 +02:00
</Container>
2021-09-05 01:09:30 +02:00
);
}