sleeves to mui

This commit is contained in:
Olivier Gagnon 2021-09-26 20:55:38 -04:00
parent 3289f76cd0
commit 14e6dd0158
23 changed files with 658 additions and 630 deletions

@ -14,7 +14,6 @@ import { CONSTANTS } from "../../Constants";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { Faction } from "../../Faction/Faction";
import { createSleevePurchasesFromCovenantPopup } from "../../PersonObjects/Sleeve/SleeveCovenantPurchases";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { createPopup } from "../../ui/React/createPopup";
@ -23,6 +22,7 @@ import { CreateGangPopup } from "./CreateGangPopup";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import { CovenantPurchasesRoot } from "../../PersonObjects/Sleeve/ui/CovenantPurchasesRoot";
type IProps = {
faction: Faction;
@ -66,6 +66,7 @@ const GangNames = [
];
export function FactionRoot(props: IProps): React.ReactElement {
const [sleevesOpen, setSleevesOpen] = useState(false);
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
@ -101,10 +102,6 @@ export function FactionRoot(props: IProps): React.ReactElement {
setPurchasingAugs(true);
}
function sleevePurchases(): void {
createSleevePurchasesFromCovenantPopup(player);
}
function startFieldWork(faction: Faction): void {
player.startFactionFieldWork(router, faction);
}
@ -185,11 +182,14 @@ export function FactionRoot(props: IProps): React.ReactElement {
)}
<Option buttonText={"Purchase Augmentations"} infoText={augmentationsInfo} onClick={routeToPurchaseAugs} />
{canPurchaseSleeves && (
<Option
buttonText={"Purchase & Upgrade Duplicate Sleeves"}
infoText={sleevePurchasesInfo}
onClick={sleevePurchases}
/>
<>
<Option
buttonText={"Purchase & Upgrade Duplicate Sleeves"}
infoText={sleevePurchasesInfo}
onClick={() => setSleevesOpen(true)}
/>
<CovenantPurchasesRoot open={sleevesOpen} onClose={() => setSleevesOpen(false)} />
</>
)}
</>
);

