Merge branch 'dev' into feature/add-infiltration-faction

This commit is contained in:
phyzical 2022-03-19 23:56:46 +08:00
commit 8d83c27077
17 changed files with 579 additions and 411 deletions

@ -431,17 +431,21 @@ export function IndustryOffice(props: IProps): React.ReactElement {
<Typography> <Typography>
Size: {props.office.employees.length} / {props.office.size} employees Size: {props.office.employees.length} / {props.office.size} employees
</Typography> </Typography>
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr', width: 'fit-content' }}> <Box sx={{ display: "grid", gridTemplateColumns: "1fr", width: "fit-content" }}>
<Box sx={{ gridTemplateColumns: 'repeat(3, 1fr)' }}> <Box sx={{ gridTemplateColumns: "repeat(3, 1fr)" }}>
<Tooltip title={<Typography>Automatically hires an employee and gives him/her a random name</Typography>}> <Tooltip title={<Typography>Automatically hires an employee and gives him/her a random name</Typography>}>
<Button disabled={props.office.atCapacity()} onClick={autohireEmployeeButtonOnClick}> <span>
Hire Employee <Button disabled={props.office.atCapacity()} onClick={autohireEmployeeButtonOnClick}>
</Button> Hire Employee
</Button>
</span>
</Tooltip> </Tooltip>
<Tooltip title={<Typography>Upgrade the office's size so that it can hold more employees!</Typography>}> <Tooltip title={<Typography>Upgrade the office's size so that it can hold more employees!</Typography>}>
<Button disabled={corp.funds < 0} onClick={() => setUpgradeOfficeSizeOpen(true)}> <span>
Upgrade size <Button disabled={corp.funds < 0} onClick={() => setUpgradeOfficeSizeOpen(true)}>
</Button> Upgrade size
</Button>
</span>
</Tooltip> </Tooltip>
<UpgradeOfficeSizeModal <UpgradeOfficeSizeModal
rerender={props.rerender} rerender={props.rerender}
@ -455,9 +459,11 @@ export function IndustryOffice(props: IProps): React.ReactElement {
<Tooltip <Tooltip
title={<Typography>Throw an office party to increase your employee's morale and happiness</Typography>} title={<Typography>Throw an office party to increase your employee's morale and happiness</Typography>}
> >
<Button disabled={corp.funds < 0} onClick={() => setThrowPartyOpen(true)}> <span>
Throw Party <Button disabled={corp.funds < 0} onClick={() => setThrowPartyOpen(true)}>
</Button> Throw Party
</Button>
</span>
</Tooltip> </Tooltip>
<ThrowPartyModal <ThrowPartyModal
rerender={props.rerender} rerender={props.rerender}
@ -467,7 +473,6 @@ export function IndustryOffice(props: IProps): React.ReactElement {
/> />
</> </>
)} )}
</Box> </Box>
<SwitchButton manualMode={employeeManualAssignMode} switchMode={setEmployeeManualAssignMode} /> <SwitchButton manualMode={employeeManualAssignMode} switchMode={setEmployeeManualAssignMode} />
</Box> </Box>

