bitburner-src/src/ui/WorkInProgressRoot.tsx

537 lines
15 KiB
TypeScript
Raw Normal View History

import React from "react";
import { Box, Button, Container, Paper, Table, TableBody, Tooltip, Typography } from "@mui/material";
import { Player } from "@player";
import { FactionWorkType, LocationName } from "@enums";
2022-05-03 00:31:40 +02:00
import { Money } from "./React/Money";
import { MoneyRate } from "./React/MoneyRate";
import { ProgressBar } from "./React/Progress";
2021-09-18 01:43:08 +02:00
import { Reputation } from "./React/Reputation";
import { ReputationRate } from "./React/ReputationRate";
2022-05-03 00:31:40 +02:00
import { StatsRow } from "./React/StatsRow";
import { useRerender } from "./React/hooks";
import { Companies } from "../Company/Companies";
import { CONSTANTS } from "../Constants";
import { Locations } from "../Locations/Locations";
import { Settings } from "../Settings/Settings";
import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
import { filterTruthy } from "../utils/helpers/ArrayHelpers";
2022-07-07 08:00:23 +02:00
import { isCrimeWork } from "../Work/CrimeWork";
import { isClassWork } from "../Work/ClassWork";
2022-07-12 07:59:23 +02:00
import { WorkStats } from "../Work/WorkStats";
2022-07-10 07:37:36 +02:00
import { isCreateProgramWork } from "../Work/CreateProgramWork";
import { isGraftingWork } from "../Work/GraftingWork";
import { isFactionWork } from "../Work/FactionWork";
import { isCompanyWork } from "../Work/CompanyWork";
import { Router } from "./GameRoot";
import { Page } from "./Router";
import { formatExp, formatPercent } from "./formatNumber";
2021-09-18 01:43:08 +02:00
const CYCLES_PER_SEC = 1000 / CONSTANTS.MilliPerCycle;
2022-05-02 23:34:17 +02:00
interface IWorkInfo {
buttons: {
cancel: () => void;
unfocus?: () => void;
};
title: string | React.ReactElement;
description?: string | React.ReactElement;
gains?: React.ReactElement[];
2022-05-02 23:34:17 +02:00
progress?: {
elapsed?: number;
2022-05-03 00:31:40 +02:00
remaining?: number;
2022-05-02 23:34:17 +02:00
percentage?: number;
};
stopText: string;
stopTooltip?: string | React.ReactElement;
}
2022-07-26 14:08:51 +02:00
function ExpRows(rate: WorkStats): React.ReactElement[] {
return filterTruthy([
rate.hackExp > 0 && (
<StatsRow
key="hack"
name="Hacking Exp"
color={Settings.theme.hack}
data={{
content: `${formatExp(rate.hackExp * CYCLES_PER_SEC)} / sec`,
}}
/>
),
rate.strExp > 0 && (
<StatsRow
key="str"
name="Strength Exp"
color={Settings.theme.combat}
data={{
content: `${formatExp(rate.strExp * CYCLES_PER_SEC)} / sec`,
}}
/>
),
rate.defExp > 0 && (
<StatsRow
key="def"
name="Defense Exp"
color={Settings.theme.combat}
data={{
content: `${formatExp(rate.defExp * CYCLES_PER_SEC)} / sec`,
}}
/>
),
rate.dexExp > 0 && (
<StatsRow
key="dex"
name="Dexterity Exp"
color={Settings.theme.combat}
data={{
content: `${formatExp(rate.dexExp * CYCLES_PER_SEC)} / sec`,
}}
/>
),
rate.agiExp > 0 && (
<StatsRow
key="agi"
name="Agility Exp"
color={Settings.theme.combat}
data={{
content: `${formatExp(rate.agiExp * CYCLES_PER_SEC)} / sec`,
}}
/>
),
rate.chaExp > 0 && (
<StatsRow
key="cha"
name="Charisma Exp"
color={Settings.theme.cha}
data={{
content: `${formatExp(rate.chaExp * CYCLES_PER_SEC)} / sec`,
}}
/>
),
]);
}
/* Because crime exp is given all at once at the end, we don't care about the cycles per second. */
2022-07-26 14:08:51 +02:00
function CrimeExpRows(rate: WorkStats): React.ReactElement[] {
return filterTruthy([
rate.hackExp > 0 && (
2022-07-26 14:08:51 +02:00
<StatsRow
key="hack"
2022-07-26 14:08:51 +02:00
name="Hacking Exp"
color={Settings.theme.hack}
data={{
content: `${formatExp(rate.hackExp)}`,
2022-07-26 14:08:51 +02:00
}}
/>
),
rate.strExp > 0 && (
2022-07-26 14:08:51 +02:00
<StatsRow
key="str"
2022-07-26 14:08:51 +02:00
name="Strength Exp"
color={Settings.theme.combat}
data={{
content: `${formatExp(rate.strExp)}`,
2022-07-26 14:08:51 +02:00
}}
/>
),
rate.defExp > 0 && (
2022-07-26 14:08:51 +02:00
<StatsRow
key="def"
2022-07-26 14:08:51 +02:00
name="Defense Exp"
color={Settings.theme.combat}
data={{
content: `${formatExp(rate.defExp)}`,
2022-07-26 14:08:51 +02:00
}}
/>
),
rate.dexExp > 0 && (
2022-07-26 14:08:51 +02:00
<StatsRow
key="dex"
2022-07-26 14:08:51 +02:00
name="Dexterity Exp"
color={Settings.theme.combat}
data={{
content: `${formatExp(rate.dexExp)}`,
2022-07-26 14:08:51 +02:00
}}
/>
),
rate.agiExp > 0 && (
2022-07-26 14:08:51 +02:00
<StatsRow
key="agi"
2022-07-26 14:08:51 +02:00
name="Agility Exp"
color={Settings.theme.combat}
data={{
content: `${formatExp(rate.agiExp)}`,
2022-07-26 14:08:51 +02:00
}}
/>
),
rate.chaExp > 0 && (
2022-07-26 14:08:51 +02:00
<StatsRow
key="cha"
2022-07-26 14:08:51 +02:00
name="Charisma Exp"
color={Settings.theme.cha}
data={{
content: `${formatExp(rate.chaExp)}`,
2022-07-26 14:08:51 +02:00
}}
/>
),
]);
2022-07-26 14:08:51 +02:00
}
2021-09-18 01:43:08 +02:00
export function WorkInProgressRoot(): React.ReactElement {
useRerender(CONSTANTS.MilliPerCycle);
2022-07-07 08:00:23 +02:00
let workInfo: IWorkInfo = {
buttons: {
cancel: () => undefined,
},
title: "",
stopText: "",
};
2022-09-06 15:07:12 +02:00
if (Player.currentWork === null) {
2022-12-04 09:14:06 +01:00
setTimeout(() => Router.toPage(Page.Terminal));
2022-07-19 20:21:12 +02:00
return <></>;
}
2022-07-07 08:00:23 +02:00
2022-09-06 15:07:12 +02:00
if (isCrimeWork(Player.currentWork)) {
const crime = Player.currentWork.getCrime();
const completion = (Player.currentWork.unitCompleted / crime.time) * 100;
const gains = Player.currentWork.earnings();
const successChance = crime.successRate(Player);
2022-07-19 20:21:12 +02:00
workInfo = {
buttons: {
cancel: () => {
Router.toPage(Page.Location, { location: Locations[LocationName.Slums] });
2022-09-06 15:07:12 +02:00
Player.finishWork(true);
2022-07-19 20:21:12 +02:00
},
unfocus: () => {
2022-12-04 09:14:06 +01:00
Router.toPage(Page.City);
2022-09-06 15:07:12 +02:00
Player.stopFocusing();
2022-07-07 08:00:23 +02:00
},
2022-07-19 20:21:12 +02:00
},
2022-08-26 20:03:30 +02:00
title: `You are attempting ${crime.workName}`,
2022-07-07 08:00:23 +02:00
2022-07-26 14:08:51 +02:00
gains: [
<tr key="header">
<td>
<Typography>Success chance: {formatPercent(successChance)}</Typography>
<Typography>Gains (on success)</Typography>
</td>
</tr>,
<StatsRow key="money" name="Money:" color={Settings.theme.money}>
2022-07-26 14:08:51 +02:00
<Typography>
<Money money={gains.money} />
</Typography>
</StatsRow>,
...CrimeExpRows(gains),
],
2022-07-19 20:21:12 +02:00
progress: {
2022-09-06 15:07:12 +02:00
remaining: crime.time - Player.currentWork.unitCompleted,
2022-07-19 20:21:12 +02:00
percentage: completion,
},
2022-10-09 07:25:31 +02:00
stopText: "Stop committing crime",
2022-07-19 20:21:12 +02:00
};
}
2022-09-06 15:07:12 +02:00
if (isClassWork(Player.currentWork)) {
const classWork = Player.currentWork;
2022-07-19 20:21:12 +02:00
let stopText = "";
if (classWork.isGym()) {
stopText = "Stop training at gym";
} else {
stopText = "Stop taking course";
}
2022-07-10 07:37:36 +02:00
2022-09-06 15:07:12 +02:00
const rates = classWork.calculateRates();
2022-07-19 20:21:12 +02:00
workInfo = {
buttons: {
cancel: () => {
Player.finishWork(true);
Router.toPage(Page.City);
},
unfocus: () => {
Router.toPage(Page.City);
Player.stopFocusing();
},
2022-07-19 20:21:12 +02:00
},
title: (
<>
You are currently <b>{classWork.getClass().youAreCurrently}</b>
</>
),
gains: [
<StatsRow key="totalCost" name="Total Cost" color={Settings.theme.money}>
2022-07-19 20:21:12 +02:00
<Typography>
<Money money={classWork.earnings.money} /> (<MoneyRate money={rates.money * CYCLES_PER_SEC} />)
</Typography>
</StatsRow>,
...ExpRows(rates),
],
progress: {
2023-02-21 15:44:18 +01:00
elapsed: classWork.cyclesWorked * CONSTANTS.MilliPerCycle,
2022-07-19 20:21:12 +02:00
},
stopText: stopText,
};
}
2022-07-10 07:37:36 +02:00
2022-09-06 15:07:12 +02:00
if (isCreateProgramWork(Player.currentWork)) {
const create = Player.currentWork;
2022-07-19 20:21:12 +02:00
const completion = (create.unitCompleted / create.unitNeeded()) * 100;
workInfo = {
buttons: {
cancel: () => {
Player.finishWork(true);
Router.toPage(Page.Terminal);
},
unfocus: () => {
Router.toPage(Page.Terminal);
Player.stopFocusing();
},
2022-07-19 20:21:12 +02:00
},
title: (
<>
You are currently working on coding <b>{create.programName}</b>
</>
),
progress: {
2023-02-21 15:44:18 +01:00
elapsed: create.cyclesWorked * CONSTANTS.MilliPerCycle,
2022-07-19 20:21:12 +02:00
percentage: completion,
},
stopText: "Stop creating program",
stopTooltip: "Your work will be saved and you can return to complete the program later.",
};
}
2022-07-10 07:37:36 +02:00
2022-09-06 15:07:12 +02:00
if (isGraftingWork(Player.currentWork)) {
const graftWork = Player.currentWork;
2022-07-10 07:37:36 +02:00
2022-07-19 20:21:12 +02:00
workInfo = {
buttons: {
cancel: () => {
Player.finishWork(true);
Router.toPage(Page.Terminal);
},
unfocus: () => {
Router.toPage(Page.Terminal);
Player.stopFocusing();
},
2022-07-19 20:21:12 +02:00
},
title: (
<>
You are currently working on grafting <b>{graftWork.augmentation}</b>
2022-07-19 20:21:12 +02:00
</>
),
progress: {
elapsed: graftWork.cyclesWorked * CONSTANTS.MilliPerCycle,
percentage: (graftWork.unitCompleted / graftWork.unitNeeded()) * 100,
2022-07-19 20:21:12 +02:00
},
stopText: "Stop grafting",
stopTooltip: (
<>
If you cancel, your work will <b>not</b> be saved, and the money you spent will <b>not</b> be returned
</>
),
};
}
2022-07-10 07:37:36 +02:00
2022-09-06 15:07:12 +02:00
if (isFactionWork(Player.currentWork)) {
const faction = Player.currentWork.getFaction();
2022-07-19 20:21:12 +02:00
if (!faction) {
2022-07-10 07:37:36 +02:00
workInfo = {
buttons: {
2022-12-04 09:14:06 +01:00
cancel: () => Router.toPage(Page.Factions),
2022-07-10 07:37:36 +02:00
},
2022-07-19 20:21:12 +02:00
title:
2022-09-06 15:07:12 +02:00
`You have not joined ${Player.currentWork.factionName || "(Faction not found)"} at this time,` +
2022-07-19 20:21:12 +02:00
" please try again if you think this should have worked",
2022-07-10 07:37:36 +02:00
2022-07-19 20:21:12 +02:00
stopText: "Back to Factions",
2022-07-10 07:37:36 +02:00
};
}
2022-05-03 00:31:40 +02:00
2022-07-19 20:21:12 +02:00
const description = {
[FactionWorkType.hacking]: "carrying out hacking contracts",
[FactionWorkType.field]: "carrying out field missions",
[FactionWorkType.security]: "performing security detail",
2022-07-19 20:21:12 +02:00
};
2022-09-06 15:07:12 +02:00
const exp = Player.currentWork.getExpRates();
2022-07-19 20:21:12 +02:00
workInfo = {
buttons: {
cancel: () => {
Router.toPage(Page.Faction, { faction });
Player.finishWork(true);
},
unfocus: () => {
Router.toPage(Page.Faction, { faction });
Player.stopFocusing();
},
2022-07-19 20:21:12 +02:00
},
title: (
<>
2022-09-06 15:07:12 +02:00
You are currently {description[Player.currentWork.factionWorkType]} for <b>{faction.name}</b>
2022-07-19 20:21:12 +02:00
</>
),
description: (
<>
Current Faction Reputation: <Reputation reputation={faction.playerReputation} /> (
2022-09-06 15:07:12 +02:00
<ReputationRate reputation={Player.currentWork.getReputationRate() * CYCLES_PER_SEC} />)
2022-07-19 20:21:12 +02:00
</>
),
gains: ExpRows(exp),
progress: {
2023-02-21 15:44:18 +01:00
elapsed: Player.currentWork.cyclesWorked * CONSTANTS.MilliPerCycle,
2022-07-19 20:21:12 +02:00
},
stopText: "Stop Faction work",
};
}
2022-09-06 15:07:12 +02:00
if (isCompanyWork(Player.currentWork)) {
const comp = Companies[Player.currentWork.companyName];
if (comp) {
2022-05-03 00:31:40 +02:00
workInfo = {
buttons: {
2022-12-04 09:14:06 +01:00
cancel: () => Router.toPage(Page.Terminal),
2022-05-03 00:31:40 +02:00
},
2022-07-19 20:21:12 +02:00
title:
2022-09-06 15:07:12 +02:00
`You cannot work for ${Player.currentWork.companyName || "(Company not found)"} at this time,` +
2022-07-19 20:21:12 +02:00
" please try again if you think this should have worked",
2022-05-03 00:31:40 +02:00
2022-07-19 20:21:12 +02:00
stopText: "Back to Terminal",
2022-05-03 00:31:40 +02:00
};
2021-09-18 01:43:08 +02:00
}
2022-07-19 20:21:12 +02:00
const companyRep = comp.playerReputation;
2022-05-03 00:31:40 +02:00
2022-09-06 15:07:12 +02:00
const position = Player.jobs[Player.currentWork.companyName];
const gains = Player.currentWork.getGainRates();
2022-07-19 20:21:12 +02:00
workInfo = {
buttons: {
cancel: () => {
Player.finishWork(true);
Router.toPage(Page.Job, { location: Locations[comp.name] });
},
unfocus: () => {
Player.stopFocusing();
Router.toPage(Page.Job, { location: Locations[comp.name] });
},
2022-07-19 20:21:12 +02:00
},
title: (
<>
2022-09-06 15:07:12 +02:00
You are currently working as a <b>{position}</b> at <b>{Player.currentWork.companyName}</b>
2022-07-19 20:21:12 +02:00
</>
),
description: (
<>
Current Company Reputation: <Reputation reputation={companyRep} />
</>
),
gains: [
<StatsRow key="money" name="Money" color={Settings.theme.money}>
2022-07-19 20:21:12 +02:00
<Typography>
<MoneyRate money={gains.money * CYCLES_PER_SEC} />
</Typography>
</StatsRow>,
<StatsRow key="reputation" name="Company Reputation" color={Settings.theme.rep}>
2022-07-19 20:21:12 +02:00
<Typography>
<ReputationRate reputation={gains.reputation * CYCLES_PER_SEC} />
</Typography>
</StatsRow>,
...ExpRows(gains),
],
progress: {
2023-02-21 15:44:18 +01:00
elapsed: Player.currentWork.cyclesWorked * CONSTANTS.MilliPerCycle,
2022-07-19 20:21:12 +02:00
},
stopText: "Stop working",
};
2022-05-03 00:31:40 +02:00
}
2022-07-07 08:00:23 +02:00
if (workInfo.title === "") {
2022-05-03 00:31:40 +02:00
return <></>;
2022-03-19 15:31:48 +01:00
}
2022-05-03 00:31:40 +02:00
const tooltipInfo =
typeof workInfo.stopTooltip === "string" ? (
2022-05-03 00:31:40 +02:00
<Typography>{workInfo.stopTooltip}</Typography>
) : (
workInfo.stopTooltip || <></>
);
2022-05-02 23:34:17 +02:00
return (
<Container
maxWidth="md"
sx={{ display: "flex", flexDirection: "column", justifyContent: "center", height: "calc(100vh - 16px)" }}
>
<Paper sx={{ p: 1, mb: 1 }}>
<Typography variant="h6">{workInfo.title}</Typography>
<Typography>{workInfo.description}</Typography>
2022-05-03 00:31:40 +02:00
{workInfo.gains && (
<Table sx={{ mt: 1 }}>
<TableBody>{workInfo.gains}</TableBody>
2022-05-03 00:31:40 +02:00
</Table>
)}
2022-05-02 23:34:17 +02:00
</Paper>
<Paper sx={{ mb: 1, p: 1 }}>
{workInfo.progress !== undefined && (
<Box sx={{ mb: 1 }}>
<Box
display="grid"
sx={{
gridTemplateColumns: `repeat(${Object.keys(workInfo.progress).length}, 1fr)`,
width: "100%",
justifyItems: "center",
2022-05-03 00:31:40 +02:00
textAlign: "center",
2022-05-02 23:34:17 +02:00
}}
>
{workInfo.progress.elapsed !== undefined && (
<Typography>{convertTimeMsToTimeElapsedString(workInfo.progress.elapsed)} elapsed</Typography>
)}
2022-05-03 00:31:40 +02:00
{workInfo.progress.remaining !== undefined && (
<Typography>{convertTimeMsToTimeElapsedString(workInfo.progress.remaining)} remaining</Typography>
)}
2022-05-02 23:34:17 +02:00
{workInfo.progress.percentage !== undefined && (
<Typography>{workInfo.progress.percentage.toFixed(2)}% done</Typography>
)}
</Box>
{workInfo.progress.percentage !== undefined && (
<ProgressBar variant="determinate" value={workInfo.progress.percentage} color="primary" />
)}
</Box>
)}
2021-10-16 21:43:28 +02:00
2022-05-02 23:34:17 +02:00
<Box display="grid" sx={{ gridTemplateColumns: `repeat(${Object.keys(workInfo.buttons).length}, 1fr)` }}>
{workInfo.stopTooltip ? (
<Tooltip title={tooltipInfo}>
<Button onClick={workInfo.buttons.cancel}>{workInfo.stopText}</Button>
</Tooltip>
) : (
<Button onClick={workInfo.buttons.cancel}>{workInfo.stopText}</Button>
)}
{workInfo.buttons.unfocus && (
<Button onClick={workInfo.buttons.unfocus}>Do something else simultaneously</Button>
)}
</Box>
</Paper>
</Container>
);
2021-09-18 01:43:08 +02:00
}