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 };