Merge pull request #3184 from nickofolas/improvement/sleeve-and-clash-ui

Sleeve and Territory UI refresh
This commit is contained in:
hydroflame 2022-03-19 11:23:28 -04:00 committed by GitHub
commit 15badafd37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 457 additions and 360 deletions

@ -3,7 +3,6 @@
*/
import React, { useState } from "react";
import { useGang } from "./Context";
import { generateTableRow } from "./GangMemberStats";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
@ -22,7 +21,7 @@ import { GangMember } from "../GangMember";
import { UpgradeType } from "../data/upgrades";
import { use } from "../../ui/Context";
import { Settings } from "../../Settings/Settings";
import { characterOverviewStyles as useStyles } from "../../ui/React/CharacterOverview";
import { StatsRow } from "../../ui/React/StatsRow";
interface INextRevealProps {
upgrades: string[];
@ -91,7 +90,6 @@ interface IPanelProps {
}
function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {
const classes = useStyles();
const gang = useGang();
const player = use.Player();
const setRerender = useState(false)[1];
@ -178,12 +176,12 @@ function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {
>
<Table>
<TableBody>
{generateTableRow("Hacking", props.member.hack, props.member.hack_exp, Settings.theme.hack, classes)}
{generateTableRow("Strength", props.member.str, props.member.str_exp, Settings.theme.combat, classes)}
{generateTableRow("Defense", props.member.def, props.member.def_exp, Settings.theme.combat, classes)}
{generateTableRow("Dexterity", props.member.dex, props.member.dex_exp, Settings.theme.combat, classes)}
{generateTableRow("Agility", props.member.agi, props.member.agi_exp, Settings.theme.combat, classes)}
{generateTableRow("Charisma", props.member.cha, props.member.cha_exp, Settings.theme.cha, classes)}
<StatsRow name="Hacking" color={Settings.theme.hack} data={{ level: props.member.hack, exp: props.member.hack_exp }} />
<StatsRow name="Strength" color={Settings.theme.combat} data={{ level: props.member.str, exp: props.member.str_exp }} />
<StatsRow name="Defense" color={Settings.theme.combat} data={{ level: props.member.def, exp: props.member.def_exp }} />
<StatsRow name="Dexterity" color={Settings.theme.combat} data={{ level: props.member.dex, exp: props.member.dex_exp }} />
<StatsRow name="Agility" color={Settings.theme.combat} data={{ level: props.member.agi, exp: props.member.agi_exp }} />
<StatsRow name="Charisma" color={Settings.theme.cha} data={{ level: props.member.cha, exp: props.member.cha_exp }} />
</TableBody>
</Table>
</Tooltip>

@ -17,36 +17,14 @@ import {
import { numeralWrapper } from "../../ui/numeralFormat";
import { GangMember } from "../GangMember";
import { Settings } from "../../Settings/Settings";
import { formatNumber } from "../../utils/StringHelperFunctions";
import { MoneyRate } from "../../ui/React/MoneyRate";
import { StatsRow } from "../../ui/React/StatsRow";
import { characterOverviewStyles as useStyles } from "../../ui/React/CharacterOverview";
interface IProps {
member: GangMember;
}
export const generateTableRow = (
name: string,
level: number,
exp: number,
color: string,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
classes: any
): React.ReactElement => {
return (
<TableRow>
<TableCell classes={{ root: classes.cellNone }}>
<Typography style={{ color: color }}>{name}</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography style={{ color: color }}>
{formatNumber(level, 0)} ({numeralWrapper.formatExp(exp)} exp)
</Typography>
</TableCell>
</TableRow>
)
}
export function GangMemberStats(props: IProps): React.ReactElement {
const classes = useStyles();
@ -102,12 +80,12 @@ export function GangMemberStats(props: IProps): React.ReactElement {
>
<Table sx={{ display: 'table', mb: 1, width: '100%' }}>
<TableBody>
{generateTableRow("Hacking", props.member.hack, props.member.hack_exp, Settings.theme.hack, classes)}
{generateTableRow("Strength", props.member.str, props.member.str_exp, Settings.theme.combat, classes)}
{generateTableRow("Defense", props.member.def, props.member.def_exp, Settings.theme.combat, classes)}
{generateTableRow("Dexterity", props.member.dex, props.member.dex_exp, Settings.theme.combat, classes)}
{generateTableRow("Agility", props.member.agi, props.member.agi_exp, Settings.theme.combat, classes)}
{generateTableRow("Charisma", props.member.cha, props.member.cha_exp, Settings.theme.cha, classes)}
<StatsRow name="Hacking" color={Settings.theme.hack} data={{ level: props.member.hack, exp: props.member.hack_exp }} />
<StatsRow name="Strength" color={Settings.theme.combat} data={{ level: props.member.str, exp: props.member.str_exp }} />
<StatsRow name="Defense" color={Settings.theme.combat} data={{ level: props.member.def, exp: props.member.def_exp }} />
<StatsRow name="Dexterity" color={Settings.theme.combat} data={{ level: props.member.dex, exp: props.member.dex_exp }} />
<StatsRow name="Agility" color={Settings.theme.combat} data={{ level: props.member.agi, exp: props.member.agi_exp }} />
<StatsRow name="Charisma" color={Settings.theme.cha} data={{ level: props.member.cha, exp: props.member.cha_exp }} />
<TableRow>
<TableCell classes={{ root: classes.cellNone }}>
<br />

@ -0,0 +1,54 @@
import React from "react";
import Typography from "@mui/material/Typography";
import { Modal } from "../../ui/React/Modal";
interface IProps {
open: boolean;
onClose: () => void;
}
export const TerritoryInfoModal = ({ open, onClose }: IProps): React.ReactElement => {
return (
<Modal open={open} onClose={onClose}>
<>
<Typography variant='h4'>
Clashing
</Typography>
<Typography>
Every ~20 seconds, your gang has a chance to 'clash' with other gangs. Your chance to win a clash depends on
your gang's power, which is listed in the display below. Your gang's power slowly accumulates over time. The
accumulation rate is determined by the stats of all Gang members you have assigned to the 'Territory Warfare'
task. Gang members that are not assigned to this task do not contribute to your gang's power. Your gang also
loses a small amount of power whenever you lose a clash.
<br />
<br />
NOTE: Gang members assigned to 'Territory Warfare' can be killed during clashes. This can happen regardless of
whether you win or lose the clash. A gang member being killed results in both respect and power loss for your
gang.
</Typography>
<br />
<Typography variant='h4'>
Territory
</Typography>
<Typography>
The amount of territory you have affects all aspects of your Gang members' production, including money, respect,
and wanted level. It is very beneficial to have high territory control.
<br />
<br />
To increase your chances of winning territory, assign gang members to "Territory Warfare". This will build your
gang power. Then, enable "Engage in Territory Warfare" to start fighting over territory.
</Typography>
<br />
<Typography variant='h4'>
Territory Clash Chance
</Typography>
<Typography>
This percentage represents the chance you have of 'clashing' with another gang. If you do not wish to
gain/lose territory, then keep this percentage at 0% by not engaging in territory warfare.
</Typography>
</>
</Modal >
);
}

@ -1,133 +1,100 @@
/**
* React Component for the territory subpage.
*/
import React from "react";
import React, { useState } from "react";
import {
Container,
Button,
Paper,
Box,
Tooltip,
Switch,
FormControlLabel,
Typography
} from "@mui/material";
import { Help } from "@mui/icons-material";
import { numeralWrapper } from "../../ui/numeralFormat";
import { formatNumber } from "../../utils/StringHelperFunctions";
import { AllGangs } from "../AllGangs";
import { useGang } from "./Context";
import Typography from "@mui/material/Typography";
import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch";
import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box";
import Paper from "@mui/material/Paper";
import { AllGangs } from "../AllGangs";
import { useGang } from "./Context";
import { TerritoryInfoModal } from "./TerritoryInfoModal";
export function TerritorySubpage(): React.ReactElement {
const gang = useGang();
const gangNames = Object.keys(AllGangs).filter((g) => g != gang.facName);
const [infoOpen, setInfoOpen] = useState(false);
return (
<>
<Container disableGutters maxWidth="md" sx={{ mx: 0 }}>
<Typography>
This page shows how much territory your Gang controls. This statistic is listed as a percentage, which
represents how much of the total territory you control.
<br />
<br />
Every ~20 seconds, your gang has a chance to 'clash' with other gangs. Your chance to win a clash depends on
your gang's power, which is listed in the display below. Your gang's power slowly accumulates over time. The
accumulation rate is determined by the stats of all Gang members you have assigned to the 'Territory Warfare'
task. Gang members that are not assigned to this task do not contribute to your gang's power. Your gang also
loses a small amount of power whenever you lose a clash.
<br />
<br />
NOTE: Gang members assigned to 'Territory Warfare' can be killed during clashes. This can happen regardless of
whether you win or lose the clash. A gang member being killed results in both respect and power loss for your
gang.
<br />
<br />
The amount of territory you have affects all aspects of your Gang members' production, including money, respect,
and wanted level. It is very beneficial to have high territory control.
<br />
<br />
To increase your chances of winning territory assign gang members to "Territory Warfare", this will build your
gang power. Then enable "Engage in Territory Warfare" to start fighting over territory.
</Typography>
<Button onClick={() => setInfoOpen(true)} sx={{ my: 1 }}>
<Help sx={{ mr: 1 }} />
About Gang Territory
</Button>
<Box component={Paper} sx={{ p: 1, mb: 1 }}>
<Typography variant="h6" sx={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
{gang.facName} (Your gang)
</Typography>
<FormControlLabel
control={
<Switch
control={<Switch
checked={gang.territoryWarfareEngaged}
onChange={(event) => (gang.territoryWarfareEngaged = event.target.checked)}
/>
}
label={
<Tooltip
title={
<Typography>
/>}
label={<Tooltip
title={<Typography>
Engaging in Territory Warfare sets your clash chance to 100%. Disengaging will cause your clash chance
to gradually decrease until it reaches 0%.
</Typography>
}
>
</Typography>}>
<Typography>Engage in Territory Warfare</Typography>
</Tooltip>
}
/>
<br />
<Box display="flex">
<Tooltip
title={
<Typography>
This percentage represents the chance you have of 'clashing' with with another gang. If you do not wish to
gain/lose territory, then keep this percentage at 0% by not engaging in territory warfare.
</Typography>
}
>
<Typography>
Territory Clash Chance: {numeralWrapper.formatPercentage(gang.territoryClashChance, 3)}
</Typography>
</Tooltip>
</Box>
</Tooltip>} />
<br />
<FormControlLabel
control={
<Switch
control={<Switch
checked={gang.notifyMemberDeath}
onChange={(event) => (gang.notifyMemberDeath = event.target.checked)}
/>
}
label={
<Tooltip
title={
<Typography>
/>}
label={<Tooltip
title={<Typography>
If this is enabled, then you will receive a pop-up notifying you whenever one of your Gang Members dies
in a territory clash.
</Typography>
}
>
</Typography>}>
<Typography>Notify about Gang Member Deaths</Typography>
</Tooltip>
}
/>
<br />
<Paper>
</Tooltip>} />
<Typography>
<b>
<u>{gang.facName}</u>
</b>
<br />
Power: {formatNumber(AllGangs[gang.facName].power, 6)}
<br />
Territory: {formatTerritory(AllGangs[gang.facName].territory)}%
<br />
<br />
<b>Territory Clash Chance:</b> {numeralWrapper.formatPercentage(gang.territoryClashChance, 3)} <br />
<b>Power:</b> {formatNumber(AllGangs[gang.facName].power, 3)} <br />
<b>Territory:</b> {formatTerritory(AllGangs[gang.facName].territory)}% <br />
</Typography>
</Box>
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)' }}>
{gangNames.map((name) => (
<OtherGangTerritory key={name} name={name} />
))}
</Paper>
</>
</Box>
<TerritoryInfoModal open={infoOpen} onClose={() => setInfoOpen(false)} />
</Container >
);
}
function formatTerritory(n: number): string {
const v = n * 100;
const precision = 3;
if (v <= 0) {
return formatNumber(0, 2);
return formatNumber(0, precision);
} else if (v >= 100) {
return formatNumber(100, 2);
return formatNumber(100, precision);
} else {
return formatNumber(v, 2);
return formatNumber(v, precision);
}
}
@ -141,15 +108,15 @@ function OtherGangTerritory(props: ITerritoryProps): React.ReactElement {
const power = AllGangs[props.name].power;
const clashVictoryChance = playerPower / (power + playerPower);
return (
<Typography>
<u>{props.name}</u>
<br />
Power: {formatNumber(power, 6)}
<br />
Territory: {formatTerritory(AllGangs[props.name].territory)}%<br />
Chance to win clash with this gang: {numeralWrapper.formatPercentage(clashVictoryChance, 3)}
<br />
<br />
<Box component={Paper} sx={{ p: 1 }}>
<Typography variant="h6" sx={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
{props.name}
</Typography>
<Typography>
<b>Power:</b> {formatNumber(power, 3)} <br />
<b>Territory:</b> {formatTerritory(AllGangs[props.name].territory)}% <br />
<b>Clash Win Chance:</b> {numeralWrapper.formatPercentage(clashVictoryChance, 3)}
</Typography>
</Box>
);
}

@ -50,6 +50,7 @@ export function SleeveAugmentationsModal(props: IProps): React.ReactElement {
return (
<Modal open={props.open} onClose={props.onClose}>
<>
<Box sx={{ mx: 1 }}>
<Typography>
You can purchase Augmentations for your Duplicate Sleeves. These Augmentations have the same effect as they
would for you. You can only purchase Augmentations that you have unlocked through Factions.
@ -58,6 +59,7 @@ export function SleeveAugmentationsModal(props: IProps): React.ReactElement {
When purchasing an Augmentation for a Duplicate Sleeve, they are immediately installed. This means that the
Duplicate Sleeve will immediately lose all of its stat experience.
</Typography>
<Box component={Paper} sx={{ my: 1, p: 1 }}>
<Table size="small" padding="none">
<TableBody>
{availableAugs.map((aug) => {
@ -83,41 +85,27 @@ export function SleeveAugmentationsModal(props: IProps): React.ReactElement {
})}
</TableBody>
</Table>
</Box>
</Box>
{ownedAugNames.length > 0 && (
<>
<Typography>Owned Augmentations:</Typography>
<Typography sx={{ mx: 1 }}>Owned Augmentations:</Typography>
<Box display="grid" sx={{ gridTemplateColumns: 'repeat(5, 1fr)', m: 1 }}>
{ownedAugNames.map((augName) => {
const aug = Augmentations[augName];
let tooltip = <></>;
if (typeof aug.info === "string") {
tooltip = (
<>
<span>{aug.info}</span>
<br />
<br />
{aug.stats}
</>
);
} else {
tooltip = (
<>
{aug.info}
<br />
<br />
{aug.stats}
</>
);
}
const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info
const tooltip = (<>{info}<br /><br />{aug.stats}</>);
return (
<Tooltip key={augName} title={<Typography>{tooltip}</Typography>}>
<Paper>
<Paper sx={{ p: 1 }}>
<Typography>{augName}</Typography>
</Paper>
</Tooltip>
);
})}
</Box>
</>
)}
</>

@ -1,34 +1,29 @@
import React, { useState } from "react";
import {
Box,
Paper,
Typography,
Button,
Tooltip
} from "@mui/material";
import { CONSTANTS } from "../../../Constants";
import { Crimes } from "../../../Crime/Crimes";
import { numeralWrapper } from "../../../ui/numeralFormat";
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
import { use } from "../../../ui/Context";
import { FactionWorkType } from "../../../Faction/FactionWorkTypeEnum";
import { Sleeve } from "../Sleeve";
import { SleeveTaskType } from "../SleeveTaskTypesEnum";
import { CONSTANTS } from "../../../Constants";
import { Crimes } from "../../../Crime/Crimes";
import { numeralWrapper } from "../../../ui/numeralFormat";
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
import { SleeveAugmentationsModal } from "./SleeveAugmentationsModal";
import { TravelModal } from "./TravelModal";
import { Money } from "../../../ui/React/Money";
import { MoneyRate } from "../../../ui/React/MoneyRate";
import { use } from "../../../ui/Context";
import { ReputationRate } from "../../../ui/React/ReputationRate";
import { StatsElement } from "./StatsElement";
import { StatsElement, EarningsElement } from "./StatsElement";
import { MoreStatsModal } from "./MoreStatsModal";
import { MoreEarningsModal } from "./MoreEarningsModal";
import { TaskSelector } from "./TaskSelector";
import { FactionWorkType } from "../../../Faction/FactionWorkTypeEnum";
import { StatsTable } from "../../../ui/React/StatsTable";
import Typography from "@mui/material/Typography";
import Paper from "@mui/material/Paper";
import Grid from "@mui/material/Grid";
import Button from "@mui/material/Button";
import Tooltip from "@mui/material/Tooltip";
interface IProps {
sleeve: Sleeve;
@ -141,42 +136,22 @@ export function SleeveElem(props: IProps): React.ReactElement {
console.error(`Invalid/Unrecognized taskValue in updateSleeveTaskDescription(): ${abc[0]}`);
}
let data: any[][] = [];
if (props.sleeve.currentTask === SleeveTaskType.Crime) {
data = [
[`Money`, <Money money={parseFloat(props.sleeve.currentTaskLocation)} />, `(on success)`],
[`Hacking Exp`, numeralWrapper.formatExp(props.sleeve.gainRatesForTask.hack), `(2x on success)`],
[`Strength Exp`, numeralWrapper.formatExp(props.sleeve.gainRatesForTask.str), `(2x on success)`],
[`Defense Exp`, numeralWrapper.formatExp(props.sleeve.gainRatesForTask.def), `(2x on success)`],
[`Dexterity Exp`, numeralWrapper.formatExp(props.sleeve.gainRatesForTask.dex), `(2x on success)`],
[`Agility Exp`, numeralWrapper.formatExp(props.sleeve.gainRatesForTask.agi), `(2x on success)`],
[`Charisma Exp`, numeralWrapper.formatExp(props.sleeve.gainRatesForTask.cha), `(2x on success)`],
];
} else {
data = [
[`Money:`, <MoneyRate money={5 * props.sleeve.gainRatesForTask.money} />],
[`Hacking Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.hack)} / s`],
[`Strength Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.str)} / s`],
[`Defense Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.def)} / s`],
[`Dexterity Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.dex)} / s`],
[`Agility Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.agi)} / s`],
[`Charisma Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.cha)} / s`],
];
if (props.sleeve.currentTask === SleeveTaskType.Company || props.sleeve.currentTask === SleeveTaskType.Faction) {
const repGain: number = props.sleeve.getRepGain(player);
data.push([`Reputation:`, <ReputationRate reputation={5 * repGain} />]);
}
}
return (
<>
<Grid container component={Paper}>
<Grid item xs={3}>
<Box component={Paper} sx={{ width: 'auto' }}>
<Box sx={{ m: 1 }}>
<Box display="grid" sx={{ gridTemplateColumns: '1fr 1fr', width: '100%', gap: 1 }}>
<Box>
<StatsElement sleeve={props.sleeve} />
<Box display="grid" sx={{ gridTemplateColumns: '1fr 1fr', width: '100%' }}>
<Button onClick={() => setStatsOpen(true)}>More Stats</Button>
<Button onClick={() => setEarningsOpen(true)}>More Earnings Info</Button>
<Tooltip title={player.money < CONSTANTS.TravelCost ? <Typography>Insufficient funds</Typography> : ""}>
<span>
<Button onClick={() => setTravelOpen(true)} disabled={player.money < CONSTANTS.TravelCost}>
<Button
onClick={() => setTravelOpen(true)}
disabled={player.money < CONSTANTS.TravelCost}
sx={{ width: '100%', height: '100%' }}
>
Travel
</Button>
</span>
@ -185,14 +160,22 @@ export function SleeveElem(props: IProps): React.ReactElement {
title={props.sleeve.shock < 100 ? <Typography>Unlocked when sleeve has fully recovered</Typography> : ""}
>
<span>
<Button onClick={() => setAugmentationsOpen(true)} disabled={props.sleeve.shock < 100}>
<Button
onClick={() => setAugmentationsOpen(true)}
disabled={props.sleeve.shock < 100}
sx={{ width: '100%', height: '100%' }}
>
Manage Augmentations
</Button>
</span>
</Tooltip>
</Grid>
<Grid item xs={5}>
</Box>
</Box>
<Box>
<EarningsElement sleeve={props.sleeve} />
<Box>
<TaskSelector player={player} sleeve={props.sleeve} setABC={setABC} />
<Button onClick={setTask} sx={{ width: '100%' }}>Set Task</Button>
<Typography>{desc}</Typography>
<Typography>
{props.sleeve.currentTask === SleeveTaskType.Crime &&
@ -201,13 +184,8 @@ export function SleeveElem(props: IProps): React.ReactElement {
totalTicks: 25,
})}
</Typography>
<Button onClick={setTask}>Set Task</Button>
</Grid>
<Grid item xs={4}>
<StatsTable title="Earnings (Pre-Synchronization)" rows={data} />
<Button onClick={() => setEarningsOpen(true)}>More Earnings Info</Button>
</Grid>
</Grid>
</Box>
</Box>
<MoreStatsModal open={statsOpen} onClose={() => setStatsOpen(false)} sleeve={props.sleeve} />
<MoreEarningsModal open={earningsOpen} onClose={() => setEarningsOpen(false)} sleeve={props.sleeve} />
<TravelModal
@ -221,6 +199,8 @@ export function SleeveElem(props: IProps): React.ReactElement {
onClose={() => setAugmentationsOpen(false)}
sleeve={props.sleeve}
/>
</>
</Box>
</Box>
</Box >
);
}

@ -1,12 +1,16 @@
import React, { useState, useEffect } from "react";
import {
Box,
Typography,
Button,
Container
} from "@mui/material";
import { use } from "../../../ui/Context";
import { SleeveElem } from "./SleeveElem";
import { FAQModal } from "./FAQModal";
import { use } from "../../../ui/Context";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import Link from "@mui/material/Link";
export function SleeveRoot(): React.ReactElement {
const player = use.Player();
@ -23,6 +27,7 @@ export function SleeveRoot(): React.ReactElement {
return (
<>
<Container disableGutters maxWidth="md" sx={{ mx: 0 }}>
<Typography variant="h4">Sleeves</Typography>
<Typography>
Duplicate Sleeves are MK-V Synthoids (synthetic androids) into which your consciousness has been copied. In
@ -34,16 +39,17 @@ export function SleeveRoot(): React.ReactElement {
<br />
</Typography>
</Container>
<Button onClick={() => setFAQOpen(true)}>FAQ</Button>
<Link
target="_blank"
href="https://bitburner.readthedocs.io/en/latest/advancedgameplay/sleeves.html#duplicate-sleeves"
>
<Typography> Documentation</Typography>
</Link>
<Button href="https://bitburner.readthedocs.io/en/latest/advancedgameplay/sleeves.html#duplicate-sleeves" target="_blank">
Wiki Documentation
</Button>
<Box display="grid" sx={{ gridTemplateColumns: 'repeat(2, 1fr)', mt: 1 }}>
{player.sleeves.map((sleeve, i) => (
<SleeveElem key={i} rerender={rerender} sleeve={sleeve} />
))}
</Box>
<FAQModal open={FAQOpen} onClose={() => setFAQOpen(false)} />
</>
);

@ -1,31 +1,110 @@
import { Sleeve } from "../Sleeve";
import { numeralWrapper } from "../../../ui/numeralFormat";
import React from "react";
import { StatsTable } from "../../../ui/React/StatsTable";
import {
Typography,
Table,
TableBody,
TableCell,
TableRow,
} from "@mui/material";
import { numeralWrapper } from "../../../ui/numeralFormat";
import { Settings } from "../../../Settings/Settings";
import { StatsRow } from "../../../ui/React/StatsRow";
import { characterOverviewStyles as useStyles } from "../../../ui/React/CharacterOverview";
import { Money } from "../../../ui/React/Money";
import { MoneyRate } from "../../../ui/React/MoneyRate";
import { ReputationRate } from "../../../ui/React/ReputationRate";
import { use } from "../../../ui/Context";
import { Sleeve } from "../Sleeve";
import { SleeveTaskType } from "../SleeveTaskTypesEnum";
interface IProps {
sleeve: Sleeve;
}
export function StatsElement(props: IProps): React.ReactElement {
const rows = [
[
"HP: ",
<>
{numeralWrapper.formatHp(props.sleeve.hp)} / {numeralWrapper.formatHp(props.sleeve.max_hp)}
</>,
],
["City: ", <>{props.sleeve.city}</>],
["Hacking: ", <>{numeralWrapper.formatSkill(props.sleeve.hacking)}</>],
["Strength: ", <>{numeralWrapper.formatSkill(props.sleeve.strength)}</>],
["Defense: ", <>{numeralWrapper.formatSkill(props.sleeve.defense)}</>],
["Dexterity: ", <>{numeralWrapper.formatSkill(props.sleeve.dexterity)}</>],
["Agility: ", <>{numeralWrapper.formatSkill(props.sleeve.agility)}</>],
["Charisma: ", <>{numeralWrapper.formatSkill(props.sleeve.charisma)}</>],
["Shock: ", <>{numeralWrapper.formatSleeveShock(100 - props.sleeve.shock)}</>],
["Sync: ", <>{numeralWrapper.formatSleeveSynchro(props.sleeve.sync)}</>],
["Memory: ", <>{numeralWrapper.formatSleeveMemory(props.sleeve.memory)}</>],
];
return <StatsTable rows={rows} />;
const classes = useStyles();
return (
<Table sx={{ display: 'table', mb: 1, width: '100%' }}>
<TableBody>
<StatsRow name="City" color={Settings.theme.primary} data={{ content: props.sleeve.city }} />
<StatsRow name="HP" color={Settings.theme.hp}
data={{ content: `${numeralWrapper.formatHp(props.sleeve.hp)} / ${numeralWrapper.formatHp(props.sleeve.max_hp)}` }}
/>
<StatsRow name="Hacking" color={Settings.theme.hack} data={{ level: props.sleeve.hacking, exp: props.sleeve.hacking_exp }} />
<StatsRow name="Strength" color={Settings.theme.combat} data={{ level: props.sleeve.strength, exp: props.sleeve.strength_exp }} />
<StatsRow name="Defense" color={Settings.theme.combat} data={{ level: props.sleeve.defense, exp: props.sleeve.defense_exp }} />
<StatsRow name="Dexterity" color={Settings.theme.combat} data={{ level: props.sleeve.dexterity, exp: props.sleeve.dexterity_exp }} />
<StatsRow name="Agility" color={Settings.theme.combat} data={{ level: props.sleeve.agility, exp: props.sleeve.agility_exp }} />
<StatsRow name="Charisma" color={Settings.theme.cha} data={{ level: props.sleeve.charisma, exp: props.sleeve.charisma_exp }} />
<TableRow>
<TableCell classes={{ root: classes.cellNone }}>
<br />
</TableCell>
</TableRow>
<StatsRow name="Shock" color={Settings.theme.primary} data={{ content: numeralWrapper.formatSleeveShock(100 - props.sleeve.shock) }} />
<StatsRow name="Sync" color={Settings.theme.primary} data={{ content: numeralWrapper.formatSleeveSynchro(props.sleeve.sync) }} />
<StatsRow name="Memory" color={Settings.theme.primary} data={{ content: numeralWrapper.formatSleeveMemory(props.sleeve.memory) }} />
</TableBody>
</Table>
)
}
export function EarningsElement(props: IProps): React.ReactElement {
const classes = useStyles();
const player = use.Player();
let data: any[][] = [];
if (props.sleeve.currentTask === SleeveTaskType.Crime) {
data = [
[`Money`, <><Money money={parseFloat(props.sleeve.currentTaskLocation)} /> (on success)</>],
[`Hacking Exp`, `${numeralWrapper.formatExp(props.sleeve.gainRatesForTask.hack)} (2x on success)`],
[`Strength Exp`, `${numeralWrapper.formatExp(props.sleeve.gainRatesForTask.str)} (2x on success)`],
[`Defense Exp`, `${numeralWrapper.formatExp(props.sleeve.gainRatesForTask.def)} (2x on success)`],
[`Dexterity Exp`, `${numeralWrapper.formatExp(props.sleeve.gainRatesForTask.dex)} (2x on success)`],
[`Agility Exp`, `${numeralWrapper.formatExp(props.sleeve.gainRatesForTask.agi)} (2x on success)`],
[`Charisma Exp`, `${numeralWrapper.formatExp(props.sleeve.gainRatesForTask.cha)} (2x on success)`],
];
} else {
data = [
[`Money:`, <MoneyRate money={5 * props.sleeve.gainRatesForTask.money} />],
[`Hacking Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.hack)} / sec`],
[`Strength Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.str)} / sec`],
[`Defense Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.def)} / sec`],
[`Dexterity Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.dex)} / sec`],
[`Agility Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.agi)} / sec`],
[`Charisma Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.cha)} / sec`],
];
if (props.sleeve.currentTask === SleeveTaskType.Company || props.sleeve.currentTask === SleeveTaskType.Faction) {
const repGain: number = props.sleeve.getRepGain(player);
data.push([`Reputation:`, <ReputationRate reputation={5 * repGain} />]);
}
}
return (
<Table sx={{ display: 'table', mb: 1, width: '100%', lineHeight: 0 }}>
<TableBody>
<TableRow>
<TableCell classes={{ root: classes.cellNone }}>
<Typography variant='h6'>
Earnings
</Typography>
</TableCell>
</TableRow>
{data.map(([a, b]) => (
<TableRow key={a.toString() + b.toString()}>
<TableCell classes={{ root: classes.cellNone }}>
<Typography>{a}</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography>{b}</Typography>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)
}

@ -279,7 +279,7 @@ export function TaskSelector(props: IProps): React.ReactElement {
return (
<>
<Select onChange={onS0Change} value={s0}>
<Select onChange={onS0Change} value={s0} sx={{ width: '100%' }}>
{validActions.map((task) => (
<MenuItem key={task} value={task}>
{task}
@ -288,8 +288,7 @@ export function TaskSelector(props: IProps): React.ReactElement {
</Select>
{!(details.first.length === 1 && details.first[0] === "------") && (
<>
<br />
<Select onChange={onS1Change} value={s1}>
<Select onChange={onS1Change} value={s1} sx={{ width: '100%' }}>
{details.first.map((detail) => (
<MenuItem key={detail} value={detail}>
{detail}
@ -300,8 +299,7 @@ export function TaskSelector(props: IProps): React.ReactElement {
)}
{!(details2.length === 1 && details2[0] === "------") && (
<>
<br />
<Select onChange={onS2Change} value={s2}>
<Select onChange={onS2Change} value={s2} sx={{ width: '100%' }}>
{details2.map((detail) => (
<MenuItem key={detail} value={detail}>
{detail}

49
src/ui/React/StatsRow.tsx Normal file

@ -0,0 +1,49 @@
import React from "react";
import {
Typography,
TableCell,
TableRow,
} from "@mui/material";
import { numeralWrapper } from "../numeralFormat";
import { formatNumber } from "../../utils/StringHelperFunctions";
import { characterOverviewStyles as useStyles } from "./CharacterOverview";
interface ITableRowData {
content?: string;
level?: number;
exp?: number;
}
interface IProps {
name: string;
color: string;
classes?: any;
data: ITableRowData;
}
export const StatsRow = ({ name, color, classes = useStyles(), data }: IProps): React.ReactElement => {
let content;
if (data.content !== undefined) {
content = data.content;
} else if (data.level !== undefined && data.exp !== undefined) {
content = `${formatNumber(data.level, 0)} (${numeralWrapper.formatExp(data.exp)} exp)`;
} else if (data.level !== undefined && data.exp === undefined) {
content = `${formatNumber(data.level, 0)}`;
}
return (
<TableRow>
<TableCell classes={{ root: classes.cellNone }}>
<Typography style={{ color: color }}>{name}</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography style={{ color: color }}>
{content}
</Typography>
</TableCell>
</TableRow>
)
}