@ -18,8 +18,8 @@ export enum FactionNames {
Aevum = "Aevum", Aevum = "Aevum",
Chongqing = "Chongqing", Chongqing = "Chongqing",
Ishima = "Ishima", Ishima = "Ishima",
NewTokyo = "NewTokyo", NewTokyo = "New Tokyo",
Sector12 = "Sector12", Sector12 = "Sector-12",
Volhaven = "Volhaven", Volhaven = "Volhaven",
SpeakersForTheDead = "Speakers for the Dead", SpeakersForTheDead = "Speakers for the Dead",
TheDarkArmy = "The Dark Army", TheDarkArmy = "The Dark Army",

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

@ -17,36 +17,14 @@ import {
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { GangMember } from "../GangMember"; import { GangMember } from "../GangMember";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { formatNumber } from "../../utils/StringHelperFunctions";
import { MoneyRate } from "../../ui/React/MoneyRate"; import { MoneyRate } from "../../ui/React/MoneyRate";
import { StatsRow } from "../../ui/React/StatsRow";
import { characterOverviewStyles as useStyles } from "../../ui/React/CharacterOverview"; import { characterOverviewStyles as useStyles } from "../../ui/React/CharacterOverview";
interface IProps { interface IProps {
member: GangMember; 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 { export function GangMemberStats(props: IProps): React.ReactElement {
const classes = useStyles(); const classes = useStyles();
@ -102,12 +80,12 @@ export function GangMemberStats(props: IProps): React.ReactElement {
> >
<Table sx={{ display: 'table', mb: 1, width: '100%' }}> <Table sx={{ display: 'table', mb: 1, width: '100%' }}>
<TableBody> <TableBody>
{generateTableRow("Hacking", props.member.hack, props.member.hack_exp, Settings.theme.hack, classes)} <StatsRow name="Hacking" color={Settings.theme.hack} data={{ level: props.member.hack, exp: props.member.hack_exp }} />
{generateTableRow("Strength", props.member.str, props.member.str_exp, Settings.theme.combat, classes)} <StatsRow name="Strength" color={Settings.theme.combat} data={{ level: props.member.str, exp: props.member.str_exp }} />
{generateTableRow("Defense", props.member.def, props.member.def_exp, Settings.theme.combat, classes)} <StatsRow name="Defense" color={Settings.theme.combat} data={{ level: props.member.def, exp: props.member.def_exp }} />
{generateTableRow("Dexterity", props.member.dex, props.member.dex_exp, Settings.theme.combat, classes)} <StatsRow name="Dexterity" color={Settings.theme.combat} data={{ level: props.member.dex, exp: props.member.dex_exp }} />
{generateTableRow("Agility", props.member.agi, props.member.agi_exp, Settings.theme.combat, classes)} <StatsRow name="Agility" color={Settings.theme.combat} data={{ level: props.member.agi, exp: props.member.agi_exp }} />
{generateTableRow("Charisma", props.member.cha, props.member.cha_exp, Settings.theme.cha, classes)} <StatsRow name="Charisma" color={Settings.theme.cha} data={{ level: props.member.cha, exp: props.member.cha_exp }} />
<TableRow> <TableRow>
<TableCell classes={{ root: classes.cellNone }}> <TableCell classes={{ root: classes.cellNone }}>
<br /> <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. * 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 { numeralWrapper } from "../../ui/numeralFormat";
import { formatNumber } from "../../utils/StringHelperFunctions"; import { formatNumber } from "../../utils/StringHelperFunctions";
import { AllGangs } from "../AllGangs";
import { useGang } from "./Context";
import Typography from "@mui/material/Typography"; import { AllGangs } from "../AllGangs";
import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch"; import { useGang } from "./Context";
import Tooltip from "@mui/material/Tooltip"; import { TerritoryInfoModal } from "./TerritoryInfoModal";
import Box from "@mui/material/Box";
import Paper from "@mui/material/Paper";
export function TerritorySubpage(): React.ReactElement { export function TerritorySubpage(): React.ReactElement {
const gang = useGang(); const gang = useGang();
const gangNames = Object.keys(AllGangs).filter((g) => g != gang.facName); const gangNames = Object.keys(AllGangs).filter((g) => g != gang.facName);
const [infoOpen, setInfoOpen] = useState(false);
return ( return (
<> <Container disableGutters maxWidth="md" sx={{ mx: 0 }}>
<Typography> <Typography>
This page shows how much territory your Gang controls. This statistic is listed as a percentage, which 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. 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> </Typography>
<FormControlLabel
control={ <Button onClick={() => setInfoOpen(true)} sx={{ my: 1 }}>
<Switch <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
checked={gang.territoryWarfareEngaged} checked={gang.territoryWarfareEngaged}
onChange={(event) => (gang.territoryWarfareEngaged = event.target.checked)} onChange={(event) => (gang.territoryWarfareEngaged = event.target.checked)}
/> />}
} label={<Tooltip
label={ title={<Typography>
<Tooltip Engaging in Territory Warfare sets your clash chance to 100%. Disengaging will cause your clash chance
title={ to gradually decrease until it reaches 0%.
<Typography> </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>Engage in Territory Warfare</Typography> <Typography>Engage in Territory Warfare</Typography>
</Tooltip> </Tooltip>} />
} <br />
/> <FormControlLabel
<br /> control={<Switch
<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>
<br />
<FormControlLabel
control={
<Switch
checked={gang.notifyMemberDeath} checked={gang.notifyMemberDeath}
onChange={(event) => (gang.notifyMemberDeath = event.target.checked)} onChange={(event) => (gang.notifyMemberDeath = event.target.checked)}
/> />}
} label={<Tooltip
label={ title={<Typography>
<Tooltip If this is enabled, then you will receive a pop-up notifying you whenever one of your Gang Members dies
title={ in a territory clash.
<Typography> </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>Notify about Gang Member Deaths</Typography> <Typography>Notify about Gang Member Deaths</Typography>
</Tooltip> </Tooltip>} />
}
/>
<br />
<Paper>
<Typography> <Typography>
<b> <b>Territory Clash Chance:</b> {numeralWrapper.formatPercentage(gang.territoryClashChance, 3)} <br />
<u>{gang.facName}</u> <b>Power:</b> {formatNumber(AllGangs[gang.facName].power, 3)} <br />
</b> <b>Territory:</b> {formatTerritory(AllGangs[gang.facName].territory)}% <br />
<br />
Power: {formatNumber(AllGangs[gang.facName].power, 6)}
<br />
Territory: {formatTerritory(AllGangs[gang.facName].territory)}%
<br />
<br />
</Typography> </Typography>
</Box>
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)' }}>
{gangNames.map((name) => ( {gangNames.map((name) => (
<OtherGangTerritory key={name} name={name} /> <OtherGangTerritory key={name} name={name} />
))} ))}
</Paper> </Box>
</> <TerritoryInfoModal open={infoOpen} onClose={() => setInfoOpen(false)} />
</Container >
); );
} }
function formatTerritory(n: number): string { function formatTerritory(n: number): string {
const v = n * 100; const v = n * 100;
const precision = 3;
if (v <= 0) { if (v <= 0) {
return formatNumber(0, 2); return formatNumber(0, precision);
} else if (v >= 100) { } else if (v >= 100) {
return formatNumber(100, 2); return formatNumber(100, precision);
} else { } 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 power = AllGangs[props.name].power;
const clashVictoryChance = playerPower / (power + playerPower); const clashVictoryChance = playerPower / (power + playerPower);
return ( return (
<Typography> <Box component={Paper} sx={{ p: 1 }}>
<u>{props.name}</u> <Typography variant="h6" sx={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
<br /> {props.name}
Power: {formatNumber(power, 6)} </Typography>
<br /> <Typography>
Territory: {formatTerritory(AllGangs[props.name].territory)}%<br /> <b>Power:</b> {formatNumber(power, 3)} <br />
Chance to win clash with this gang: {numeralWrapper.formatPercentage(clashVictoryChance, 3)} <b>Territory:</b> {formatTerritory(AllGangs[props.name].territory)}% <br />
<br /> <b>Clash Win Chance:</b> {numeralWrapper.formatPercentage(clashVictoryChance, 3)}
<br /> </Typography>
</Typography> </Box>
); );
} }

@ -181,6 +181,8 @@ export const RamCosts: IMap<any> = {
getForecast: RamCostConstants.ScriptBuySellStockRamCost, getForecast: RamCostConstants.ScriptBuySellStockRamCost,
purchase4SMarketData: RamCostConstants.ScriptBuySellStockRamCost, purchase4SMarketData: RamCostConstants.ScriptBuySellStockRamCost,
purchase4SMarketDataTixApi: RamCostConstants.ScriptBuySellStockRamCost, purchase4SMarketDataTixApi: RamCostConstants.ScriptBuySellStockRamCost,
purchaseWseAccount: RamCostConstants.ScriptBuySellStockRamCost,
purchaseTixApi: RamCostConstants.ScriptBuySellStockRamCost,
}, },
getPurchasedServerLimit: RamCostConstants.ScriptGetPurchasedServerLimit, getPurchasedServerLimit: RamCostConstants.ScriptGetPurchasedServerLimit,
getPurchasedServerMaxRam: RamCostConstants.ScriptGetPurchasedServerMaxRam, getPurchasedServerMaxRam: RamCostConstants.ScriptGetPurchasedServerMaxRam,

@ -8,7 +8,7 @@ import { getBuyTransactionCost, getSellTransactionGain } from "../StockMarket/St
import { OrderTypes } from "../StockMarket/data/OrderTypes"; import { OrderTypes } from "../StockMarket/data/OrderTypes";
import { PositionTypes } from "../StockMarket/data/PositionTypes"; import { PositionTypes } from "../StockMarket/data/PositionTypes";
import { StockSymbols } from "../StockMarket/data/StockSymbols"; import { StockSymbols } from "../StockMarket/data/StockSymbols";
import { getStockMarket4SDataCost, getStockMarket4STixApiCost } from "../StockMarket/StockMarketCosts"; import { getStockMarket4SDataCost, getStockMarket4STixApiCost, getStockMarketWseCost, getStockMarketTixApiCost } from "../StockMarket/StockMarketCosts";
import { Stock } from "../StockMarket/Stock"; import { Stock } from "../StockMarket/Stock";
import { TIX } from "../ScriptEditor/NetscriptDefinitions"; import { TIX } from "../ScriptEditor/NetscriptDefinitions";
@ -388,5 +388,47 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
workerScript.log("stock.purchase4SMarketDataTixApi", () => "Purchased 4S Market Data TIX API"); workerScript.log("stock.purchase4SMarketDataTixApi", () => "Purchased 4S Market Data TIX API");
return true; return true;
}, },
purchaseWseAccount: function (): boolean {
helper.updateDynamicRam("PurchaseWseAccount", getRamCost(player, "stock", "purchaseWseAccount"));
if (player.hasWseAccount) {
workerScript.log("stock.purchaseWseAccount", () => "Already purchased WSE Account");
return true;
}
if (player.money < getStockMarketWseCost()) {
workerScript.log(
"stock.purchaseWseAccount",
() => "Not enough money to purchase WSE Account Access",
);
return false;
}
player.hasWseAccount = true;
player.loseMoney(getStockMarketWseCost(), "stock");
workerScript.log("stock.purchaseWseAccount", () => "Purchased WSE Account Access");
return true;
},
purchaseTixApi: function (): boolean {
helper.updateDynamicRam("purchaseTixApi", getRamCost(player, "stock", "purchaseTixApi"));
if (player.hasTixApiAccess) {
workerScript.log("stock.purchaseTixApi", () => "Already purchased TIX API");
return true;
}
if (player.money < getStockMarketTixApiCost()) {
workerScript.log(
"stock.purchaseTixApi",
() => "Not enough money to purchase TIX API Access",
);
return false;
}
player.hasTixApiAccess = true;
player.loseMoney(getStockMarketTixApiCost(), "stock");
workerScript.log("stock.purchaseTixApi", () => "Purchased TIX API");
return true;
},
}; };
} }

@ -932,7 +932,9 @@ export function startFactionSecurityWork(this: IPlayer, faction: Faction): void
export function workForFaction(this: IPlayer, numCycles: number): boolean { export function workForFaction(this: IPlayer, numCycles: number): boolean {
const faction = Factions[this.currentWorkFactionName]; const faction = Factions[this.currentWorkFactionName];
if (!faction) { return false; } if (!faction) {
return false;
}
//Constantly update the rep gain rate //Constantly update the rep gain rate
switch (this.factionWorkType) { switch (this.factionWorkType) {
@ -1252,12 +1254,7 @@ export function getWorkRepGain(this: IPlayer): number {
// } // }
/* Creating a Program */ /* Creating a Program */
export function startCreateProgramWork( export function startCreateProgramWork(this: IPlayer, programName: string, time: number, reqLevel: number): void {
this: IPlayer,
programName: string,
time: number,
reqLevel: number,
): void {
this.resetWorkStatus(); this.resetWorkStatus();
this.isWorking = true; this.isWorking = true;
this.workType = CONSTANTS.WorkTypeCreateProgram; this.workType = CONSTANTS.WorkTypeCreateProgram;
@ -1510,20 +1507,20 @@ export function finishCrime(this: IPlayer, cancelled: boolean): string {
if (ws.disableLogs.ALL == null && ws.disableLogs.commitCrime == null) { if (ws.disableLogs.ALL == null && ws.disableLogs.commitCrime == null) {
ws.scriptRef.log( ws.scriptRef.log(
"SUCCESS: Crime successful! Gained " + "SUCCESS: Crime successful! Gained " +
numeralWrapper.formatMoney(this.workMoneyGained) + numeralWrapper.formatMoney(this.workMoneyGained) +
", " + ", " +
numeralWrapper.formatExp(this.workHackExpGained) + numeralWrapper.formatExp(this.workHackExpGained) +
" hack exp, " + " hack exp, " +
numeralWrapper.formatExp(this.workStrExpGained) + numeralWrapper.formatExp(this.workStrExpGained) +
" str exp, " + " str exp, " +
numeralWrapper.formatExp(this.workDefExpGained) + numeralWrapper.formatExp(this.workDefExpGained) +
" def exp, " + " def exp, " +
numeralWrapper.formatExp(this.workDexExpGained) + numeralWrapper.formatExp(this.workDexExpGained) +
" dex exp, " + " dex exp, " +
numeralWrapper.formatExp(this.workAgiExpGained) + numeralWrapper.formatExp(this.workAgiExpGained) +
" agi exp, " + " agi exp, " +
numeralWrapper.formatExp(this.workChaExpGained) + numeralWrapper.formatExp(this.workChaExpGained) +
" cha exp.", " cha exp.",
); );
} }
} else { } else {
@ -1562,18 +1559,18 @@ export function finishCrime(this: IPlayer, cancelled: boolean): string {
if (ws.disableLogs.ALL == null && ws.disableLogs.commitCrime == null) { if (ws.disableLogs.ALL == null && ws.disableLogs.commitCrime == null) {
ws.scriptRef.log( ws.scriptRef.log(
"FAIL: Crime failed! Gained " + "FAIL: Crime failed! Gained " +
numeralWrapper.formatExp(this.workHackExpGained) + numeralWrapper.formatExp(this.workHackExpGained) +
" hack exp, " + " hack exp, " +
numeralWrapper.formatExp(this.workStrExpGained) + numeralWrapper.formatExp(this.workStrExpGained) +
" str exp, " + " str exp, " +
numeralWrapper.formatExp(this.workDefExpGained) + numeralWrapper.formatExp(this.workDefExpGained) +
" def exp, " + " def exp, " +
numeralWrapper.formatExp(this.workDexExpGained) + numeralWrapper.formatExp(this.workDexExpGained) +
" dex exp, " + " dex exp, " +
numeralWrapper.formatExp(this.workAgiExpGained) + numeralWrapper.formatExp(this.workAgiExpGained) +
" agi exp, " + " agi exp, " +
numeralWrapper.formatExp(this.workChaExpGained) + numeralWrapper.formatExp(this.workChaExpGained) +
" cha exp.", " cha exp.",
); );
} }
} else { } else {
@ -2000,13 +1997,15 @@ export function isQualified(this: IPlayer, company: Company, position: CompanyPo
const reqAgility = position.requiredDexterity > 0 ? position.requiredDexterity + offset : 0; const reqAgility = position.requiredDexterity > 0 ? position.requiredDexterity + offset : 0;
const reqCharisma = position.requiredCharisma > 0 ? position.requiredCharisma + offset : 0; const reqCharisma = position.requiredCharisma > 0 ? position.requiredCharisma + offset : 0;
return this.hacking >= reqHacking && return (
this.hacking >= reqHacking &&
this.strength >= reqStrength && this.strength >= reqStrength &&
this.defense >= reqDefense && this.defense >= reqDefense &&
this.dexterity >= reqDexterity && this.dexterity >= reqDexterity &&
this.agility >= reqAgility && this.agility >= reqAgility &&
this.charisma >= reqCharisma && this.charisma >= reqCharisma &&
company.playerReputation >= position.requiredReputation; company.playerReputation >= position.requiredReputation
);
} }
/********** Reapplying Augmentations and Source File ***********/ /********** Reapplying Augmentations and Source File ***********/
@ -2237,7 +2236,8 @@ export function checkForFactionInvitations(this: IPlayer): Faction[] {
//Fulcrum Secret Technologies - If u've unlocked fulcrum secret technolgoies server and have a high rep with the company //Fulcrum Secret Technologies - If u've unlocked fulcrum secret technolgoies server and have a high rep with the company
const fulcrumsecrettechonologiesFac = Factions[FactionNames.FulcrumSecretTechnologies]; const fulcrumsecrettechonologiesFac = Factions[FactionNames.FulcrumSecretTechnologies];
const fulcrumSecretServer = GetServer(SpecialServers.FulcrumSecretTechnologies); const fulcrumSecretServer = GetServer(SpecialServers.FulcrumSecretTechnologies);
if (!(fulcrumSecretServer instanceof Server)) throw new Error(`${FactionNames.FulcrumSecretTechnologies} should be normal server`); if (!(fulcrumSecretServer instanceof Server))
throw new Error(`${FactionNames.FulcrumSecretTechnologies} should be normal server`);
if (fulcrumSecretServer == null) { if (fulcrumSecretServer == null) {
console.error(`Could not find ${FactionNames.FulcrumSecretTechnologies} Server`); console.error(`Could not find ${FactionNames.FulcrumSecretTechnologies} Server`);
} else if ( } else if (

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

@ -1,34 +1,29 @@
import React, { useState } from "react"; 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 { Sleeve } from "../Sleeve";
import { SleeveTaskType } from "../SleeveTaskTypesEnum"; 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 { SleeveAugmentationsModal } from "./SleeveAugmentationsModal";
import { TravelModal } from "./TravelModal"; import { TravelModal } from "./TravelModal";
import { Money } from "../../../ui/React/Money"; import { StatsElement, EarningsElement } from "./StatsElement";
import { MoneyRate } from "../../../ui/React/MoneyRate";
import { use } from "../../../ui/Context";
import { ReputationRate } from "../../../ui/React/ReputationRate";
import { StatsElement } from "./StatsElement";
import { MoreStatsModal } from "./MoreStatsModal"; import { MoreStatsModal } from "./MoreStatsModal";
import { MoreEarningsModal } from "./MoreEarningsModal"; import { MoreEarningsModal } from "./MoreEarningsModal";
import { TaskSelector } from "./TaskSelector"; 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 { interface IProps {
sleeve: Sleeve; sleeve: Sleeve;
@ -141,86 +136,71 @@ export function SleeveElem(props: IProps): React.ReactElement {
console.error(`Invalid/Unrecognized taskValue in updateSleeveTaskDescription(): ${abc[0]}`); 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 ( return (
<> <Box component={Paper} sx={{ width: 'auto' }}>
<Grid container component={Paper}> <Box sx={{ m: 1 }}>
<Grid item xs={3}> <Box display="grid" sx={{ gridTemplateColumns: '1fr 1fr', width: '100%', gap: 1 }}>
<StatsElement sleeve={props.sleeve} /> <Box>
<Button onClick={() => setStatsOpen(true)}>More Stats</Button> <StatsElement sleeve={props.sleeve} />
<Tooltip title={player.money < CONSTANTS.TravelCost ? <Typography>Insufficient funds</Typography> : ""}> <Box display="grid" sx={{ gridTemplateColumns: '1fr 1fr', width: '100%' }}>
<span> <Button onClick={() => setStatsOpen(true)}>More Stats</Button>
<Button onClick={() => setTravelOpen(true)} disabled={player.money < CONSTANTS.TravelCost}> <Button onClick={() => setEarningsOpen(true)}>More Earnings Info</Button>
Travel <Tooltip title={player.money < CONSTANTS.TravelCost ? <Typography>Insufficient funds</Typography> : ""}>
</Button> <span>
</span> <Button
</Tooltip> onClick={() => setTravelOpen(true)}
<Tooltip disabled={player.money < CONSTANTS.TravelCost}
title={props.sleeve.shock < 100 ? <Typography>Unlocked when sleeve has fully recovered</Typography> : ""} sx={{ width: '100%', height: '100%' }}
> >
<span> Travel
<Button onClick={() => setAugmentationsOpen(true)} disabled={props.sleeve.shock < 100}> </Button>
Manage Augmentations </span>
</Button> </Tooltip>
</span> <Tooltip
</Tooltip> title={props.sleeve.shock < 100 ? <Typography>Unlocked when sleeve has fully recovered</Typography> : ""}
</Grid> >
<Grid item xs={5}> <span>
<TaskSelector player={player} sleeve={props.sleeve} setABC={setABC} /> <Button
<Typography>{desc}</Typography> onClick={() => setAugmentationsOpen(true)}
<Typography> disabled={props.sleeve.shock < 100}
{props.sleeve.currentTask === SleeveTaskType.Crime && sx={{ width: '100%', height: '100%' }}
createProgressBarText({ >
progress: props.sleeve.currentTaskTime / props.sleeve.currentTaskMaxTime, Manage Augmentations
totalTicks: 25, </Button>
})} </span>
</Typography> </Tooltip>
<Button onClick={setTask}>Set Task</Button> </Box>
</Grid> </Box>
<Grid item xs={4}> <Box>
<StatsTable title="Earnings (Pre-Synchronization)" rows={data} /> <EarningsElement sleeve={props.sleeve} />
<Button onClick={() => setEarningsOpen(true)}>More Earnings Info</Button> <Box>
</Grid> <TaskSelector player={player} sleeve={props.sleeve} setABC={setABC} />
</Grid> <Button onClick={setTask} sx={{ width: '100%' }}>Set Task</Button>
<MoreStatsModal open={statsOpen} onClose={() => setStatsOpen(false)} sleeve={props.sleeve} /> <Typography>{desc}</Typography>
<MoreEarningsModal open={earningsOpen} onClose={() => setEarningsOpen(false)} sleeve={props.sleeve} /> <Typography>
<TravelModal {props.sleeve.currentTask === SleeveTaskType.Crime &&
open={travelOpen} createProgressBarText({
onClose={() => setTravelOpen(false)} progress: props.sleeve.currentTaskTime / props.sleeve.currentTaskMaxTime,
sleeve={props.sleeve} totalTicks: 25,
rerender={props.rerender} })}
/> </Typography>
<SleeveAugmentationsModal </Box>
open={augmentationsOpen} </Box>
onClose={() => setAugmentationsOpen(false)} <MoreStatsModal open={statsOpen} onClose={() => setStatsOpen(false)} sleeve={props.sleeve} />
sleeve={props.sleeve} <MoreEarningsModal open={earningsOpen} onClose={() => setEarningsOpen(false)} sleeve={props.sleeve} />
/> <TravelModal
</> open={travelOpen}
onClose={() => setTravelOpen(false)}
sleeve={props.sleeve}
rerender={props.rerender}
/>
<SleeveAugmentationsModal
open={augmentationsOpen}
onClose={() => setAugmentationsOpen(false)}
sleeve={props.sleeve}
/>
</Box>
</Box>
</Box >
); );
} }

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

@ -1,31 +1,110 @@
import { Sleeve } from "../Sleeve";
import { numeralWrapper } from "../../../ui/numeralFormat";
import React from "react"; 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 { interface IProps {
sleeve: Sleeve; sleeve: Sleeve;
} }
export function StatsElement(props: IProps): React.ReactElement { export function StatsElement(props: IProps): React.ReactElement {
const rows = [ const classes = useStyles();
[
"HP: ", return (
<> <Table sx={{ display: 'table', mb: 1, width: '100%' }}>
{numeralWrapper.formatHp(props.sleeve.hp)} / {numeralWrapper.formatHp(props.sleeve.max_hp)} <TableBody>
</>, <StatsRow name="City" color={Settings.theme.primary} data={{ content: props.sleeve.city }} />
], <StatsRow name="HP" color={Settings.theme.hp}
["City: ", <>{props.sleeve.city}</>], data={{ content: `${numeralWrapper.formatHp(props.sleeve.hp)} / ${numeralWrapper.formatHp(props.sleeve.max_hp)}` }}
["Hacking: ", <>{numeralWrapper.formatSkill(props.sleeve.hacking)}</>], />
["Strength: ", <>{numeralWrapper.formatSkill(props.sleeve.strength)}</>], <StatsRow name="Hacking" color={Settings.theme.hack} data={{ level: props.sleeve.hacking, exp: props.sleeve.hacking_exp }} />
["Defense: ", <>{numeralWrapper.formatSkill(props.sleeve.defense)}</>], <StatsRow name="Strength" color={Settings.theme.combat} data={{ level: props.sleeve.strength, exp: props.sleeve.strength_exp }} />
["Dexterity: ", <>{numeralWrapper.formatSkill(props.sleeve.dexterity)}</>], <StatsRow name="Defense" color={Settings.theme.combat} data={{ level: props.sleeve.defense, exp: props.sleeve.defense_exp }} />
["Agility: ", <>{numeralWrapper.formatSkill(props.sleeve.agility)}</>], <StatsRow name="Dexterity" color={Settings.theme.combat} data={{ level: props.sleeve.dexterity, exp: props.sleeve.dexterity_exp }} />
["Charisma: ", <>{numeralWrapper.formatSkill(props.sleeve.charisma)}</>], <StatsRow name="Agility" color={Settings.theme.combat} data={{ level: props.sleeve.agility, exp: props.sleeve.agility_exp }} />
["Shock: ", <>{numeralWrapper.formatSleeveShock(100 - props.sleeve.shock)}</>], <StatsRow name="Charisma" color={Settings.theme.cha} data={{ level: props.sleeve.charisma, exp: props.sleeve.charisma_exp }} />
["Sync: ", <>{numeralWrapper.formatSleeveSynchro(props.sleeve.sync)}</>], <TableRow>
["Memory: ", <>{numeralWrapper.formatSleeveMemory(props.sleeve.memory)}</>], <TableCell classes={{ root: classes.cellNone }}>
]; <br />
return <StatsTable rows={rows} />; </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 ( return (
<> <>
<Select onChange={onS0Change} value={s0}> <Select onChange={onS0Change} value={s0} sx={{ width: '100%' }}>
{validActions.map((task) => ( {validActions.map((task) => (
<MenuItem key={task} value={task}> <MenuItem key={task} value={task}>
{task} {task}
@ -288,8 +288,7 @@ export function TaskSelector(props: IProps): React.ReactElement {
</Select> </Select>
{!(details.first.length === 1 && details.first[0] === "------") && ( {!(details.first.length === 1 && details.first[0] === "------") && (
<> <>
<br /> <Select onChange={onS1Change} value={s1} sx={{ width: '100%' }}>
<Select onChange={onS1Change} value={s1}>
{details.first.map((detail) => ( {details.first.map((detail) => (
<MenuItem key={detail} value={detail}> <MenuItem key={detail} value={detail}>
{detail} {detail}
@ -300,8 +299,7 @@ export function TaskSelector(props: IProps): React.ReactElement {
)} )}
{!(details2.length === 1 && details2[0] === "------") && ( {!(details2.length === 1 && details2[0] === "------") && (
<> <>
<br /> <Select onChange={onS2Change} value={s2} sx={{ width: '100%' }}>
<Select onChange={onS2Change} value={s2}>
{details2.map((detail) => ( {details2.map((detail) => (
<MenuItem key={detail} value={detail}> <MenuItem key={detail} value={detail}>
{detail} {detail}

@ -1506,6 +1506,20 @@ export interface TIX {
* @returns True if you successfully purchased it or if you already have access, false otherwise. * @returns True if you successfully purchased it or if you already have access, false otherwise.
*/ */
purchase4SMarketDataTixApi(): boolean; purchase4SMarketDataTixApi(): boolean;
/**
* Purchase WSE Account.
* @remarks RAM cost: 2.5 GB
* @returns True if you successfully purchased it or if you already have access, false otherwise.
*/
purchaseWseAccount(): boolean;
/**
* Purchase TIX API Access
* @remarks RAM cost: 2.5 GB
* @returns True if you successfully purchased it or if you already have access, false otherwise.
*/
purchaseTixApi(): boolean;
} }
/** /**

@ -8,3 +8,11 @@ export function getStockMarket4SDataCost(): number {
export function getStockMarket4STixApiCost(): number { export function getStockMarket4STixApiCost(): number {
return CONSTANTS.MarketDataTixApi4SCost * BitNodeMultipliers.FourSigmaMarketDataApiCost; return CONSTANTS.MarketDataTixApi4SCost * BitNodeMultipliers.FourSigmaMarketDataApiCost;
} }
export function getStockMarketWseCost(): number {
return CONSTANTS.WSEAccountCost;
}
export function getStockMarketTixApiCost(): number {
return CONSTANTS.TIXAPICost;
}

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