@ -537,6 +537,7 @@ export class Sleeve extends Person {
break;
case SleeveTaskType.Synchro:
this.sync = Math.min(100, this.sync + p.getIntelligenceBonus(0.5) * 0.0002 * cyclesUsed);
if (this.sync >= 100) this.resetTaskStatus();
break;
default:
break;

@ -1,19 +0,0 @@
/**
* Implements the purchasing of extra Duplicate Sleeves from The Covenant,
* as well as the purchasing of upgrades (memory)
*/
import { IPlayer } from "../IPlayer";
import { CovenantPurchasesRoot } from "./ui/CovenantPurchasesRoot";
import { createPopup, removePopup } from "../../ui/React/createPopup";
export const MaxSleevesFromCovenant = 5;
export const BaseCostPerSleeve = 10e12;
export const PopupId = "covenant-sleeve-purchases-popup";
export function createSleevePurchasesFromCovenantPopup(p: IPlayer): void {
createPopup(PopupId, CovenantPurchasesRoot, {
p: p,
closeFn: () => removePopup(PopupId),
});
}

@ -0,0 +1,7 @@
/**
* Implements the purchasing of extra Duplicate Sleeves from The Covenant,
* as well as the purchasing of upgrades (memory)
*/
export const MaxSleevesFromCovenant = 5;
export const BaseCostPerSleeve = 10e12;

@ -4,31 +4,33 @@
*/
import React, { useState } from "react";
import { CovenantSleeveUpgrades } from "./CovenantSleeveUpgrades";
import { CovenantSleeveMemoryUpgrade } from "./CovenantSleeveMemoryUpgrade";
import { Sleeve } from "../Sleeve";
import { BaseCostPerSleeve, MaxSleevesFromCovenant, PopupId } from "../SleeveCovenantPurchases";
import { IPlayer } from "../../IPlayer";
import { BaseCostPerSleeve, MaxSleevesFromCovenant } from "../SleeveCovenantPurchases";
import { PopupCloseButton } from "../../../ui/React/PopupCloseButton";
import { StdButton } from "../../../ui/React/StdButton";
import { Money } from "../../../ui/React/Money";
import { Modal } from "../../../ui/React/Modal";
import { use } from "../../../ui/Context";
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
interface IProps {
closeFn: () => void;
p: IPlayer;
open: boolean;
onClose: () => void;
}
export function CovenantPurchasesRoot(props: IProps): React.ReactElement {
const player = use.Player();
const [update, setUpdate] = useState(0);
/**
* Get the cost to purchase a new Duplicate Sleeve
*/
function purchaseCost(): number {
return (props.p.sleevesFromCovenant + 1) * BaseCostPerSleeve;
return (player.sleevesFromCovenant + 1) * BaseCostPerSleeve;
}
/**
@ -40,20 +42,20 @@ export function CovenantPurchasesRoot(props: IProps): React.ReactElement {
// Purchasing a new Duplicate Sleeve
let purchaseDisabled = false;
if (!props.p.canAfford(purchaseCost())) {
if (!player.canAfford(purchaseCost())) {
purchaseDisabled = true;
}
if (props.p.sleevesFromCovenant >= MaxSleevesFromCovenant) {
if (player.sleevesFromCovenant >= MaxSleevesFromCovenant) {
purchaseDisabled = true;
}
function purchaseOnClick(): void {
if (props.p.sleevesFromCovenant >= MaxSleevesFromCovenant) return;
if (player.sleevesFromCovenant >= MaxSleevesFromCovenant) return;
if (props.p.canAfford(purchaseCost())) {
props.p.loseMoney(purchaseCost());
props.p.sleevesFromCovenant += 1;
props.p.sleeves.push(new Sleeve(props.p));
if (player.canAfford(purchaseCost())) {
player.loseMoney(purchaseCost());
player.sleevesFromCovenant += 1;
player.sleeves.push(new Sleeve(player));
rerender();
} else {
dialogBoxCreate(`You cannot afford to purchase a Duplicate Sleeve`, false);
@ -62,35 +64,31 @@ export function CovenantPurchasesRoot(props: IProps): React.ReactElement {
// Purchasing Upgrades for Sleeves
const upgradePanels = [];
for (let i = 0; i < props.p.sleeves.length; ++i) {
const sleeve = props.p.sleeves[i];
upgradePanels.push(<CovenantSleeveUpgrades {...props} sleeve={sleeve} index={i} rerender={rerender} key={i} />);
for (let i = 0; i < player.sleeves.length; ++i) {
const sleeve = player.sleeves[i];
upgradePanels.push(<CovenantSleeveMemoryUpgrade index={i} p={player} rerender={rerender} sleeve={sleeve} />);
}
return (
<div>
<PopupCloseButton popup={PopupId} text={"Close"} />
{props.p.sleevesFromCovenant < MaxSleevesFromCovenant && (
<>
<p>
Would you like to purchase an additional Duplicate Sleeve from The Covenant for{" "}
<Money money={purchaseCost()} player={props.p} />?
</p>
<br />
<p>
These Duplicate Sleeves are permanent (they persist through BitNodes). You can purchase a total of{" "}
{MaxSleevesFromCovenant} from The Covenant.
</p>
<StdButton disabled={purchaseDisabled} onClick={purchaseOnClick} text={"Purchase"} />
</>
)}
<br />
<br />
<p>
Here, you can also purchase upgrades for your Duplicate Sleeves. These upgrades are also permanent, meaning they
persist across BitNodes.
</p>
{upgradePanels}
</div>
<Modal open={props.open} onClose={props.onClose}>
<>
{player.sleevesFromCovenant < MaxSleevesFromCovenant && (
<>
<Typography>
Purchase an additional Sleeves. These Duplicate Sleeves are permanent (they persist through BitNodes). You
can purchase a total of {MaxSleevesFromCovenant} from The Covenant.
</Typography>
<Button disabled={purchaseDisabled} onClick={purchaseOnClick}>
Purchase -&nbsp;
<Money money={purchaseCost()} player={player} />
</Button>
</>
)}
<br />
<br />
<Typography>You can also purchase upgrades for your Sleeves. These upgrades are also permanent.</Typography>
{upgradePanels}
</>
</Modal>
);
}

@ -2,15 +2,20 @@
* React component for a panel that lets you purchase upgrades for a Duplicate
* Sleeve's Memory (through The Covenant)
*/
import * as React from "react";
import React, { useState } from "react";
import { Sleeve } from "../Sleeve";
import { IPlayer } from "../../IPlayer";
import { numeralWrapper } from "../../../ui/numeralFormat";
import { StdButton } from "../../../ui/React/StdButton";
import { Money } from "../../../ui/React/Money";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
import Box from "@mui/material/Box";
import Paper from "@mui/material/Paper";
interface IProps {
index: number;
p: IPlayer;
@ -18,99 +23,74 @@ interface IProps {
sleeve: Sleeve;
}
interface IState {
amt: number;
}
export function CovenantSleeveMemoryUpgrade(props: IProps): React.ReactElement {
const [amt, setAmt] = useState(1);
export class CovenantSleeveMemoryUpgrade extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
amt: 1,
};
this.changePurchaseAmount = this.changePurchaseAmount.bind(this);
this.purchaseMemory = this.purchaseMemory.bind(this);
}
changePurchaseAmount(e: React.ChangeEvent<HTMLInputElement>): void {
function changePurchaseAmount(e: React.ChangeEvent<HTMLInputElement>): void {
let n: number = parseInt(e.target.value);
if (isNaN(n)) n = 1;
const maxMemory = 100 - this.props.sleeve.memory;
const maxMemory = 100 - props.sleeve.memory;
if (n > maxMemory) n = maxMemory;
this.setState({
amt: n,
});
setAmt(n);
}
getPurchaseCost(): number {
if (isNaN(this.state.amt)) {
function getPurchaseCost(): number {
if (isNaN(amt)) {
return Infinity;
}
const maxMemory = 100 - this.props.sleeve.memory;
if (this.state.amt > maxMemory) {
const maxMemory = 100 - props.sleeve.memory;
if (amt > maxMemory) {
return Infinity;
}
return this.props.sleeve.getMemoryUpgradeCost(this.state.amt);
return props.sleeve.getMemoryUpgradeCost(amt);
}
purchaseMemory(): void {
const cost = this.getPurchaseCost();
if (this.props.p.canAfford(cost)) {
this.props.sleeve.upgradeMemory(this.state.amt);
this.props.p.loseMoney(cost);
this.props.rerender();
function purchaseMemory(): void {
const cost = getPurchaseCost();
if (props.p.canAfford(cost)) {
props.sleeve.upgradeMemory(amt);
props.p.loseMoney(cost);
props.rerender();
}
}
render(): React.ReactNode {
const inputId = `sleeve-${this.props.index}-memory-upgrade-input`;
// Memory cannot go above 100
const maxMemory = 100 - this.props.sleeve.memory;
// Purchase button props
const cost = this.getPurchaseCost();
const purchaseBtnDisabled = !this.props.p.canAfford(cost);
let purchaseBtnContent;
if (isNaN(this.state.amt)) {
purchaseBtnContent = <>Invalid value</>;
} else if (this.state.amt > maxMemory) {
purchaseBtnContent = <>Memory cannot exceed 100</>;
} else {
purchaseBtnContent = (
<>
Purchase {this.state.amt} memory - <Money money={cost} player={this.props.p} />?
</>
);
}
return (
<div>
<h2>
<u>Upgrade Memory</u>
</h2>
<p>
Purchase a memory upgrade for your sleeve. Note that a sleeve's max memory is 100 (current:{" "}
{numeralWrapper.formatSleeveMemory(this.props.sleeve.memory)})
</p>
<label htmlFor={inputId}>Amount of memory to purchase (must be an integer):</label>
<input
className="text-input"
id={inputId}
onChange={this.changePurchaseAmount}
type={"number"}
value={this.state.amt}
/>
<br />
<StdButton disabled={purchaseBtnDisabled} onClick={this.purchaseMemory} text={purchaseBtnContent} />
</div>
// Purchase button props
const cost = getPurchaseCost();
const purchaseBtnDisabled = !props.p.canAfford(cost);
let purchaseBtnContent = <></>;
if (isNaN(amt)) {
purchaseBtnContent = <>Invalid value</>;
} else {
purchaseBtnContent = (
<>
Purchase {amt} memory&nbsp;-&nbsp;
<Money money={cost} player={props.p} />
</>
);
}
return (
<Paper sx={{ my: 1, p: 1 }}>
<Typography variant="h6" color="primary">
Upgrade Memory of Sleeve {props.index}
</Typography>
<Typography>
Purchase a memory upgrade for your sleeve. Note that a sleeve's max memory is 100 (current:{" "}
{numeralWrapper.formatSleeveMemory(props.sleeve.memory)})
</Typography>
<Box display="flex" flexDirection="row" alignItems="center">
<Typography>Amount of memory to purchase (must be an integer):&nbsp;</Typography>
<TextField variant="standard" onChange={changePurchaseAmount} type={"number"} value={amt} />
</Box>
<br />
<Button disabled={purchaseBtnDisabled} onClick={purchaseMemory}>
{purchaseBtnContent}
</Button>
</Paper>
);
}

@ -1,28 +0,0 @@
/**
* React Component for a panel that lets you purchase upgrades for a single
* Duplicate Sleeve through The Covenant
*/
import * as React from "react";
import { CovenantSleeveMemoryUpgrade } from "./CovenantSleeveMemoryUpgrade";
import { Sleeve } from "../Sleeve";
import { IPlayer } from "../../IPlayer";
interface IProps {
index: number;
p: IPlayer;
rerender: () => void;
sleeve: Sleeve;
}
export class CovenantSleeveUpgrades extends React.Component<IProps, any> {
render(): React.ReactNode {
return (
<div className={"bladeburner-action"}>
<h1>Duplicate Sleeve {this.props.index}</h1>
<CovenantSleeveMemoryUpgrade {...this.props} />
</div>
);
}
}

@ -1,33 +0,0 @@
import * as React from "react";
interface IProps {
title: string;
stats: any[][];
}
export function EarningsTableElement(props: IProps): React.ReactElement {
return (
<>
<pre>{props.title}</pre>
<table>
<tbody>
{props.stats.map((stat: any[], i: number) => (
<tr key={i}>
{stat.map((s: any, i: number) => {
let style = {};
if (i !== 0) {
style = { textAlign: "right" };
}
return (
<td style={style} key={i}>
{s}
</td>
);
})}
</tr>
))}
</tbody>
</table>
</>
);
}

@ -0,0 +1,118 @@
import React from "react";
import { Modal } from "../../../ui/React/Modal";
import Typography from "@mui/material/Typography";
interface IProps {
open: boolean;
onClose: () => void;
}
export function FAQModal({ open, onClose }: IProps): React.ReactElement {
return (
<Modal open={open} onClose={onClose}>
<>
<Typography variant="h4">How do Duplicate Sleeves work?</Typography>
<br />
<Typography>
Duplicate Sleeves are essentially clones. You can use them to perform any work type action, such as working
for a company/faction or committing a crime. Having sleeves perform these tasks earns you money, experience,
and reputation.
</Typography>
<br />
<br />
<Typography>
Sleeves are their own individuals, which means they each have their own experience and stats.
</Typography>
<br />
<br />
<Typography>
When a sleeve earns experience, it earns experience for itself, the player's original 'consciousness', as well
as all of the player's other sleeves.
</Typography>
<br />
<br />
<Typography variant="h4">What is Synchronization (Sync)?</Typography>
<br />
<Typography>
Synchronization is a measure of how aligned your consciousness is with that of your Duplicate Sleeves. It is a
numerical value between 1 and 100, and it affects how much experience is earned when the sleeve is performing
a task.
</Typography>
<br />
<br />
<Typography>
Let N be the sleeve's synchronization. When the sleeve earns experience by performing a task, both the sleeve
and the player's original host consciousness earn N% of the amount of experience normally earned by the task.
All of the player's other sleeves earn ((N/100)^2 * 100)% of the experience.
</Typography>
<br />
<br />
<Typography>Synchronization can be increased by assigning sleeves to the 'Synchronize' task.</Typography>
<br />
<br />
<Typography variant="h4">What is Shock?</Typography>
<br />
<Typography>
Sleeve shock is a measure of how much trauma the sleeve has due to being placed in a new body. It is a
numerical value between 0 and 99, where 99 indicates full shock and 0 indicates no shock. Shock affects the
amount of experience earned by the sleeve.
</Typography>
<br />
<br />
<Typography>
Sleeve shock slowly decreases over time. You can further increase the rate at which it decreases by assigning
sleeves to the 'Shock Recovery' task.
</Typography>
<br />
<br />
<Typography variant="h4">Why can't I work for this company or faction?</Typography>
<br />
<Typography>
Only one of your sleeves can work for a given company/faction a time. To clarify further, if you have two
sleeves they can work for two different companies, but they cannot both work for the same company.
</Typography>
<br />
<br />
<Typography variant="h4">Why did my Sleeve stop working?</Typography>
<br />
<Typography>
Sleeves are subject to the same time restrictions as you. This means that they automatically stop working at a
company after 8 hours, and stop working for a faction after 20 hours.
</Typography>
<br />
<br />
<Typography variant="h4">How do I buy Augmentations for my Sleeves?</Typography>
<br />
<Typography>Your Sleeve needs to have a Shock of 0 in order for you to buy Augmentations for it.</Typography>
<br />
<br />
<Typography variant="h4">Why can't I buy the X Augmentation for my sleeve?</Typography>
<br />
<Typography>
Certain Augmentations, like Bladeburner-specific ones and NeuroFlux Governor, are not available for sleeves.
</Typography>
<br />
<br />
<Typography variant="h4">Do sleeves get reset when installing Augmentations or switching BitNodes?</Typography>
<br />
<Typography>Sleeves are reset when switching BitNodes, but not when installing Augmentations.</Typography>
<br />
<br />
<Typography variant="h4">What is Memory?</Typography>
<br />
<Typography>
Sleeve memory dictates what a sleeve's synchronization will be when its reset by switching BitNodes. For
example, if a sleeve has a memory of 25, then when you switch BitNodes its synchronization will initially be
set to 25, rather than 1.
</Typography>
<br />
<br />
<Typography>
Memory can only be increased by purchasing upgrades from The Covenant. It is a persistent stat, meaning it
never gets resets back to 1. The maximum possible value for a sleeve's memory is 100.
</Typography>
</>
</Modal>
);
}

@ -3,14 +3,17 @@ import { numeralWrapper } from "../../../ui/numeralFormat";
import { Money } from "../../../ui/React/Money";
import * as React from "react";
import { StatsTable } from "../../../ui/React/StatsTable";
import { Modal } from "../../../ui/React/Modal";
interface IProps {
open: boolean;
onClose: () => void;
sleeve: Sleeve;
}
export function MoreEarningsContent(props: IProps): React.ReactElement {
export function MoreEarningsModal(props: IProps): React.ReactElement {
return (
<>
<Modal open={props.open} onClose={props.onClose}>
<StatsTable
rows={[
["Money ", <Money money={props.sleeve.earningsForTask.money} />],
@ -50,6 +53,6 @@ export function MoreEarningsContent(props: IProps): React.ReactElement {
title="Total Earnings for Other Sleeves:"
/>
<br />
</>
</Modal>
);
}

@ -1,49 +0,0 @@
import { Sleeve } from "../Sleeve";
import { numeralWrapper } from "../../../ui/numeralFormat";
import { StatsTable } from "../../../ui/React/StatsTable";
import * as React from "react";
interface IProps {
sleeve: Sleeve;
}
export function MoreStatsContent(props: IProps): React.ReactElement {
return (
<>
<StatsTable
rows={[
["Hacking: ", props.sleeve.hacking_skill, `(${numeralWrapper.formatExp(props.sleeve.hacking_exp)} exp)`],
["Strength: ", props.sleeve.strength, `(${numeralWrapper.formatExp(props.sleeve.strength_exp)} exp)`],
["Defense: ", props.sleeve.defense, `(${numeralWrapper.formatExp(props.sleeve.defense_exp)} exp)`],
["Dexterity: ", props.sleeve.dexterity, `(${numeralWrapper.formatExp(props.sleeve.dexterity_exp)} exp)`],
["Agility: ", props.sleeve.agility, `(${numeralWrapper.formatExp(props.sleeve.agility_exp)} exp)`],
["Charisma: ", props.sleeve.charisma, `(${numeralWrapper.formatExp(props.sleeve.charisma_exp)} exp)`],
]}
title="Stats:"
/>
<br />
<StatsTable
rows={[
["Hacking Level multiplier: ", numeralWrapper.formatPercentage(props.sleeve.hacking_mult)],
["Hacking Experience multiplier: ", numeralWrapper.formatPercentage(props.sleeve.hacking_exp_mult)],
["Strength Level multiplier: ", numeralWrapper.formatPercentage(props.sleeve.strength_mult)],
["Strength Experience multiplier: ", numeralWrapper.formatPercentage(props.sleeve.strength_exp_mult)],
["Defense Level multiplier: ", numeralWrapper.formatPercentage(props.sleeve.defense_mult)],
["Defense Experience multiplier: ", numeralWrapper.formatPercentage(props.sleeve.defense_exp_mult)],
["Dexterity Level multiplier: ", numeralWrapper.formatPercentage(props.sleeve.dexterity_mult)],
["Dexterity Experience multiplier: ", numeralWrapper.formatPercentage(props.sleeve.dexterity_exp_mult)],
["Agility Level multiplier: ", numeralWrapper.formatPercentage(props.sleeve.agility_mult)],
["Agility Experience multiplier: ", numeralWrapper.formatPercentage(props.sleeve.agility_exp_mult)],
["Charisma Level multiplier: ", numeralWrapper.formatPercentage(props.sleeve.charisma_mult)],
["Charisma Experience multiplier: ", numeralWrapper.formatPercentage(props.sleeve.charisma_exp_mult)],
["Faction Reputation Gain multiplier: ", numeralWrapper.formatPercentage(props.sleeve.faction_rep_mult)],
["Company Reputation Gain multiplier: ", numeralWrapper.formatPercentage(props.sleeve.company_rep_mult)],
["Salary multiplier: ", numeralWrapper.formatPercentage(props.sleeve.work_money_mult)],
["Crime Money multiplier: ", numeralWrapper.formatPercentage(props.sleeve.crime_money_mult)],
["Crime Success multiplier: ", numeralWrapper.formatPercentage(props.sleeve.crime_success_mult)],
]}
title="Multipliers:"
/>
</>
);
}

@ -0,0 +1,85 @@
import { Sleeve } from "../Sleeve";
import { numeralWrapper } from "../../../ui/numeralFormat";
import { StatsTable } from "../../../ui/React/StatsTable";
import { Modal } from "../../../ui/React/Modal";
import React from "react";
interface IProps {
open: boolean;
onClose: () => void;
sleeve: Sleeve;
}
export function MoreStatsModal(props: IProps): React.ReactElement {
return (
<Modal open={props.open} onClose={props.onClose}>
<StatsTable
rows={[
[
<>Hacking:&nbsp;</>,
props.sleeve.hacking_skill,
<>&nbsp;({numeralWrapper.formatExp(props.sleeve.hacking_exp)} exp)</>,
],
[
<>Strength:&nbsp;</>,
props.sleeve.strength,
<>&nbsp;({numeralWrapper.formatExp(props.sleeve.strength_exp)} exp)</>,
],
[
<>Defense:&nbsp;</>,
props.sleeve.defense,
<>&nbsp;({numeralWrapper.formatExp(props.sleeve.defense_exp)} exp)</>,
],
[
<>Dexterity:&nbsp;</>,
props.sleeve.dexterity,
<>&nbsp;({numeralWrapper.formatExp(props.sleeve.dexterity_exp)} exp)</>,
],
[
<>Agility:&nbsp;</>,
props.sleeve.agility,
<>&nbsp;({numeralWrapper.formatExp(props.sleeve.agility_exp)} exp)</>,
],
[
<>Charisma:&nbsp;</>,
props.sleeve.charisma,
<>&nbsp;({numeralWrapper.formatExp(props.sleeve.charisma_exp)} exp)</>,
],
]}
title="Stats:"
/>
<br />
<StatsTable
rows={[
[<>Hacking Level multiplier:&nbsp;</>, numeralWrapper.formatPercentage(props.sleeve.hacking_mult)],
[<>Hacking Experience multiplier:&nbsp;</>, numeralWrapper.formatPercentage(props.sleeve.hacking_exp_mult)],
[<>Strength Level multiplier:&nbsp;</>, numeralWrapper.formatPercentage(props.sleeve.strength_mult)],
[<>Strength Experience multiplier:&nbsp;</>, numeralWrapper.formatPercentage(props.sleeve.strength_exp_mult)],
[<>Defense Level multiplier:&nbsp;</>, numeralWrapper.formatPercentage(props.sleeve.defense_mult)],
[<>Defense Experience multiplier:&nbsp;</>, numeralWrapper.formatPercentage(props.sleeve.defense_exp_mult)],
[<>Dexterity Level multiplier:&nbsp;</>, numeralWrapper.formatPercentage(props.sleeve.dexterity_mult)],
[
<>Dexterity Experience multiplier:&nbsp;</>,
numeralWrapper.formatPercentage(props.sleeve.dexterity_exp_mult),
],
[<>Agility Level multiplier:&nbsp;</>, numeralWrapper.formatPercentage(props.sleeve.agility_mult)],
[<>Agility Experience multiplier:&nbsp;</>, numeralWrapper.formatPercentage(props.sleeve.agility_exp_mult)],
[<>Charisma Level multiplier:&nbsp;</>, numeralWrapper.formatPercentage(props.sleeve.charisma_mult)],
[<>Charisma Experience multiplier:&nbsp;</>, numeralWrapper.formatPercentage(props.sleeve.charisma_exp_mult)],
[
<>Faction Reputation Gain multiplier:&nbsp;</>,
numeralWrapper.formatPercentage(props.sleeve.faction_rep_mult),
],
[
<>Company Reputation Gain multiplier:&nbsp;</>,
numeralWrapper.formatPercentage(props.sleeve.company_rep_mult),
],
[<>Salary multiplier:&nbsp;</>, numeralWrapper.formatPercentage(props.sleeve.work_money_mult)],
[<>Crime Money multiplier:&nbsp;</>, numeralWrapper.formatPercentage(props.sleeve.crime_money_mult)],
[<>Crime Success multiplier:&nbsp;</>, numeralWrapper.formatPercentage(props.sleeve.crime_success_mult)],
]}
title="Multipliers:"
/>
</Modal>
);
}

@ -0,0 +1,126 @@
import React, { useState, useEffect } from "react";
import { Sleeve } from "../Sleeve";
import { findSleevePurchasableAugs } from "../SleeveHelpers";
import { Augmentations } from "../../../Augmentation/Augmentations";
import { Augmentation } from "../../../Augmentation/Augmentation";
import { Money } from "../../../ui/React/Money";
import { Modal } from "../../../ui/React/Modal";
import { use } from "../../../ui/Context";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
import Paper from "@mui/material/Paper";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import TableBody from "@mui/material/TableBody";
import Table from "@mui/material/Table";
import { TableCell } from "../../../ui/React/Table";
import TableRow from "@mui/material/TableRow";
interface IProps {
open: boolean;
onClose: () => void;
sleeve: Sleeve;
}
export function SleeveAugmentationsModal(props: IProps): React.ReactElement {
const player = use.Player();
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
useEffect(() => {
const id = setInterval(rerender, 150);
return () => clearInterval(id);
}, []);
// Array of all owned Augmentations. Names only
const ownedAugNames = props.sleeve.augmentations.map((e) => e.name);
// You can only purchase Augmentations that are actually available from
// your factions. I.e. you must be in a faction that has the Augmentation
// and you must also have enough rep in that faction in order to purchase it.
const availableAugs = findSleevePurchasableAugs(props.sleeve, player);
function purchaseAugmentation(aug: Augmentation): void {
props.sleeve.tryBuyAugmentation(player, aug);
rerender();
}
return (
<Modal open={props.open} onClose={props.onClose}>
<>
<Typography>
You can purchase Augmentations for your Duplicate Sleeves. These Augmentations have the same effect as they
would for you. You can only purchase Augmentations that you have unlocked through Factions.
<br />
<br />
When purchasing an Augmentation for a Duplicate Sleeve, they are immediately installed. This means that the
Duplicate Sleeve will immediately lose all of its stat experience.
</Typography>
<Table size="small" padding="none">
<TableBody>
{availableAugs.map((aug) => {
return (
<TableRow key={aug.name}>
<TableCell>
<Button onClick={() => purchaseAugmentation(aug)} disabled={player.money.lt(aug.startingCost)}>
Buy
</Button>
</TableCell>
<TableCell>
<Box display="flex">
<Tooltip disableInteractive title={aug.stats || ""}>
<Typography>{aug.name}</Typography>
</Tooltip>
</Box>
</TableCell>
<TableCell>
<Money money={aug.startingCost} player={player} />
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
{ownedAugNames.length > 0 && (
<>
<Typography>Owned Augmentations:</Typography>
{ownedAugNames.map((augName) => {
const aug = Augmentations[augName];
let tooltip = <></>;
if (typeof aug.info === "string") {
tooltip = (
<>
<span>{aug.info}</span>
<br />
<br />
{aug.stats}
</>
);
} else {
tooltip = (
<>
{aug.info}
<br />
<br />
{aug.stats}
</>
);
}
return (
<Tooltip key={augName} disableInteractive title={<Typography>{tooltip}</Typography>}>
<Paper>
<Typography>{augName}</Typography>
</Paper>
</Tooltip>
);
})}
</>
)}
</>
</Modal>
);
}

@ -1,90 +0,0 @@
import React, { useState, useEffect } from "react";
import { Sleeve } from "../Sleeve";
import { findSleevePurchasableAugs } from "../SleeveHelpers";
import { Augmentations } from "../../../Augmentation/Augmentations";
import { Augmentation } from "../../../Augmentation/Augmentation";
import { IPlayer } from "../../IPlayer";
import { Money } from "../../../ui/React/Money";
import { renderToStaticMarkup } from "react-dom/server";
interface IProps {
sleeve: Sleeve;
player: IPlayer;
}
export function SleeveAugmentationsPopup(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
useEffect(() => {
const id = setInterval(rerender, 150);
return () => clearInterval(id);
}, []);
// Array of all owned Augmentations. Names only
const ownedAugNames = props.sleeve.augmentations.map((e) => e.name);
// You can only purchase Augmentations that are actually available from
// your factions. I.e. you must be in a faction that has the Augmentation
// and you must also have enough rep in that faction in order to purchase it.
const availableAugs = findSleevePurchasableAugs(props.sleeve, props.player);
function purchaseAugmentation(aug: Augmentation): void {
props.sleeve.tryBuyAugmentation(props.player, aug);
rerender();
}
return (
<div className="noselect">
<p style={{ display: "block" }}>Owned Augmentations:</p>
<div style={{ width: "70%" }}>
{ownedAugNames.map((augName) => {
const aug = Augmentations[augName];
let tooltip = aug.info;
if (typeof tooltip !== "string") {
tooltip = renderToStaticMarkup(tooltip);
}
tooltip += "<br /><br />";
tooltip += renderToStaticMarkup(aug.stats || <></>);
return (
<div key={augName} className="gang-owned-upgrade tooltip">
{augName}
<span className="tooltiptext" dangerouslySetInnerHTML={{ __html: tooltip }}></span>
</div>
);
})}
</div>
<p>
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.
<br />
<br />
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.
</p>
{availableAugs.map((aug) => {
let info = aug.info;
if (typeof info !== "string") {
info = renderToStaticMarkup(info);
}
info += "<br /><br />";
info += renderToStaticMarkup(aug.stats || <></>);
return (
<div key={aug.name} className="cmpy-mgmt-upgrade-div" onClick={() => purchaseAugmentation(aug)}>
<div style={{ fontSize: "12px", padding: "2px" }}>
<h2>{aug.name}</h2>
<br />
Cost: <Money money={aug.startingCost} player={props.player} />
<br />
<br />
<span dangerouslySetInnerHTML={{ __html: info }}></span>
</div>
</div>
);
})}
</div>
);
}

@ -3,91 +3,72 @@ import React, { useState } from "react";
import { Sleeve } from "../Sleeve";
import { SleeveTaskType } from "../SleeveTaskTypesEnum";
import { IPlayer } from "../../IPlayer";
import { CONSTANTS } from "../../../Constants";
import { Crimes } from "../../../Crime/Crimes";
import { numeralWrapper } from "../../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
import { createPopup } from "../../../ui/React/createPopup";
import { SleeveAugmentationsPopup } from "../ui/SleeveAugmentationsPopup";
import { TravelPopup } from "../ui/TravelPopup";
import { EarningsTableElement } from "../ui/EarningsTableElement";
import { SleeveAugmentationsModal } from "./SleeveAugmentationsModal";
import { TravelModal } from "./TravelModal";
import { Money } from "../../../ui/React/Money";
import { MoneyRate } from "../../../ui/React/MoneyRate";
import { use } from "../../../ui/Context";
import { ReputationRate } from "../../../ui/React/ReputationRate";
import { StatsElement } from "../ui/StatsElement";
import { MoreStatsContent } from "../ui/MoreStatsContent";
import { MoreEarningsContent } from "../ui/MoreEarningsContent";
import { MoreStatsModal } from "./MoreStatsModal";
import { MoreEarningsModal } from "../ui/MoreEarningsModal";
import { TaskSelector } from "../ui/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 {
player: IPlayer;
sleeve: Sleeve;
rerender: () => void;
}
export function SleeveElem(props: IProps): React.ReactElement {
const player = use.Player();
const [statsOpen, setStatsOpen] = useState(false);
const [earningsOpen, setEarningsOpen] = useState(false);
const [travelOpen, setTravelOpen] = useState(false);
const [augmentationsOpen, setAugmentationsOpen] = useState(false);
const [abc, setABC] = useState(["------", "------", "------"]);
function openMoreStats(): void {
dialogBoxCreate(<MoreStatsContent sleeve={props.sleeve} />);
}
function openTravel(): void {
const popupId = "sleeve-travel-popup";
createPopup(popupId, TravelPopup, {
popupId: popupId,
sleeve: props.sleeve,
player: props.player,
rerender: props.rerender,
});
}
function openManageAugmentations(): void {
const popupId = "sleeve-augmentation-popup";
createPopup(popupId, SleeveAugmentationsPopup, {
sleeve: props.sleeve,
player: props.player,
});
}
function openMoreEarnings(): void {
dialogBoxCreate(<MoreEarningsContent sleeve={props.sleeve} />);
}
function setTask(): void {
props.sleeve.resetTaskStatus(); // sets to idle
switch (abc[0]) {
case "------":
break;
case "Work for Company":
props.sleeve.workForCompany(props.player, abc[1]);
props.sleeve.workForCompany(player, abc[1]);
break;
case "Work for Faction":
props.sleeve.workForFaction(props.player, abc[1], abc[2]);
props.sleeve.workForFaction(player, abc[1], abc[2]);
break;
case "Commit Crime":
props.sleeve.commitCrime(props.player, abc[1]);
props.sleeve.commitCrime(player, abc[1]);
break;
case "Take University Course":
props.sleeve.takeUniversityCourse(props.player, abc[2], abc[1]);
props.sleeve.takeUniversityCourse(player, abc[2], abc[1]);
break;
case "Workout at Gym":
props.sleeve.workoutAtGym(props.player, abc[2], abc[1]);
props.sleeve.workoutAtGym(player, abc[2], abc[1]);
break;
case "Shock Recovery":
props.sleeve.shockRecovery(props.player);
props.sleeve.shockRecovery(player);
break;
case "Synchronize":
props.sleeve.synchronize(props.player);
props.sleeve.synchronize(player);
break;
default:
console.error(`Invalid/Unrecognized taskValue in setSleeveTask(): ${abc[0]}`);
@ -168,11 +149,6 @@ export function SleeveElem(props: IProps): React.ReactElement {
[`Agility Exp`, numeralWrapper.formatExp(props.sleeve.gainRatesForTask.agi), `(2x on success)`],
[`Charisma Exp`, numeralWrapper.formatExp(props.sleeve.gainRatesForTask.cha), `(2x on success)`],
];
// elems.taskProgressBar.innerText = createProgressBarText({
// progress: props.sleeve.currentTaskTime / props.sleeve.currentTaskMaxTime,
// totalTicks: 25,
// });
} else {
data = [
[`Money:`, <MoneyRate money={5 * props.sleeve.gainRatesForTask.money} />],
@ -184,60 +160,68 @@ export function SleeveElem(props: IProps): React.ReactElement {
[`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(props.player);
const repGain: number = props.sleeve.getRepGain(player);
data.push([`Reputation:`, ReputationRate(5 * repGain)]);
}
// elems.taskProgressBar.innerText = "";
}
return (
<div className="sleeve-elem">
<div className="sleeve-panel" style={{ width: "25%" }}>
<div className="sleeve-stats-text">
<>
<Grid container component={Paper}>
<Grid item xs={3}>
<StatsElement sleeve={props.sleeve} />
<button className="std-button" onClick={openMoreStats}>
More Stats
</button>
<button
className={`std-button${props.player.money.lt(CONSTANTS.TravelCost) ? " tooltip" : ""}`}
onClick={openTravel}
disabled={props.player.money.lt(CONSTANTS.TravelCost)}
<Button onClick={() => setStatsOpen(true)}>More Stats</Button>
<Tooltip
disableInteractive
title={player.money.lt(CONSTANTS.TravelCost) ? <Typography>Insufficient funds</Typography> : ""}
>
Travel
{props.player.money.lt(CONSTANTS.TravelCost) && <span className="tooltiptext">Not enough money</span>}
</button>
<button
className={`std-button${props.sleeve.shock < 100 ? " tooltip" : ""}`}
onClick={openManageAugmentations}
style={{ display: "block" }}
disabled={props.sleeve.shock < 100}
<span>
<Button onClick={() => setTravelOpen(true)} disabled={player.money.lt(CONSTANTS.TravelCost)}>
Travel
</Button>
</span>
</Tooltip>
<Tooltip
disableInteractive
title={props.sleeve.shock < 100 ? <Typography>Unlocked when sleeve has fully recovered</Typography> : ""}
>
Manage Augmentations
{props.sleeve.shock < 100 && <span className="tooltiptext">Unlocked when sleeve has fully recovered</span>}
</button>
</div>
</div>
<div className="sleeve-panel" style={{ width: "40%" }}>
<TaskSelector player={props.player} sleeve={props.sleeve} setABC={setABC} />
<p>{desc}</p>
<p>
{props.sleeve.currentTask === SleeveTaskType.Crime &&
createProgressBarText({
progress: props.sleeve.currentTaskTime / props.sleeve.currentTaskMaxTime,
totalTicks: 25,
})}
</p>
<button className="std-button" onClick={setTask}>
Set Task
</button>
</div>
<div className="sleeve-panel" style={{ width: "35%" }}>
<EarningsTableElement title="Earnings (Pre-Synchronization)" stats={data} />
<button className="std-button" onClick={openMoreEarnings}>
More Earnings Info
</button>
</div>
</div>
<span>
<Button onClick={() => setAugmentationsOpen(true)} disabled={props.sleeve.shock < 100}>
Manage Augmentations
</Button>
</span>
</Tooltip>
</Grid>
<Grid item xs={5}>
<TaskSelector player={player} sleeve={props.sleeve} setABC={setABC} />
<Typography>{desc}</Typography>
<Typography>
{props.sleeve.currentTask === SleeveTaskType.Crime &&
createProgressBarText({
progress: props.sleeve.currentTaskTime / props.sleeve.currentTaskMaxTime,
totalTicks: 25,
})}
</Typography>
<Button onClick={setTask}>Set Task</Button>
</Grid>
<Grid item xs={4}>
<StatsTable title="Earnings (Pre-Synchronization)" rows={data} />
<Button onClick={() => setEarningsOpen(true)}>More Earnings Info</Button>
</Grid>
</Grid>
<MoreStatsModal open={statsOpen} onClose={() => setStatsOpen(false)} 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}
/>
</>
);
}

@ -1,28 +1,30 @@
import React, { useState, useEffect } from "react";
import { IPlayer } from "../../IPlayer";
import { SleeveElem } from "./SleeveElem";
import { FAQModal } from "./FAQModal";
import { use } from "../../../ui/Context";
import { SleeveElem } from "../ui/SleeveElem";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import Link from "@mui/material/Link";
interface IProps {
player: IPlayer;
}
export function SleeveRoot(props: IProps): React.ReactElement {
export function SleeveRoot(): React.ReactElement {
const player = use.Player();
const [FAQOpen, setFAQOpen] = useState(false);
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
useEffect(() => {
const id = setInterval(rerender, 150);
const id = setInterval(rerender, 200);
return () => clearInterval(id);
}, []);
return (
<>
<h1>Sleeves</h1>
<p>
<Typography variant="h4">Sleeves</Typography>
<Typography>
Duplicate Sleeves are MK-V Synthoids (synthetic androids) into which your consciousness has been copied. In
other words, these Synthoids contain a perfect duplicate of your mind.
<br />
@ -30,26 +32,19 @@ export function SleeveRoot(props: IProps): React.ReactElement {
Sleeves can be used to perform different tasks synchronously.
<br />
<br />
</p>
</Typography>
<button className="std-button" style={{ display: "inline-block" }}>
FAQ
</button>
<a
className="std-button"
style={{ display: "inline-block" }}
<Button onClick={() => setFAQOpen(true)}>FAQ</Button>
<Link
target="_blank"
href="https://bitburner.readthedocs.io/en/latest/advancedgameplay/sleeves.html#duplicate-sleeves"
>
Documentation
</a>
<ul>
{props.player.sleeves.map((sleeve, i) => (
<li key={i}>
<SleeveElem rerender={rerender} player={props.player} sleeve={sleeve} />
</li>
))}
</ul>
</Link>
{player.sleeves.map((sleeve, i) => (
<SleeveElem key={i} rerender={rerender} sleeve={sleeve} />
))}
<FAQModal open={FAQOpen} onClose={() => setFAQOpen(false)} />
</>
);
}

@ -1,94 +1,31 @@
import { Sleeve } from "../Sleeve";
import { numeralWrapper } from "../../../ui/numeralFormat";
import * as React from "react";
import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions";
import { CONSTANTS } from "../../../Constants";
import React from "react";
import { StatsTable } from "../../../ui/React/StatsTable";
interface IProps {
sleeve: Sleeve;
}
export function StatsElement(props: IProps): React.ReactElement {
let style = {};
style = { textAlign: "right" };
return (
<>
<table>
<tbody>
<tr>
<td className="character-hp-cell">HP: </td>
<td className="character-hp-cell" style={style}>
{numeralWrapper.formatHp(props.sleeve.hp)} / {numeralWrapper.formatHp(props.sleeve.max_hp)}
</td>
</tr>
<tr>
<td>City: </td>
<td style={style}>{props.sleeve.city}</td>
</tr>
<tr>
<td className="character-hack-cell">Hacking: </td>
<td className="character-hack-cell" style={style}>
{numeralWrapper.formatSkill(props.sleeve.hacking_skill)}
</td>
</tr>
<tr>
<td className="character-combat-cell">Strength: </td>
<td className="character-combat-cell" style={style}>
{numeralWrapper.formatSkill(props.sleeve.strength)}
</td>
</tr>
<tr>
<td className="character-combat-cell">Defense: </td>
<td className="character-combat-cell" style={style}>
{numeralWrapper.formatSkill(props.sleeve.defense)}
</td>
</tr>
<tr>
<td className="character-combat-cell">Dexterity: </td>
<td className="character-combat-cell" style={style}>
{numeralWrapper.formatSkill(props.sleeve.dexterity)}
</td>
</tr>
<tr>
<td className="character-combat-cell">Agility: </td>
<td className="character-combat-cell" style={style}>
{numeralWrapper.formatSkill(props.sleeve.agility)}
</td>
</tr>
<tr>
<td className="character-cha-cell">Charisma: </td>
<td className="character-cha-cell" style={style}>
{numeralWrapper.formatSkill(props.sleeve.charisma)}
</td>
</tr>
<tr>
<td className="character-int-cell">Shock: </td>
<td className="character-int-cell" style={style}>
{numeralWrapper.formatSleeveShock(100 - props.sleeve.shock)}
</td>
</tr>
<tr>
<td className="character-int-cell">Sync: </td>
<td className="character-int-cell" style={style}>
{numeralWrapper.formatSleeveSynchro(props.sleeve.sync)}
</td>
</tr>
<tr>
<td className="character-int-cell">Memory: </td>
<td className="character-int-cell" style={style}>
{numeralWrapper.formatSleeveMemory(props.sleeve.memory)}
</td>
</tr>
{props.sleeve.storedCycles > 15 && (
<tr>
<td>Bonus time: </td>
<td style={style}>
{convertTimeMsToTimeElapsedString((props.sleeve.storedCycles / (1000 / CONSTANTS._idleSpeed)) * 1000)}
</td>
</tr>
)}
</tbody>
</table>
</>
);
const rows = [
[
"HP: ",
<>
{numeralWrapper.formatHp(props.sleeve.hp)} / {numeralWrapper.formatHp(props.sleeve.max_hp)}
</>,
],
["City: ", <>{props.sleeve.city}</>],
["Hacking: ", <>{numeralWrapper.formatSkill(props.sleeve.hacking_skill)}</>],
["Strength: ", <>{numeralWrapper.formatSkill(props.sleeve.strength)}</>],
["Defense: ", <>{numeralWrapper.formatSkill(props.sleeve.defense)}</>],
["Dexterity: ", <>{numeralWrapper.formatSkill(props.sleeve.dexterity)}</>],
["Agility: ", <>{numeralWrapper.formatSkill(props.sleeve.agility)}</>],
["Charisma: ", <>{numeralWrapper.formatSkill(props.sleeve.charisma)}</>],
["Shock: ", <>{numeralWrapper.formatSleeveShock(100 - props.sleeve.shock)}</>],
["Sync: ", <>{numeralWrapper.formatSleeveSynchro(props.sleeve.sync)}</>],
["Memory: ", <>{numeralWrapper.formatSleeveMemory(props.sleeve.memory)}</>],
];
return <StatsTable rows={rows} />;
}

@ -7,6 +7,8 @@ import { LocationName } from "../../../Locations/data/LocationNames";
import { CityName } from "../../../Locations/data/CityNames";
import { Factions } from "../../../Faction/Factions";
import { FactionWorkType } from "../../../Faction/FactionWorkTypeEnum";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem";
const universitySelectorOptions: string[] = [
"Study Computer Science",
@ -249,7 +251,7 @@ export function TaskSelector(props: IProps): React.ReactElement {
props.setABC([s0, s1, details2[0]]);
}
function onS0Change(event: React.ChangeEvent<HTMLSelectElement>): void {
function onS0Change(event: SelectChangeEvent<string>): void {
const n = event.target.value;
const detailsF = tasks[n];
if (detailsF === undefined) throw new Error(`No function for task '${s0}'`);
@ -261,42 +263,48 @@ export function TaskSelector(props: IProps): React.ReactElement {
props.setABC([n, details.first[0], details2[0]]);
}
function onS1Change(event: React.ChangeEvent<HTMLSelectElement>): void {
function onS1Change(event: SelectChangeEvent<string>): void {
setS1(event.target.value);
props.setABC([s0, event.target.value, s2]);
}
function onS2Change(event: React.ChangeEvent<HTMLSelectElement>): void {
function onS2Change(event: SelectChangeEvent<string>): void {
setS2(event.target.value);
props.setABC([s0, s1, event.target.value]);
}
return (
<>
<select className="dropdown" onChange={onS0Change} defaultValue={s0}>
<Select variant="standard" onChange={onS0Change} value={s0}>
{validActions.map((task) => (
<option key={task} value={task}>
<MenuItem key={task} value={task}>
{task}
</option>
</MenuItem>
))}
</select>
</Select>
{!(details.first.length === 1 && details.first[0] === "------") && (
<select className="dropdown" onChange={onS1Change} defaultValue={s1}>
{details.first.map((detail) => (
<option key={detail} value={detail}>
{detail}
</option>
))}
</select>
<>
<br />
<Select variant="standard" onChange={onS1Change} value={s1}>
{details.first.map((detail) => (
<MenuItem key={detail} value={detail}>
{detail}
</MenuItem>
))}
</Select>
</>
)}
{!(details2.length === 1 && details2[0] === "------") && (
<select className="dropdown" onChange={onS2Change} defaultValue={s2}>
{details2.map((detail) => (
<option key={detail} value={detail}>
{detail}
</option>
))}
</select>
<>
<br />
<Select variant="standard" onChange={onS2Change} value={s2}>
{details2.map((detail) => (
<MenuItem key={detail} value={detail}>
{detail}
</MenuItem>
))}
</Select>
</>
)}
</>
);

@ -0,0 +1,54 @@
import React from "react";
import { Sleeve } from "../Sleeve";
import { CONSTANTS } from "../../../Constants";
import { Money } from "../../../ui/React/Money";
import { WorldMap } from "../../../ui/React/WorldMap";
import { CityName } from "../../../Locations/data/CityNames";
import { Settings } from "../../../Settings/Settings";
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { use } from "../../../ui/Context";
import { Modal } from "../../../ui/React/Modal";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
interface IProps {
open: boolean;
onClose: () => void;
sleeve: Sleeve;
rerender: () => void;
}
export function TravelModal(props: IProps): React.ReactElement {
const player = use.Player();
function travel(city: string): void {
if (!player.canAfford(CONSTANTS.TravelCost)) {
dialogBoxCreate("You cannot afford to have this sleeve travel to another city");
}
props.sleeve.city = city as CityName;
player.loseMoney(CONSTANTS.TravelCost);
props.sleeve.resetTaskStatus();
props.rerender();
props.onClose();
}
return (
<Modal open={props.open} onClose={props.onClose}>
<>
<Typography>
Have this sleeve travel to a different city. This affects the gyms and universities at which this sleeve can
study. Traveling to a different city costs <Money money={CONSTANTS.TravelCost} player={player} />. It will
also set your current sleeve task to idle.
</Typography>
{Settings.DisableASCIIArt ? (
Object.values(CityName).map((city: CityName) => (
<Button key={city} onClick={() => travel(city)}>
{city}
</Button>
))
) : (
<WorldMap currentCity={props.sleeve.city} onTravel={(city: CityName) => travel(city)} />
)}
</>
</Modal>
);
}

@ -1,49 +0,0 @@
import React from "react";
import { Sleeve } from "../Sleeve";
import { IPlayer } from "../../IPlayer";
import { CONSTANTS } from "../../../Constants";
import { removePopup } from "../../../ui/React/createPopup";
import { Money } from "../../../ui/React/Money";
import { WorldMap } from "../../../ui/React/WorldMap";
import { CityName } from "../../../Locations/data/CityNames";
import { Settings } from "../../../Settings/Settings";
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
interface IProps {
popupId: string;
sleeve: Sleeve;
player: IPlayer;
rerender: () => void;
}
export function TravelPopup(props: IProps): React.ReactElement {
function travel(city: string): void {
if (!props.player.canAfford(CONSTANTS.TravelCost)) {
dialogBoxCreate("You cannot afford to have this sleeve travel to another city");
}
props.sleeve.city = city as CityName;
props.player.loseMoney(CONSTANTS.TravelCost);
props.sleeve.resetTaskStatus();
removePopup(props.popupId);
props.rerender();
}
return (
<>
<p>
Have this sleeve travel to a different city. This affects the gyms and universities at which this sleeve can
study. Traveling to a different city costs <Money money={CONSTANTS.TravelCost} player={props.player} />. It will
also set your current sleeve task to idle.
</p>
{Settings.DisableASCIIArt ? (
Object.values(CityName).map((city: CityName) => (
<button key={city} className="std-button" onClick={() => travel(city)}>
{city}
</button>
))
) : (
<WorldMap currentCity={props.sleeve.city} onTravel={(city: CityName) => travel(city)} />
)}
</>
);
}

@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react";
import { use } from "../../ui/Context";
import { getAvailableCreatePrograms } from "../ProgramHelpers";
import { Box, Tooltip, Typography } from "@mui/material";
import { Tooltip, Typography } from "@mui/material";
import Button from "@mui/material/Button";
export function ProgramsRoot(): React.ReactElement {

@ -306,7 +306,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
{page === Page.Terminal ? (
<TerminalRoot terminal={terminal} router={Router} player={player} />
) : page === Page.Sleeves ? (
<SleeveRoot player={player} />
<SleeveRoot />
) : page === Page.Stats ? (
<CharacterStats />
) : page === Page.CreateScript ? (

@ -19,8 +19,8 @@ export function StatsTable({ rows, title, wide }: IProps): React.ReactElement {
{title && <Typography>{title}</Typography>}
<T size="small" padding="none">
<TableBody>
{rows.map((row: any[]) => (
<TableRow key={row[0]}>
{rows.map((row: any[], i: number) => (
<TableRow key={i}>
{row.map((elem: any, i: number) => (
<TableCell key={i} align={i !== 0 ? "right" : "left"}>
<Typography noWrap>{elem}</Typography>