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>}>
<span>
<Button disabled={props.office.atCapacity()} onClick={autohireEmployeeButtonOnClick}> <Button disabled={props.office.atCapacity()} onClick={autohireEmployeeButtonOnClick}>
Hire Employee Hire Employee
</Button> </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>}>
<span>
<Button disabled={corp.funds < 0} onClick={() => setUpgradeOfficeSizeOpen(true)}> <Button disabled={corp.funds < 0} onClick={() => setUpgradeOfficeSizeOpen(true)}>
Upgrade size Upgrade size
</Button> </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>}
> >
<span>
<Button disabled={corp.funds < 0} onClick={() => setThrowPartyOpen(true)}> <Button disabled={corp.funds < 0} onClick={() => setThrowPartyOpen(true)}>
Throw Party Throw Party
</Button> </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>

@ -19,7 +19,7 @@ export enum FactionNames {
Chongqing = "Chongqing", Chongqing = "Chongqing",
Ishima = "Ishima", Ishima = "Ishima",
NewTokyo = "New Tokyo", 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>
<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 <FormControlLabel
control={ control={<Switch
<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
title={
<Typography>
Engaging in Territory Warfare sets your clash chance to 100%. Disengaging will cause your clash chance Engaging in Territory Warfare sets your clash chance to 100%. Disengaging will cause your clash chance
to gradually decrease until it reaches 0%. to gradually decrease until it reaches 0%.
</Typography> </Typography>}>
}
>
<Typography>Engage in Territory Warfare</Typography> <Typography>Engage in Territory Warfare</Typography>
</Tooltip> </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>
<br /> <br />
<FormControlLabel <FormControlLabel
control={ control={<Switch
<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
title={
<Typography>
If this is enabled, then you will receive a pop-up notifying you whenever one of your Gang Members dies If this is enabled, then you will receive a pop-up notifying you whenever one of your Gang Members dies
in a territory clash. in a territory clash.
</Typography> </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)}
<br />
Territory: {formatTerritory(AllGangs[props.name].territory)}%<br />
Chance to win clash with this gang: {numeralWrapper.formatPercentage(clashVictoryChance, 3)}
<br />
<br />
</Typography> </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>
); );
} }

@ -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;
@ -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,6 +50,7 @@ export function SleeveAugmentationsModal(props: IProps): React.ReactElement {
return ( return (
<Modal open={props.open} onClose={props.onClose}> <Modal open={props.open} onClose={props.onClose}>
<> <>
<Box sx={{ mx: 1 }}>
<Typography> <Typography>
You can purchase Augmentations for your Duplicate Sleeves. These Augmentations have the same effect as they 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. 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 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. Duplicate Sleeve will immediately lose all of its stat experience.
</Typography> </Typography>
<Box component={Paper} sx={{ my: 1, p: 1 }}>
<Table size="small" padding="none"> <Table size="small" padding="none">
<TableBody> <TableBody>
{availableAugs.map((aug) => { {availableAugs.map((aug) => {
@ -83,41 +85,27 @@ export function SleeveAugmentationsModal(props: IProps): React.ReactElement {
})} })}
</TableBody> </TableBody>
</Table> </Table>
</Box>
</Box>
{ownedAugNames.length > 0 && ( {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) => { {ownedAugNames.map((augName) => {
const aug = Augmentations[augName]; const aug = Augmentations[augName];
let tooltip = <></>; const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info
if (typeof aug.info === "string") { const tooltip = (<>{info}<br /><br />{aug.stats}</>);
tooltip = (
<>
<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,42 +136,22 @@ 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 }}>
<Box>
<StatsElement sleeve={props.sleeve} /> <StatsElement sleeve={props.sleeve} />
<Box display="grid" sx={{ gridTemplateColumns: '1fr 1fr', width: '100%' }}>
<Button onClick={() => setStatsOpen(true)}>More Stats</Button> <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> : ""}> <Tooltip title={player.money < CONSTANTS.TravelCost ? <Typography>Insufficient funds</Typography> : ""}>
<span> <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 Travel
</Button> </Button>
</span> </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> : ""} title={props.sleeve.shock < 100 ? <Typography>Unlocked when sleeve has fully recovered</Typography> : ""}
> >
<span> <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 Manage Augmentations
</Button> </Button>
</span> </span>
</Tooltip> </Tooltip>
</Grid> </Box>
<Grid item xs={5}> </Box>
<Box>
<EarningsElement sleeve={props.sleeve} />
<Box>
<TaskSelector player={player} sleeve={props.sleeve} setABC={setABC} /> <TaskSelector player={player} sleeve={props.sleeve} setABC={setABC} />
<Button onClick={setTask} sx={{ width: '100%' }}>Set Task</Button>
<Typography>{desc}</Typography> <Typography>{desc}</Typography>
<Typography> <Typography>
{props.sleeve.currentTask === SleeveTaskType.Crime && {props.sleeve.currentTask === SleeveTaskType.Crime &&
@ -201,13 +184,8 @@ export function SleeveElem(props: IProps): React.ReactElement {
totalTicks: 25, totalTicks: 25,
})} })}
</Typography> </Typography>
<Button onClick={setTask}>Set Task</Button> </Box>
</Grid> </Box>
<Grid item xs={4}>
<StatsTable title="Earnings (Pre-Synchronization)" rows={data} />
<Button onClick={() => setEarningsOpen(true)}>More Earnings Info</Button>
</Grid>
</Grid>
<MoreStatsModal open={statsOpen} onClose={() => setStatsOpen(false)} sleeve={props.sleeve} /> <MoreStatsModal open={statsOpen} onClose={() => setStatsOpen(false)} sleeve={props.sleeve} />
<MoreEarningsModal open={earningsOpen} onClose={() => setEarningsOpen(false)} sleeve={props.sleeve} /> <MoreEarningsModal open={earningsOpen} onClose={() => setEarningsOpen(false)} sleeve={props.sleeve} />
<TravelModal <TravelModal
@ -221,6 +199,8 @@ export function SleeveElem(props: IProps): React.ReactElement {
onClose={() => setAugmentationsOpen(false)} onClose={() => setAugmentationsOpen(false)}
sleeve={props.sleeve} 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,6 +27,7 @@ export function SleeveRoot(): React.ReactElement {
return ( return (
<> <>
<Container disableGutters maxWidth="md" sx={{ mx: 0 }}>
<Typography variant="h4">Sleeves</Typography> <Typography variant="h4">Sleeves</Typography>
<Typography> <Typography>
Duplicate Sleeves are MK-V Synthoids (synthetic androids) into which your consciousness has been copied. In 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 /> <br />
</Typography> </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>
</Link>
{player.sleeves.map((sleeve, i) => ( {player.sleeves.map((sleeve, i) => (
<SleeveElem key={i} rerender={rerender} sleeve={sleeve} /> <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>
)
}