import type { ActionIdentifier } from "../../Bladeburner/Types"; // Root React Component for the Corporation UI import React, { useMemo, useState, useEffect, ReactNode } from "react"; import { Box, Button, IconButton, Table, TableBody, TableCell, TableRow, Tooltip, Typography } from "@mui/material"; import SaveIcon from "@mui/icons-material/Save"; import ClearAllIcon from "@mui/icons-material/ClearAll"; import { Theme, useTheme } from "@mui/material/styles"; import { makeStyles } from "tss-react/mui"; import { Player } from "@player"; import { formatHp, formatMoney, formatSkill } from "../formatNumber"; import { Reputation } from "./Reputation"; import { KillScriptsModal } from "./KillScriptsModal"; import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions"; import { Settings } from "../../Settings/Settings"; import { Router } from "../GameRoot"; import { Page } from "../Router"; import { StatsProgressBar } from "./StatsProgressBar"; import { isClassWork } from "../../Work/ClassWork"; import { CONSTANTS } from "../../Constants"; import { isCreateProgramWork } from "../../Work/CreateProgramWork"; import { isGraftingWork } from "../../Work/GraftingWork"; import { isFactionWork } from "../../Work/FactionWork"; import { ReputationRate } from "./ReputationRate"; import { isCompanyWork } from "../../Work/CompanyWork"; import { isCrimeWork } from "../../Work/CrimeWork"; import { EventEmitter } from "../../utils/EventEmitter"; import { useRerender } from "./hooks"; import { RemoteFileApiConnectionStatus } from "../../GameOptions/ui/RemoteFileApiConnectionStatus"; export const OverviewEventEmitter = new EventEmitter(); // These values aren't displayed, they're just used for comparison to check if state has changed const valUpdaters = { HP: () => Player.hp.current + "|" + Player.hp.max, // This isn't displayed, it's just compared for updates. Money: () => Player.money, Hack: () => Player.skills.hacking, Str: () => Player.skills.strength, Def: () => Player.skills.defense, Dex: () => Player.skills.dexterity, Agi: () => Player.skills.agility, Cha: () => Player.skills.charisma, Int: () => Player.skills.intelligence, } as const; //These formattedVals functions don't take in a value because of the weirdness around HP. const formattedVals = { HP: () => `${formatHp(Player.hp.current)} / ${formatHp(Player.hp.max)}`, Money: () => formatMoney(Player.money), Hack: () => formatSkill(Player.skills.hacking), Str: () => formatSkill(Player.skills.strength), Def: () => formatSkill(Player.skills.defense), Dex: () => formatSkill(Player.skills.dexterity), Agi: () => formatSkill(Player.skills.agility), Cha: () => formatSkill(Player.skills.charisma), Int: () => formatSkill(Player.skills.intelligence), } as const; interface ValProps { name: keyof typeof valUpdaters; color?: string; } export function Val({ name, color }: ValProps): React.ReactElement { //val isn't actually used here, the update of val just forces a refresh of the formattedVal that gets shown const [__, setVal] = useState(valUpdaters[name]()); useEffect(() => { const clearSubscription = OverviewEventEmitter.subscribe(() => setVal(valUpdaters[name]())); return clearSubscription; }, [name]); if (name === "Int" && Player.bitNodeOptions.intelligenceOverride !== undefined) { return ( {formatSkill(Player.skills.intelligence)} * ); } return {formattedVals[name]()}; } interface DataRowProps { name: keyof typeof formattedVals; //name for UI display showBar: boolean; color?: string; cellType: "cellNone" | "cell"; } export function DataRow({ name, showBar, color, cellType }: DataRowProps): React.ReactElement { const { classes } = useStyles(); const skillBar = showBar && ; return ( <> {name}  {} {skillBar} ); } interface OverviewProps { parentOpen: boolean; save: () => void; killScripts: () => void; } export function CharacterOverview({ parentOpen, save, killScripts }: OverviewProps): React.ReactElement { const [killOpen, setKillOpen] = useState(false); const [hasIntelligence, setHasIntelligence] = useState(Player.skills.intelligence > 0); const [showBars, setShowBars] = useState(!Settings.DisableOverviewProgressBars); useEffect(() => { if (!parentOpen) return; // No rerendering if overview is hidden, for performance const interval = setInterval(() => { setHasIntelligence(Player.skills.intelligence > 0); setShowBars(!Settings.DisableOverviewProgressBars); OverviewEventEmitter.emit(); // Tell every other updating component to update as well }, 600); return () => clearInterval(interval); }, [parentOpen]); const { classes } = useStyles(); const theme = useTheme(); return ( <> {hasIntelligence ? ( ) : ( <> )} {} {} {}
setKillOpen(true)}> setKillOpen(false)} killScripts={killScripts} /> ); } function ActionText({ action }: { action: ActionIdentifier }): React.ReactElement { const bladeburner = Player.bladeburner; if (!bladeburner) return <>; return ( {action.type}: {action.name} ); } function BladeburnerText(): React.ReactElement { const { classes } = useStyles(); const rerender = useRerender(); useEffect(() => { const clearSubscription = OverviewEventEmitter.subscribe(rerender); return clearSubscription; }, [rerender]); const action = Player.bladeburner?.action; return useMemo( () => !action ? ( <> ) : ( <> Bladeburner: ), [action, classes.cellNone], ); } interface WorkInProgressOverviewProps { tooltip: React.ReactNode; header: React.ReactNode; children: React.ReactNode; } const onClickFocusWork = (): void => { Player.startFocusing(); Router.toPage(Page.Work); }; function WorkInProgressOverview({ tooltip, children, header }: WorkInProgressOverviewProps): React.ReactElement { const { classes } = useStyles(); return ( <> {tooltip}}> {header} {children} {useMemo( () => ( ), [classes.cellNone], )} ); } function Work(): React.ReactElement { const rerender = useRerender(); useEffect(() => { const clearSubscription = OverviewEventEmitter.subscribe(rerender); return clearSubscription; }, [rerender]); if (Player.currentWork === null || Player.focus) return <>; let details: ReactNode = ""; let header: ReactNode = ""; let innerText: ReactNode = ""; if (isCrimeWork(Player.currentWork)) { const crime = Player.currentWork.getCrime(); const perc = (Player.currentWork.unitCompleted / crime.time) * 100; details = <>{Player.currentWork.crimeType}; header = <>You are attempting to {Player.currentWork.crimeType}; innerText = <>{perc.toFixed(2)}%; } if (isClassWork(Player.currentWork)) { details = <>{Player.currentWork.getClass().youAreCurrently}; header = <>You are {Player.currentWork.getClass().youAreCurrently}; innerText = <>{convertTimeMsToTimeElapsedString(Player.currentWork.cyclesWorked * CONSTANTS.MilliPerCycle)}; } if (isCreateProgramWork(Player.currentWork)) { const create = Player.currentWork; details = <>Coding {create.programName}; header = <>Creating a program; innerText = ( <> {create.programName} {((create.unitCompleted / create.unitNeeded()) * 100).toFixed(2)}% ); } if (isGraftingWork(Player.currentWork)) { const graft = Player.currentWork; details = <>Grafting {graft.augmentation}; header = <>Grafting an Augmentation; innerText = ( <> {((graft.unitCompleted / graft.unitNeeded()) * 100).toFixed(2)}% done ); } if (isFactionWork(Player.currentWork)) { const factionWork = Player.currentWork; details = <>Doing {factionWork.factionWorkType} work; header = ( <> Working for {factionWork.factionName} ); innerText = ( <> rep
( ) ); } if (isCompanyWork(Player.currentWork)) { const companyWork = Player.currentWork; const job = Player.jobs[companyWork.companyName]; if (!job) return <>; details = <>{job}; header = ( <> Working at {companyWork.companyName} ); innerText = ( <> rep
( ) ); } return ( {innerText} ); } const useStyles = makeStyles()((theme: Theme) => ({ workCell: { textAlign: "center", maxWidth: "200px", borderBottom: "none", padding: 0, margin: 0, }, workHeader: { fontSize: "0.9rem", }, workSubtitles: { fontSize: "0.8rem", }, cellNone: { borderBottom: "none", padding: 0, margin: 0, }, cell: { padding: 0, margin: 0, }, hp: { color: theme.colors.hp, }, money: { color: theme.colors.money, }, hack: { color: theme.colors.hack, }, combat: { color: theme.colors.combat, }, cha: { color: theme.colors.cha, }, int: { color: theme.colors.int, }, })); export { useStyles };