100% mui I think

This commit is contained in:
Olivier Gagnon 2021-10-01 15:19:37 -04:00
parent 1e641468f7
commit 0ae8b72188
25 changed files with 652 additions and 863 deletions

@ -10,7 +10,6 @@ import { IPlayer } from "../../PersonObjects/IPlayer";
import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab";
import Box from "@mui/material/Box";
import Paper from "@mui/material/Paper";
interface IProps {
bladeburner: IBladeburner;

@ -38,7 +38,7 @@ export interface IGang {
getWantedPenalty(): number;
calculatePower(): number;
killMember(member: GangMember): void;
ascendMember(member: GangMember, workerScript: WorkerScript): IAscensionResult;
ascendMember(member: GangMember, workerScript?: WorkerScript): IAscensionResult;
getDiscount(): number;
getAllTaskNames(): string[];
getUpgradeCost(upg: GangMemberUpgrade): number;

@ -3,20 +3,23 @@
* ascension of a gang member.
*/
import React, { useState, useEffect } from "react";
import { Gang } from "../Gang";
import { GangMember } from "../GangMember";
import { numeralWrapper } from "../../ui/numeralFormat";
import { removePopup } from "../../ui/React/createPopup";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { Modal } from "../../ui/React/Modal";
import { useGang } from "./Context";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
interface IProps {
open: boolean;
onClose: () => void;
member: GangMember;
gang: Gang;
popupId: string;
onAscend: () => void;
}
export function AscensionPopup(props: IProps): React.ReactElement {
export function AscensionModal(props: IProps): React.ReactElement {
const gang = useGang();
const setRerender = useState(false)[1];
useEffect(() => {
@ -26,9 +29,9 @@ export function AscensionPopup(props: IProps): React.ReactElement {
function confirm(): void {
props.onAscend();
const res = props.gang.ascendMember(props.member);
const res = gang.ascendMember(props.member);
dialogBoxCreate(
<p>
<Typography>
You ascended {props.member.name}!<br />
<br />
Your gang lost {numeralWrapper.formatRespect(res.respect)} respect.
@ -48,13 +51,13 @@ export function AscensionPopup(props: IProps): React.ReactElement {
<br />
Charisma: x{numeralWrapper.format(res.cha, "0.000")}
<br />
</p>,
</Typography>,
);
removePopup(props.popupId);
props.onClose();
}
function cancel(): void {
removePopup(props.popupId);
props.onClose();
}
// const ascendBenefits = props.member.getAscensionResults();
@ -62,8 +65,8 @@ export function AscensionPopup(props: IProps): React.ReactElement {
const postAscend = props.member.getAscensionMultsAfterAscend();
return (
<>
<pre>
<Modal open={props.open} onClose={props.onClose}>
<Typography>
Are you sure you want to ascend this member? They will lose all of
<br />
their non-Augmentation upgrades and their stats will reset back to 1.
@ -92,13 +95,8 @@ export function AscensionPopup(props: IProps): React.ReactElement {
Charisma: x{numeralWrapper.format(preAscend.cha, "0.000")} =&gt; x
{numeralWrapper.format(postAscend.cha, "0.000")}
<br />
</pre>
<button className="std-button" onClick={confirm}>
Ascend
</button>
<button className="std-button" onClick={cancel}>
Cancel
</button>
</>
</Typography>
<Button onClick={confirm}>Ascend</Button>
</Modal>
);
}

@ -5,6 +5,9 @@ import * as React from "react";
import { Gang } from "../Gang";
import { CONSTANTS } from "../../Constants";
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box";
interface IProps {
gang: Gang;
@ -15,15 +18,17 @@ export function BonusTime(props: IProps): React.ReactElement {
if ((props.gang.storedCycles / CyclerPerSecond) * 1000 <= 5000) return <></>;
const bonusMillis = (props.gang.storedCycles / CyclerPerSecond) * 1000;
return (
<>
<p className="tooltip" style={{ display: "inline-block" }}>
Bonus time: {convertTimeMsToTimeElapsedString(bonusMillis)}
<span className="tooltiptext noselect">
You gain bonus time while offline or when the game is inactive (e.g. when the tab is throttled by the
browser). Bonus time makes the Gang mechanic progress faster, up to 5x the normal speed.
</span>
</p>
<br />
</>
<Box display="flex">
<Tooltip
title={
<Typography>
You gain bonus time while offline or when the game is inactive (e.g. when the tab is throttled by the
browser). Bonus time makes the Gang mechanic progress faster, up to 5x the normal speed.
</Typography>
}
>
<Typography>Bonus time: {convertTimeMsToTimeElapsedString(bonusMillis)}</Typography>
</Tooltip>
</Box>
);
}

10
src/Gang/ui/Context.ts Normal file

@ -0,0 +1,10 @@
import React, { useContext } from "react";
import { IGang } from "../IGang";
export const Context: {
Gang: React.Context<IGang>;
} = {
Gang: React.createContext<IGang>({} as IGang),
};
export const useGang = () => useContext(Context.Gang);

@ -0,0 +1,220 @@
/**
* React Component for the popup that manages gang members upgrades
*/
import React, { useState } from "react";
import { formatNumber } from "../../utils/StringHelperFunctions";
import { numeralWrapper } from "../../ui/numeralFormat";
import { GangMemberUpgrades } from "../GangMemberUpgrades";
import { GangMemberUpgrade } from "../GangMemberUpgrade";
import { Money } from "../../ui/React/Money";
import { useGang } from "./Context";
import { GangMember } from "../GangMember";
import { UpgradeType } from "../data/upgrades";
import { use } from "../../ui/Context";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box";
import Paper from "@mui/material/Paper";
import Grid from "@mui/material/Grid";
interface INextRevealProps {
upgrades: string[];
type: UpgradeType;
}
function NextReveal(props: INextRevealProps): React.ReactElement {
const gang = useGang();
const player = use.Player();
const upgrades = Object.keys(GangMemberUpgrades)
.filter((upgName: string) => {
const upg = GangMemberUpgrades[upgName];
if (player.money.gt(gang.getUpgradeCost(upg))) return false;
if (upg.type !== props.type) return false;
if (props.upgrades.includes(upgName)) return false;
return true;
})
.map((upgName: string) => GangMemberUpgrades[upgName]);
if (upgrades.length === 0) return <></>;
return (
<Typography>
Next at <Money money={upgrades[0].cost} />
</Typography>
);
}
function PurchasedUpgrade({ upgName }: { upgName: string }): React.ReactElement {
const upg = GangMemberUpgrades[upgName];
return (
<Paper sx={{ mx: 1, p: 1 }}>
<Box display="flex">
<Tooltip title={<Typography dangerouslySetInnerHTML={{ __html: upg.desc }} />}>
<Typography>{upg.name}</Typography>
</Tooltip>
</Box>
</Paper>
);
}
interface IUpgradeButtonProps {
upg: GangMemberUpgrade;
rerender: () => void;
member: GangMember;
}
function UpgradeButton(props: IUpgradeButtonProps): React.ReactElement {
const gang = useGang();
const player = use.Player();
function onClick(): void {
props.member.buyUpgrade(props.upg, player, gang);
props.rerender();
}
return (
<Tooltip title={<Typography dangerouslySetInnerHTML={{ __html: props.upg.desc }} />}>
<span>
<Typography>{props.upg.name}</Typography>
<Button onClick={onClick}>
<Money money={gang.getUpgradeCost(props.upg)} />
</Button>
</span>
</Tooltip>
);
}
interface IPanelProps {
member: GangMember;
}
function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {
const gang = useGang();
const player = use.Player();
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
function filterUpgrades(list: string[], type: UpgradeType): GangMemberUpgrade[] {
return Object.keys(GangMemberUpgrades)
.filter((upgName: string) => {
const upg = GangMemberUpgrades[upgName];
if (player.money.lt(gang.getUpgradeCost(upg))) return false;
if (upg.type !== type) return false;
if (list.includes(upgName)) return false;
return true;
})
.map((upgName: string) => GangMemberUpgrades[upgName]);
}
const weaponUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Weapon);
const armorUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Armor);
const vehicleUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Vehicle);
const rootkitUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Rootkit);
const augUpgrades = filterUpgrades(props.member.augmentations, UpgradeType.Augmentation);
const asc = {
hack: props.member.calculateAscensionMult(props.member.hack_asc_points),
str: props.member.calculateAscensionMult(props.member.str_asc_points),
def: props.member.calculateAscensionMult(props.member.def_asc_points),
dex: props.member.calculateAscensionMult(props.member.dex_asc_points),
agi: props.member.calculateAscensionMult(props.member.agi_asc_points),
cha: props.member.calculateAscensionMult(props.member.cha_asc_points),
};
return (
<Paper>
<Typography variant="h5" color="primary">
{props.member.name} ({props.member.task})
</Typography>
<Typography>
Hack: {props.member.hack} (x
{formatNumber(props.member.hack_mult * asc.hack, 2)})<br />
Str: {props.member.str} (x
{formatNumber(props.member.str_mult * asc.str, 2)})<br />
Def: {props.member.def} (x
{formatNumber(props.member.def_mult * asc.def, 2)})<br />
Dex: {props.member.dex} (x
{formatNumber(props.member.dex_mult * asc.dex, 2)})<br />
Agi: {props.member.agi} (x
{formatNumber(props.member.agi_mult * asc.agi, 2)})<br />
Cha: {props.member.cha} (x
{formatNumber(props.member.cha_mult * asc.cha, 2)})
</Typography>
<Box display="flex" flexWrap="wrap">
<Typography>Purchased Upgrades: </Typography>
<br />
{props.member.upgrades.map((upg: string) => (
<PurchasedUpgrade key={upg} upgName={upg} />
))}
{props.member.augmentations.map((upg: string) => (
<PurchasedUpgrade key={upg} upgName={upg} />
))}
</Box>
<Box display="flex" justifyContent="space-around">
<Box>
<Typography variant="h6" color="primary">
Weapons
</Typography>
{weaponUpgrades.map((upg) => (
<UpgradeButton key={upg.name} rerender={rerender} member={props.member} upg={upg} />
))}
<NextReveal type={UpgradeType.Weapon} upgrades={props.member.upgrades} />
</Box>
<Box>
<Typography variant="h6" color="primary">
Armor
</Typography>
{armorUpgrades.map((upg) => (
<UpgradeButton key={upg.name} rerender={rerender} member={props.member} upg={upg} />
))}
<NextReveal type={UpgradeType.Armor} upgrades={props.member.upgrades} />
</Box>
<Box>
<Typography variant="h6" color="primary">
Vehicles
</Typography>
{vehicleUpgrades.map((upg) => (
<UpgradeButton key={upg.name} rerender={rerender} member={props.member} upg={upg} />
))}
<NextReveal type={UpgradeType.Vehicle} upgrades={props.member.upgrades} />
</Box>
<Box>
<Typography variant="h6" color="primary">
Rootkits
</Typography>
{rootkitUpgrades.map((upg) => (
<UpgradeButton key={upg.name} rerender={rerender} member={props.member} upg={upg} />
))}
<NextReveal type={UpgradeType.Rootkit} upgrades={props.member.upgrades} />
</Box>
<Box>
<Typography variant="h6" color="primary">
Augmentations
</Typography>
{augUpgrades.map((upg) => (
<UpgradeButton key={upg.name} rerender={rerender} member={props.member} upg={upg} />
))}
<NextReveal type={UpgradeType.Augmentation} upgrades={props.member.upgrades} />
</Box>
</Box>
</Paper>
);
}
export function EquipmentsSubpage(): React.ReactElement {
const gang = useGang();
return (
<>
<Tooltip
title={
<Typography>
You get a discount on equipment and upgrades based on your gang's respect and power. More respect and power
leads to more discounts.
</Typography>
}
>
<Typography>Discount: -{numeralWrapper.formatPercentage(1 - 1 / gang.getDiscount())}</Typography>
</Tooltip>
{gang.members.map((member: GangMember) => (
<GangMemberUpgradePanel key={member.name} member={member} />
))}
</>
);
}

@ -1,23 +1,36 @@
/**
* React Component for a gang member on the management subpage.
*/
import React from "react";
import { Gang } from "../Gang";
import React, { useState } from "react";
import { GangMember } from "../GangMember";
import { BBAccordion } from "../../ui/React/BBAccordion";
import { GangMemberAccordionContent } from "./GangMemberAccordionContent";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemText from "@mui/material/ListItemText";
import Paper from "@mui/material/Paper";
import Collapse from "@mui/material/Collapse";
import ExpandMore from "@mui/icons-material/ExpandMore";
import ExpandLess from "@mui/icons-material/ExpandLess";
interface IProps {
gang: Gang;
member: GangMember;
}
export function GangMemberAccordion(props: IProps): React.ReactElement {
const [open, setOpen] = useState(true);
return (
<BBAccordion
panelInitiallyOpened={true}
headerContent={<>{props.member.name}</>}
panelContent={<GangMemberAccordionContent gang={props.gang} member={props.member} />}
/>
<Box component={Paper}>
<ListItemButton onClick={() => setOpen((old) => !old)}>
<ListItemText primary={<Typography>{props.member.name}</Typography>} />
{open ? <ExpandLess color="primary" /> : <ExpandMore color="primary" />}
</ListItemButton>
<Collapse in={open} unmountOnExit>
<Box sx={{ mx: 4 }}>
<GangMemberAccordionContent member={props.member} />
</Box>
</Collapse>
</Box>
);
}

@ -6,27 +6,26 @@ import React, { useState } from "react";
import { GangMemberStats } from "./GangMemberStats";
import { TaskSelector } from "./TaskSelector";
import { TaskDescription } from "./TaskDescription";
import { Gang } from "../Gang";
import { GangMember } from "../GangMember";
import Grid from "@mui/material/Grid";
interface IProps {
gang: Gang;
member: GangMember;
}
export function GangMemberAccordionContent(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
return (
<>
<div className={"gang-member-info-div tooltip"}>
<GangMemberStats onAscend={() => setRerender((old) => !old)} gang={props.gang} member={props.member} />
</div>
<div className={"gang-member-info-div"}>
<TaskSelector onTaskChange={() => setRerender((old) => !old)} gang={props.gang} member={props.member} />
</div>
<div className={"gang-member-info-div"}>
<Grid container>
<Grid item xs={4}>
<GangMemberStats onAscend={() => setRerender((old) => !old)} member={props.member} />
</Grid>
<Grid item xs={4}>
<TaskSelector onTaskChange={() => setRerender((old) => !old)} member={props.member} />
</Grid>
<Grid item xs={4}>
<TaskDescription member={props.member} />
</div>
</>
</Grid>
</Grid>
);
}

@ -2,59 +2,21 @@
* React Component for the list of gang members on the management subpage.
*/
import React, { useState } from "react";
import { GangMemberUpgradePopup } from "./GangMemberUpgradePopup";
import { GangMemberAccordion } from "./GangMemberAccordion";
import { createPopup } from "../../ui/React/createPopup";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Gang } from "../Gang";
import { GangMember } from "../GangMember";
import { RecruitButton } from "./RecruitButton";
import { useGang } from "./Context";
interface IProps {
gang: Gang;
player: IPlayer;
}
export function GangMemberList(props: IProps): React.ReactElement {
const [filter, setFilter] = useState("");
export function GangMemberList(): React.ReactElement {
const gang = useGang();
const setRerender = useState(false)[1];
function openUpgradePopup(): void {
const popupId = `gang-upgrade-popup`;
createPopup(popupId, GangMemberUpgradePopup, {
gang: props.gang,
player: props.player,
popupId: popupId,
});
}
function onFilterChange(event: React.ChangeEvent<HTMLInputElement>): void {
setFilter(event.target.value);
}
const members = props.gang.members.filter(
(member: GangMember) => member.name.indexOf(filter) > -1 || member.task.indexOf(filter) > -1,
);
return (
<>
<RecruitButton onRecruit={() => setRerender((old) => !old)} gang={props.gang} />
<br />
<input
className="text-input noselect"
placeholder="Filter gang member"
style={{ margin: "5px", padding: "5px" }}
value={filter}
onChange={onFilterChange}
/>
<a className="a-link-button" style={{ display: "inline-block" }} onClick={openUpgradePopup}>
Manage Equipment
</a>
<RecruitButton onRecruit={() => setRerender((old) => !old)} />
<ul>
{members.map((member: GangMember) => (
<li key={member.name}>
<GangMemberAccordion gang={props.gang} member={member} />
</li>
{gang.members.map((member: GangMember) => (
<GangMemberAccordion key={member.name} member={member} />
))}
</ul>
</>

@ -2,48 +2,26 @@
* React Component for the first part of a gang member details.
* Contains skills and exp.
*/
import React from "react";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
import React, { useState } from "react";
import { formatNumber } from "../../utils/StringHelperFunctions";
import { numeralWrapper } from "../../ui/numeralFormat";
import { createPopup } from "../../ui/React/createPopup";
import { Gang } from "../Gang";
import { GangMember } from "../GangMember";
import { AscensionPopup } from "./AscensionPopup";
import { AscensionModal } from "./AscensionModal";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
import Button from "@mui/material/Button";
import { StaticModal } from "../../ui/React/StaticModal";
import IconButton from "@mui/material/IconButton";
import HelpIcon from "@mui/icons-material/Help";
interface IProps {
member: GangMember;
gang: Gang;
onAscend: () => void;
}
export function GangMemberStats(props: IProps): React.ReactElement {
function ascend(): void {
const popupId = `gang-management-ascend-member ${props.member.name}`;
createPopup(popupId, AscensionPopup, {
member: props.member,
gang: props.gang,
popupId: popupId,
onAscend: props.onAscend,
});
}
function openAscensionHelp(): void {
dialogBoxCreate(
<>
Ascending a Gang Member resets the member's progress and stats in exchange for a permanent boost to their stat
multipliers.
<br />
<br />
The additional stat multiplier that the Gang Member gains upon ascension is based on the amount of exp they
have.
<br />
<br />
Upon ascension, the member will lose all of its non-Augmentation Equipment and your gang will lose respect equal
to the total respect earned by the member.
</>,
);
}
const [helpOpen, setHelpOpen] = useState(false);
const [ascendOpen, setAscendOpen] = useState(false);
const asc = {
hack: props.member.calculateAscensionMult(props.member.hack_asc_points),
@ -56,48 +34,77 @@ export function GangMemberStats(props: IProps): React.ReactElement {
return (
<>
<span className="tooltiptext smallfont">
Hk: x{numeralWrapper.formatMultiplier(props.member.hack_mult * asc.hack)}(x
{numeralWrapper.formatMultiplier(props.member.hack_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.hack)} Asc)
<br />
St: x{numeralWrapper.formatMultiplier(props.member.str_mult * asc.str)}
(x{numeralWrapper.formatMultiplier(props.member.str_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.str)} Asc)
<br />
Df: x{numeralWrapper.formatMultiplier(props.member.def_mult * asc.def)}
(x{numeralWrapper.formatMultiplier(props.member.def_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.def)} Asc)
<br />
Dx: x{numeralWrapper.formatMultiplier(props.member.dex_mult * asc.dex)}
(x{numeralWrapper.formatMultiplier(props.member.dex_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.dex)} Asc)
<br />
Ag: x{numeralWrapper.formatMultiplier(props.member.agi_mult * asc.agi)}
(x{numeralWrapper.formatMultiplier(props.member.agi_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.agi)} Asc)
<br />
Ch: x{numeralWrapper.formatMultiplier(props.member.cha_mult * asc.cha)}
(x{numeralWrapper.formatMultiplier(props.member.cha_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.cha)} Asc)
</span>
<pre>
Hacking: {formatNumber(props.member.hack, 0)} ({numeralWrapper.formatExp(props.member.hack_exp)} exp)
<br />
Strength: {formatNumber(props.member.str, 0)} ({numeralWrapper.formatExp(props.member.str_exp)} exp)
<br />
Defense: {formatNumber(props.member.def, 0)} ({numeralWrapper.formatExp(props.member.def_exp)} exp)
<br />
Dexterity: {formatNumber(props.member.dex, 0)} ({numeralWrapper.formatExp(props.member.dex_exp)} exp)
<br />
Agility: {formatNumber(props.member.agi, 0)} ({numeralWrapper.formatExp(props.member.agi_exp)} exp)
<br />
Charisma: {formatNumber(props.member.cha, 0)} ({numeralWrapper.formatExp(props.member.cha_exp)} exp)
<br />
</pre>
<Tooltip
title={
<Typography>
Hk: x{numeralWrapper.formatMultiplier(props.member.hack_mult * asc.hack)}(x
{numeralWrapper.formatMultiplier(props.member.hack_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.hack)}{" "}
Asc)
<br />
St: x{numeralWrapper.formatMultiplier(props.member.str_mult * asc.str)}
(x{numeralWrapper.formatMultiplier(props.member.str_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.str)}{" "}
Asc)
<br />
Df: x{numeralWrapper.formatMultiplier(props.member.def_mult * asc.def)}
(x{numeralWrapper.formatMultiplier(props.member.def_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.def)}{" "}
Asc)
<br />
Dx: x{numeralWrapper.formatMultiplier(props.member.dex_mult * asc.dex)}
(x{numeralWrapper.formatMultiplier(props.member.dex_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.dex)}{" "}
Asc)
<br />
Ag: x{numeralWrapper.formatMultiplier(props.member.agi_mult * asc.agi)}
(x{numeralWrapper.formatMultiplier(props.member.agi_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.agi)}{" "}
Asc)
<br />
Ch: x{numeralWrapper.formatMultiplier(props.member.cha_mult * asc.cha)}
(x{numeralWrapper.formatMultiplier(props.member.cha_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.cha)}{" "}
Asc)
</Typography>
}
>
<Typography>
Hacking: {formatNumber(props.member.hack, 0)} ({numeralWrapper.formatExp(props.member.hack_exp)} exp)
<br />
Strength: {formatNumber(props.member.str, 0)} ({numeralWrapper.formatExp(props.member.str_exp)} exp)
<br />
Defense: {formatNumber(props.member.def, 0)} ({numeralWrapper.formatExp(props.member.def_exp)} exp)
<br />
Dexterity: {formatNumber(props.member.dex, 0)} ({numeralWrapper.formatExp(props.member.dex_exp)} exp)
<br />
Agility: {formatNumber(props.member.agi, 0)} ({numeralWrapper.formatExp(props.member.agi_exp)} exp)
<br />
Charisma: {formatNumber(props.member.cha, 0)} ({numeralWrapper.formatExp(props.member.cha_exp)} exp)
<br />
</Typography>
</Tooltip>
<br />
{props.member.canAscend() && (
<>
<button className="accordion-button noselect" onClick={ascend}>
Ascend
</button>
<div className="help-tip noselect" style={{ marginTop: "5px" }} onClick={openAscensionHelp}>
?
</div>
<Button onClick={() => setAscendOpen(true)}>Ascend</Button>
<AscensionModal
open={ascendOpen}
onClose={() => setAscendOpen(false)}
member={props.member}
onAscend={props.onAscend}
/>
<IconButton onClick={() => setHelpOpen(true)}>
<HelpIcon />
</IconButton>
<StaticModal open={helpOpen} onClose={() => setHelpOpen(false)}>
<Typography>
Ascending a Gang Member resets the member's progress and stats in exchange for a permanent boost to their
stat multipliers.
<br />
<br />
The additional stat multiplier that the Gang Member gains upon ascension is based on the amount of exp
they have.
<br />
<br />
Upon ascension, the member will lose all of its non-Augmentation Equipment and your gang will lose respect
equal to the total respect earned by the member.
</Typography>
</StaticModal>
</>
)}
</>

@ -1,224 +0,0 @@
/**
* React Component for the popup that manages gang members upgrades
*/
import React, { useState, useEffect } from "react";
import { formatNumber } from "../../utils/StringHelperFunctions";
import { numeralWrapper } from "../../ui/numeralFormat";
import { GangMemberUpgrades } from "../GangMemberUpgrades";
import { GangMemberUpgrade } from "../GangMemberUpgrade";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Money } from "../../ui/React/Money";
import { removePopup } from "../../ui/React/createPopup";
import { GangMember } from "../GangMember";
import { Gang } from "../Gang";
import { UpgradeType } from "../data/upgrades";
interface INextRevealProps {
gang: Gang;
upgrades: string[];
type: UpgradeType;
player: IPlayer;
}
function NextReveal(props: INextRevealProps): React.ReactElement {
const upgrades = Object.keys(GangMemberUpgrades)
.filter((upgName: string) => {
const upg = GangMemberUpgrades[upgName];
if (props.player.money.gt(props.gang.getUpgradeCost(upg))) return false;
if (upg.type !== props.type) return false;
if (props.upgrades.includes(upgName)) return false;
return true;
})
.map((upgName: string) => GangMemberUpgrades[upgName]);
if (upgrades.length === 0) return <></>;
return (
<p>
Next at <Money money={upgrades[0].cost} />
</p>
);
}
interface IPanelProps {
member: GangMember;
gang: Gang;
player: IPlayer;
}
function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {
const setRerender = useState(false)[1];
function filterUpgrades(list: string[], type: UpgradeType): GangMemberUpgrade[] {
return Object.keys(GangMemberUpgrades)
.filter((upgName: string) => {
const upg = GangMemberUpgrades[upgName];
if (props.player.money.lt(props.gang.getUpgradeCost(upg))) return false;
if (upg.type !== type) return false;
if (list.includes(upgName)) return false;
return true;
})
.map((upgName: string) => GangMemberUpgrades[upgName]);
}
const weaponUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Weapon);
const armorUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Armor);
const vehicleUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Vehicle);
const rootkitUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Rootkit);
const augUpgrades = filterUpgrades(props.member.augmentations, UpgradeType.Augmentation);
function purchasedUpgrade(upgName: string): React.ReactElement {
const upg = GangMemberUpgrades[upgName];
return (
<div key={upgName} className="gang-owned-upgrade tooltip">
{upg.name}
<span className="tooltiptext" dangerouslySetInnerHTML={{ __html: upg.desc }} />
</div>
);
}
function upgradeButton(upg: GangMemberUpgrade, left = false): React.ReactElement {
function onClick(): void {
props.member.buyUpgrade(upg, props.player, props.gang);
setRerender((old) => !old);
}
return (
<a
key={upg.name}
className="a-link-button tooltip"
style={{
margin: "2px",
padding: "2px",
display: "block",
fontSize: "11px",
}}
onClick={onClick}
>
{upg.name} - <Money money={props.gang.getUpgradeCost(upg)} player={props.player} />
<span className={left ? "tooltiptextleft" : "tooltiptext"} dangerouslySetInnerHTML={{ __html: upg.desc }} />
</a>
);
}
const asc = {
hack: props.member.calculateAscensionMult(props.member.hack_asc_points),
str: props.member.calculateAscensionMult(props.member.str_asc_points),
def: props.member.calculateAscensionMult(props.member.def_asc_points),
dex: props.member.calculateAscensionMult(props.member.dex_asc_points),
agi: props.member.calculateAscensionMult(props.member.agi_asc_points),
cha: props.member.calculateAscensionMult(props.member.cha_asc_points),
};
return (
<div style={{ border: "1px solid white" }}>
<h1>
{props.member.name}({props.member.task})
</h1>
<pre style={{ fontSize: "14px", display: "inline-block", width: "20%" }}>
Hack: {props.member.hack} (x
{formatNumber(props.member.hack_mult * asc.hack, 2)})<br />
Str: {props.member.str} (x
{formatNumber(props.member.str_mult * asc.str, 2)})<br />
Def: {props.member.def} (x
{formatNumber(props.member.def_mult * asc.def, 2)})<br />
Dex: {props.member.dex} (x
{formatNumber(props.member.dex_mult * asc.dex, 2)})<br />
Agi: {props.member.agi} (x
{formatNumber(props.member.agi_mult * asc.agi, 2)})<br />
Cha: {props.member.cha} (x
{formatNumber(props.member.cha_mult * asc.cha, 2)})
</pre>
<div className="gang-owned-upgrades-div noselect">
Purchased Upgrades: {props.member.upgrades.map((upg: string) => purchasedUpgrade(upg))}
{props.member.augmentations.map((upg: string) => purchasedUpgrade(upg))}
</div>
<div className="noselect" style={{ width: "20%", display: "inline-block" }}>
<h2>Weapons</h2>
{weaponUpgrades.map((upg) => upgradeButton(upg))}
<NextReveal
gang={props.gang}
type={UpgradeType.Weapon}
player={props.player}
upgrades={props.member.upgrades}
/>
</div>
<div className="noselect" style={{ width: "20%", display: "inline-block" }}>
<h2>Armor</h2>
{armorUpgrades.map((upg) => upgradeButton(upg))}
<NextReveal gang={props.gang} type={UpgradeType.Armor} player={props.player} upgrades={props.member.upgrades} />
</div>
<div className="noselect" style={{ width: "20%", display: "inline-block" }}>
<h2>Vehicles</h2>
{vehicleUpgrades.map((upg) => upgradeButton(upg))}
<NextReveal
gang={props.gang}
type={UpgradeType.Vehicle}
player={props.player}
upgrades={props.member.upgrades}
/>
</div>
<div className="noselect" style={{ width: "20%", display: "inline-block" }}>
<h2>Rootkits</h2>
{rootkitUpgrades.map((upg) => upgradeButton(upg, true))}
<NextReveal
gang={props.gang}
type={UpgradeType.Rootkit}
player={props.player}
upgrades={props.member.upgrades}
/>
</div>
<div className="noselect" style={{ width: "20%", display: "inline-block" }}>
<h2>Augmentations</h2>
{augUpgrades.map((upg) => upgradeButton(upg, true))}
<NextReveal
gang={props.gang}
type={UpgradeType.Augmentation}
player={props.player}
upgrades={props.member.upgrades}
/>
</div>
</div>
);
}
interface IProps {
gang: Gang;
player: IPlayer;
popupId: string;
}
export function GangMemberUpgradePopup(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
const [filter, setFilter] = useState("");
function closePopup(this: Window, ev: KeyboardEvent): void {
if (ev.keyCode !== 27) return;
removePopup(props.popupId);
}
useEffect(() => {
window.addEventListener<"keydown">("keydown", closePopup);
const id = setInterval(() => setRerender((old) => !old), 1000);
return () => {
clearInterval(id);
window.removeEventListener<"keydown">("keydown", closePopup);
};
}, []);
return (
<>
<input
className="text-input noselect"
value={filter}
placeholder="Filter gang member"
onChange={(event) => setFilter(event.target.value)}
/>
<p className="tooltip" style={{ marginLeft: "6px", display: "inline-block" }}>
Discount: -{numeralWrapper.formatPercentage(1 - 1 / props.gang.getDiscount())}
<span className="tooltiptext noselect">
You get a discount on equipment and upgrades based on your gang's respect and power. More respect and power
leads to more discounts.
</span>
</p>
{props.gang.members.map((member: GangMember) => (
<GangMemberUpgradePanel key={member.name} player={props.player} gang={props.gang} member={member} />
))}
</>
);
}

@ -4,48 +4,49 @@
import React, { useState, useEffect } from "react";
import { ManagementSubpage } from "./ManagementSubpage";
import { TerritorySubpage } from "./TerritorySubpage";
import { EquipmentsSubpage } from "./EquipmentsSubpage";
import { use } from "../../ui/Context";
import { Factions } from "../../Faction/Factions";
import { Context } from "./Context";
import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab";
enum Page {
Management,
Equipment,
Territory,
}
export function GangRoot(): React.ReactElement {
const player = use.Player();
const router = use.Router();
const gang = (function () {
if (player.gang === null) throw new Error("Gang should not be null");
return player.gang;
})();
const [management, setManagement] = useState(true);
const [value, setValue] = React.useState(0);
function handleChange(event: React.SyntheticEvent, tab: number): void {
setValue(tab);
}
const setRerender = useState(false)[1];
useEffect(() => {
const id = setInterval(() => setRerender((old) => !old), 1000);
const id = setInterval(() => setRerender((old) => !old), 200);
return () => clearInterval(id);
}, []);
function back(): void {
router.toFaction(Factions[gang.facName]);
}
return (
<div className="gang-container">
<a className="a-link-button" style={{ display: "inline-block" }} onClick={back}>
Back
</a>
<a
className={management ? "a-link-button-inactive" : "a-link-button"}
style={{ display: "inline-block" }}
onClick={() => setManagement(true)}
>
Gang Management
</a>
<a
className={!management ? "a-link-button-inactive" : "a-link-button"}
style={{ display: "inline-block" }}
onClick={() => setManagement(false)}
>
Gang Territory
</a>
{management ? <ManagementSubpage gang={gang} player={player} /> : <TerritorySubpage gang={gang} />}
</div>
<Context.Gang.Provider value={gang}>
<Tabs variant="fullWidth" value={value} onChange={handleChange}>
<Tab label="Management" />
<Tab label="Equipment" />
<Tab label="Territory" />
</Tabs>
{value === 0 && <ManagementSubpage />}
{value === 1 && <EquipmentsSubpage />}
{value === 2 && <TerritorySubpage />}
</Context.Gang.Provider>
);
}

@ -4,7 +4,6 @@
*/
import React from "react";
import { Factions } from "../../Faction/Factions";
import { Gang } from "../Gang";
import { formatNumber } from "../../utils/StringHelperFunctions";
import { numeralWrapper } from "../../ui/numeralFormat";
@ -12,13 +11,14 @@ import { MoneyRate } from "../../ui/React/MoneyRate";
import { Reputation } from "../../ui/React/Reputation";
import { AllGangs } from "../AllGangs";
import { BonusTime } from "./BonusTime";
import { useGang } from "./Context";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box";
interface IProps {
gang: Gang;
}
export function GangStats(props: IProps): React.ReactElement {
const territoryMult = AllGangs[props.gang.facName].territory * 100;
export function GangStats(): React.ReactElement {
const gang = useGang();
const territoryMult = AllGangs[gang.facName].territory * 100;
let territoryStr;
if (territoryMult <= 0) {
territoryStr = formatNumber(0, 2);
@ -30,46 +30,59 @@ export function GangStats(props: IProps): React.ReactElement {
return (
<>
<p className="tooltip" style={{ display: "inline-block" }}>
Respect: {numeralWrapper.formatRespect(props.gang.respect)} (
{numeralWrapper.formatRespect(5 * props.gang.respectGainRate)} / sec)
<span className="tooltiptext">
Represents the amount of respect your gang has from other gangs and criminal organizations. Your respect
affects the amount of money your gang members will earn, and also determines how much reputation you are
earning with your gang's corresponding Faction.
</span>
</p>
<br />
<p className="tooltip" style={{ display: "inline-block" }}>
Wanted Level: {numeralWrapper.formatWanted(props.gang.wanted)} (
{numeralWrapper.formatWanted(5 * props.gang.wantedGainRate)} / sec)
<span className="tooltiptext">
Represents how much the gang is wanted by law enforcement. The higher your gang's wanted level, the harder it
will be for your gang members to make money and earn respect. Note that the minimum wanted level is 1.
</span>
</p>
<br />
<p className="tooltip" style={{ display: "inline-block" }}>
Wanted Level Penalty: -{formatNumber((1 - props.gang.getWantedPenalty()) * 100, 2)}%
<span className="tooltiptext">Penalty for respect and money gain rates due to Wanted Level</span>
</p>
<br />
<div>
<p style={{ display: "inline-block" }}>
Money gain rate: <MoneyRate money={5 * props.gang.moneyGainRate} />
</p>
</div>
<br />
<p className="tooltip" style={{ display: "inline-block" }}>
Territory: {territoryStr}%
<span className="tooltiptext">The percentage of total territory your Gang controls</span>
</p>
<br />
<p style={{ display: "inline-block" }}>
Faction reputation: <Reputation reputation={Factions[props.gang.facName].playerReputation} />
</p>
<br />
<BonusTime gang={props.gang} />
<Box display="flex">
<Tooltip
title={
<Typography>
Represents the amount of respect your gang has from other gangs and criminal organizations. Your respect
affects the amount of money your gang members will earn, and also determines how much reputation you are
earning with your gang's corresponding Faction.
</Typography>
}
>
<Typography>
Respect: {numeralWrapper.formatRespect(gang.respect)} (
{numeralWrapper.formatRespect(5 * gang.respectGainRate)} / sec)
</Typography>
</Tooltip>
</Box>
<Box display="flex">
<Tooltip
title={
<Typography>
Represents how much the gang is wanted by law enforcement. The higher your gang's wanted level, the harder
it will be for your gang members to make money and earn respect. Note that the minimum wanted level is 1.
</Typography>
}
>
<Typography>
Wanted Level: {numeralWrapper.formatWanted(gang.wanted)} (
{numeralWrapper.formatWanted(5 * gang.wantedGainRate)} / sec)
</Typography>
</Tooltip>
</Box>
<Box display="flex">
<Tooltip title={<Typography>Penalty for respect and money gain rates due to Wanted Level</Typography>}>
<Typography>Wanted Level Penalty: -{formatNumber((1 - gang.getWantedPenalty()) * 100, 2)}%</Typography>
</Tooltip>
</Box>
<Typography>
Money gain rate: <MoneyRate money={5 * gang.moneyGainRate} />
</Typography>
<Box display="flex">
<Tooltip title={<Typography>The percentage of total territory your Gang controls</Typography>}>
<Typography>Territory: {territoryStr}%</Typography>
</Tooltip>
</Box>
<Typography>
Faction reputation: <Reputation reputation={Factions[gang.facName].playerReputation} />
</Typography>
<BonusTime gang={gang} />
</>
);
}

@ -2,20 +2,16 @@
* React Component for the subpage that manages gang members, the main page.
*/
import React from "react";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { GangStats } from "./GangStats";
import { Gang } from "../Gang";
import { GangMemberList } from "./GangMemberList";
import { useGang } from "./Context";
import Typography from "@mui/material/Typography";
interface IProps {
gang: Gang;
player: IPlayer;
}
export function ManagementSubpage(props: IProps): React.ReactElement {
export function ManagementSubpage(): React.ReactElement {
const gang = useGang();
return (
<div style={{ display: "block" }}>
<p className="noselect" style={{ width: "70%" }}>
<>
<Typography>
This page is used to manage your gang members and get an overview of your gang's stats.
<br />
<br />
@ -23,7 +19,7 @@ export function ManagementSubpage(props: IProps): React.ReactElement {
too difficult. Consider training that member's stats or choosing an easier task. The tasks closer to the top of
the dropdown list are generally easier. Alternatively, the gang member's low production might be due to the fact
that your wanted level is too high. Consider assigning a few members to the '
{props.gang.isHackingGang ? "Ethical Hacking" : "Vigilante Justice"}' task to lower your wanted level.
{gang.isHackingGang ? "Ethical Hacking" : "Vigilante Justice"}' task to lower your wanted level.
<br />
<br />
Installing Augmentations does NOT reset your progress with your Gang. Furthermore, after installing
@ -31,11 +27,11 @@ export function ManagementSubpage(props: IProps): React.ReactElement {
<br />
<br />
You can also manage your gang programmatically through Netscript using the Gang API
</p>
</Typography>
<br />
<GangStats gang={props.gang} />
<GangStats />
<br />
<GangMemberList gang={props.gang} player={props.player} />
</div>
<GangMemberList />
</>
);
}

@ -1,51 +1,42 @@
/**
* React Component for the recruitment button and text on the gang main page.
*/
import React from "react";
import { Gang } from "../Gang";
import { RecruitPopup } from "./RecruitPopup";
import React, { useState } from "react";
import { RecruitModal } from "./RecruitModal";
import { GangConstants } from "../data/Constants";
import { formatNumber } from "../../utils/StringHelperFunctions";
import { createPopup } from "../../ui/React/createPopup";
import { useGang } from "./Context";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import Box from "@mui/material/Box";
interface IProps {
gang: Gang;
onRecruit: () => void;
}
export function RecruitButton(props: IProps): React.ReactElement {
if (props.gang.members.length >= GangConstants.MaximumGangMembers) {
const gang = useGang();
const [open, setOpen] = useState(false);
if (gang.members.length >= GangConstants.MaximumGangMembers) {
return <></>;
}
if (!props.gang.canRecruitMember()) {
const respect = props.gang.getRespectNeededToRecruitMember();
if (!gang.canRecruitMember()) {
const respect = gang.getRespectNeededToRecruitMember();
return (
<>
<a className="a-link-button-inactive" style={{ display: "inline-block", margin: "10px" }}>
<Box display="flex" alignItems="center">
<Button sx={{ mx: 1 }} disabled>
Recruit Gang Member
</a>
<p style={{ margin: "10px", color: "red", display: "inline-block" }}>
{formatNumber(respect, 2)} respect needed to recruit next member
</p>
</>
</Button>
<Typography>{formatNumber(respect, 2)} respect needed to recruit next member</Typography>
</Box>
);
}
function onClick(): void {
const popupId = "recruit-gang-member-popup";
createPopup(popupId, RecruitPopup, {
gang: props.gang,
popupId: popupId,
onRecruit: props.onRecruit,
});
}
return (
<>
<a className="a-link-button" onClick={onClick} style={{ display: "inline-block", margin: "10px" }}>
Recruit Gang Member
</a>
<Button onClick={() => setOpen(true)}>Recruit Gang Member</Button>
<RecruitModal open={open} onClose={() => setOpen(false)} onRecruit={props.onRecruit} />
</>
);
}

@ -2,38 +2,35 @@
* React Component for the popup used to recruit new gang members.
*/
import React, { useState } from "react";
import { Gang } from "../Gang";
import { removePopup } from "../../ui/React/createPopup";
import { Modal } from "../../ui/React/Modal";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { useGang } from "./Context";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
interface IRecruitPopupProps {
gang: Gang;
popupId: string;
open: boolean;
onClose: () => void;
onRecruit: () => void;
}
export function RecruitPopup(props: IRecruitPopupProps): React.ReactElement {
export function RecruitModal(props: IRecruitPopupProps): React.ReactElement {
const gang = useGang();
const [name, setName] = useState("");
const disabled = name === "" || !gang.canRecruitMember();
function recruit(): void {
if (name === "") {
dialogBoxCreate("You must enter a name for your Gang member!");
return;
}
if (!props.gang.canRecruitMember()) {
dialogBoxCreate("You cannot recruit another Gang member!");
return;
}
if (disabled) return;
// At this point, the only way this can fail is if you already
// have a gang member with the same name
if (!props.gang.recruitMember(name)) {
if (!gang.recruitMember(name)) {
dialogBoxCreate("You already have a gang member with this name!");
return;
}
props.onRecruit();
removePopup(props.popupId);
props.onClose();
}
function onKeyUp(event: React.KeyboardEvent<HTMLInputElement>): void {
@ -45,20 +42,23 @@ export function RecruitPopup(props: IRecruitPopupProps): React.ReactElement {
}
return (
<>
<p className="noselect">Enter a name for your new Gang member:</p>
<Modal open={props.open} onClose={props.onClose}>
<Typography>Enter a name for your new Gang member:</Typography>
<br />
<input
<TextField
autoFocus
onKeyUp={onKeyUp}
onChange={onChange}
className="text-input noselect"
type="text"
placeholder="unique name"
InputProps={{
endAdornment: (
<Button disabled={disabled} onClick={recruit}>
Recruit
</Button>
),
}}
/>
<a className="std-button" onClick={recruit}>
Recruit Gang Member
</a>
</>
</Modal>
);
}

@ -5,6 +5,7 @@
import React from "react";
import { GangMemberTasks } from "../GangMemberTasks";
import { GangMember } from "../GangMember";
import Typography from "@mui/material/Typography";
interface IProps {
member: GangMember;
@ -14,5 +15,5 @@ export function TaskDescription(props: IProps): React.ReactElement {
const task = GangMemberTasks[props.member.task];
const desc = task ? task.desc : GangMemberTasks["Unassigned"].desc;
return <p className="inline noselect" dangerouslySetInnerHTML={{ __html: desc }} />;
return <Typography dangerouslySetInnerHTML={{ __html: desc }} />;
}

@ -6,49 +6,50 @@ import React, { useState } from "react";
import { numeralWrapper } from "../../ui/numeralFormat";
import { StatsTable } from "../../ui/React/StatsTable";
import { MoneyRate } from "../../ui/React/MoneyRate";
import { Gang } from "../Gang";
import { useGang } from "./Context";
import { GangMember } from "../GangMember";
import MenuItem from "@mui/material/MenuItem";
import Select, { SelectChangeEvent } from "@mui/material/Select";
interface IProps {
member: GangMember;
gang: Gang;
onTaskChange: () => void;
}
export function TaskSelector(props: IProps): React.ReactElement {
const gang = useGang();
const [currentTask, setCurrentTask] = useState(props.member.task);
function onChange(event: React.ChangeEvent<HTMLSelectElement>): void {
function onChange(event: SelectChangeEvent<string>): void {
const task = event.target.value;
props.member.assignToTask(task);
setCurrentTask(task);
props.onTaskChange();
}
const tasks = props.gang.getAllTaskNames();
const tasks = gang.getAllTaskNames();
const data = [
[`Money:`, <MoneyRate money={5 * props.member.calculateMoneyGain(props.gang)} />],
[`Respect:`, `${numeralWrapper.formatRespect(5 * props.member.calculateRespectGain(props.gang))} / sec`],
[`Wanted Level:`, `${numeralWrapper.formatWanted(5 * props.member.calculateWantedLevelGain(props.gang))} / sec`],
[`Money:`, <MoneyRate money={5 * props.member.calculateMoneyGain(gang)} />],
[`Respect:`, `${numeralWrapper.formatRespect(5 * props.member.calculateRespectGain(gang))} / sec`],
[`Wanted Level:`, `${numeralWrapper.formatWanted(5 * props.member.calculateWantedLevelGain(gang))} / sec`],
[`Total Respect:`, `${numeralWrapper.formatRespect(props.member.earnedRespect)}`],
];
return (
<>
<select onChange={onChange} className="dropdown noselect" value={currentTask}>
<option key={0} value={"---"}>
---
</option>
<Select onChange={onChange} value={currentTask}>
<MenuItem key={0} value={"Unassigned"}>
Unassigned
</MenuItem>
{tasks.map((task: string, i: number) => (
<option key={i + 1} value={task}>
<MenuItem key={i + 1} value={task}>
{task}
</option>
</MenuItem>
))}
</select>
<div>
<StatsTable rows={data} />
</div>
</Select>
<StatsTable rows={data} />
</>
);
}

@ -3,59 +3,25 @@
*/
import React from "react";
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { formatNumber } from "../../utils/StringHelperFunctions";
import { AllGangs } from "../AllGangs";
import { Gang } from "../Gang";
import { useGang } from "./Context";
interface IProps {
gang: Gang;
}
import Typography from "@mui/material/Typography";
import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch";
import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box";
import Paper from "@mui/material/Paper";
export function TerritorySubpage(props: IProps): React.ReactElement {
function openWarfareHelp(): void {
dialogBoxCreate(
"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.",
);
}
function formatTerritory(n: number): string {
const v = n * 100;
if (v <= 0) {
return formatNumber(0, 2);
} else if (v >= 100) {
return formatNumber(100, 2);
} else {
return formatNumber(v, 2);
}
}
const playerPower = AllGangs[props.gang.facName].power;
function otherGangTerritory(name: string): React.ReactElement {
const power = AllGangs[name].power;
const clashVictoryChance = playerPower / (power + playerPower);
return (
<span key={name}>
<u>{name}</u>
<br />
Power: {formatNumber(power, 6)}
<br />
Territory: {formatTerritory(AllGangs[name].territory)}%<br />
Chance to win clash with this gang: {numeralWrapper.formatPercentage(clashVictoryChance, 3)}
<br />
<br />
</span>
);
}
const gangNames = Object.keys(AllGangs).filter((g) => g != props.gang.facName);
export function TerritorySubpage(): React.ReactElement {
const gang = useGang();
const gangNames = Object.keys(AllGangs).filter((g) => g != gang.facName);
return (
<div style={{ width: "70%" }}>
<p className="noselect">
<>
<Typography>
This page shows how much territory your Gang controls. This statistic is listed as a percentage, which
represents how much of the total territory you control.
<br />
@ -76,59 +42,113 @@ export function TerritorySubpage(props: IProps): React.ReactElement {
and wanted level. It is very beneficial to have high territory control.
<br />
<br />
</p>
<input
checked={props.gang.territoryWarfareEngaged}
id="warfare"
type="checkbox"
style={{ display: "inline-block", margin: "2px" }}
onChange={(event) => (props.gang.territoryWarfareEngaged = event.target.checked)}
</Typography>
<FormControlLabel
control={
<Switch
checked={gang.territoryWarfareEngaged}
onChange={(event) => (gang.territoryWarfareEngaged = event.target.checked)}
/>
}
label={
<Tooltip
title={
<Typography>
Engaging in Territory Warfare sets your clash chance to 100%. Disengaging will cause your clash chance
to gradually decrease until it reaches 0%.
</Typography>
}
>
<Typography>Engage in Territory Warfare</Typography>
</Tooltip>
}
/>
<label htmlFor="warfare" className="tooltip noselect" style={{ color: "white", display: "inline-block" }}>
Engage in Territory Warfare
<span className="tooltiptext" style={{ display: "inline-block" }}>
Engaging in Territory Warfare sets your clash chance to 100%. Disengaging will cause your clash chance to
gradually decrease until it reaches 0%.
</span>
</label>
<br />
<p style={{ display: "inline-block" }}>
Territory Clash Chance: {numeralWrapper.formatPercentage(props.gang.territoryClashChance, 3)}
</p>
<div className="help-tip noselect" style={{ display: "inline-block" }} onClick={openWarfareHelp}>
?
</div>
<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 />
<input
checked={props.gang.notifyMemberDeath}
id="notify"
type="checkbox"
style={{ display: "inline-block", margin: "2px" }}
onChange={(event) => (props.gang.notifyMemberDeath = event.target.checked)}
<FormControlLabel
control={
<Switch
checked={gang.notifyMemberDeath}
onChange={(event) => (gang.notifyMemberDeath = event.target.checked)}
/>
}
label={
<Tooltip
title={
<Typography>
If this is enabled, then you will receive a pop-up notifying you whenever one of your Gang Members dies
in a territory clash.
</Typography>
}
>
<Typography>Notify about Gang Member Deaths</Typography>
</Tooltip>
}
/>
<label htmlFor="warfare" className="tooltip noselect" style={{ color: "white", display: "inline-block" }}>
Notify about Gang Member Deaths
<span className="tooltiptext" style={{ display: "inline-block" }}>
If this is enabled, then you will receive a pop-up notifying you whenever one of your Gang Members dies in a
territory clash.
</span>
</label>
<br />
<fieldset style={{ display: "block", margin: "6px" }}>
<p>
<Paper>
<Typography>
<b>
<u>{props.gang.facName}</u>
<u>{gang.facName}</u>
</b>
<br />
Power: {formatNumber(AllGangs[props.gang.facName].power, 6)}
Power: {formatNumber(AllGangs[gang.facName].power, 6)}
<br />
Territory: {formatTerritory(AllGangs[props.gang.facName].territory)}%
Territory: {formatTerritory(AllGangs[gang.facName].territory)}%
<br />
<br />
{gangNames.map((name) => otherGangTerritory(name))}
</p>
</fieldset>
</div>
</Typography>
{gangNames.map((name) => (
<OtherGangTerritory key={name} name={name} />
))}
</Paper>
</>
);
}
function formatTerritory(n: number): string {
const v = n * 100;
if (v <= 0) {
return formatNumber(0, 2);
} else if (v >= 100) {
return formatNumber(100, 2);
} else {
return formatNumber(v, 2);
}
}
interface ITerritoryProps {
name: string;
}
function OtherGangTerritory(props: ITerritoryProps): React.ReactElement {
const gang = useGang();
const playerPower = AllGangs[gang.facName].power;
const power = AllGangs[props.name].power;
const clashVictoryChance = playerPower / (power + playerPower);
return (
<Typography>
<u>{props.name}</u>
<br />
Power: {formatNumber(power, 6)}
<br />
Territory: {formatTerritory(AllGangs[props.name].territory)}%<br />
Chance to win clash with this gang: {numeralWrapper.formatPercentage(clashVictoryChance, 3)}
<br />
<br />
</Typography>
);
}

@ -6,7 +6,6 @@ import { purchaseServer } from "../../Server/ServerPurchases";
import { numeralWrapper } from "../../ui/numeralFormat";
import { Money } from "../../ui/React/Money";
import { Modal } from "../../ui/React/Modal";
import { StdButton } from "../../ui/React/StdButton";
import { use } from "../../ui/Context";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";

@ -1,84 +0,0 @@
/**
* React component to create an accordion element
*/
import * as React from "react";
type IProps = {
headerClass?: string; // Override default class
headerContent: React.ReactElement;
panelClass?: string; // Override default class
panelContent: React.ReactElement;
panelInitiallyOpened?: boolean;
style?: string;
};
type IState = {
panelOpened: boolean;
};
export class BBAccordion extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.handleHeaderClick = this.handleHeaderClick.bind(this);
this.state = {
panelOpened: props.panelInitiallyOpened ? props.panelInitiallyOpened : false,
};
}
handleHeaderClick(): void {
this.setState({
panelOpened: !this.state.panelOpened,
});
}
render(): React.ReactNode {
let className = "accordion-header";
if (typeof this.props.headerClass === "string") {
className = this.props.headerClass;
}
if (this.state.panelOpened) className += " active";
return (
<>
<button className={className} onClick={this.handleHeaderClick}>
{this.props.headerContent}
</button>
<AccordionPanel
opened={this.state.panelOpened}
panelClass={this.props.panelClass}
panelContent={this.props.panelContent}
/>
</>
);
}
}
type IPanelProps = {
opened: boolean;
panelClass?: string; // Override default class
panelContent: React.ReactElement;
};
class AccordionPanel extends React.Component<IPanelProps, any> {
shouldComponentUpdate(nextProps: IPanelProps): boolean {
return this.props.opened || nextProps.opened;
}
render(): React.ReactNode {
let className = "accordion-panel";
if (typeof this.props.panelClass === "string") {
className = this.props.panelClass;
}
if (!this.props.opened) return <></>;
return (
<div className={className} style={{ display: "block" }}>
{this.props.panelContent}
</div>
);
}
}

@ -1,46 +0,0 @@
/**
* Wrapper around material-ui's Button component that styles it with
* Bitburner's UI theme
*/
import React from "react";
import { Button, ButtonProps } from "@mui/material";
import makeStyles from "@mui/styles/makeStyles";
const useStyles = makeStyles({
// Tries to emulate StdButton in buttons.scss
root: {
backgroundColor: "#555",
border: "1px solid #333",
color: "white",
margin: "5px",
padding: "3px 5px",
"&:hover": {
backgroundColor: "#666",
},
borderRadius: 0,
},
textPrimary: {
color: "rgb( 144, 202, 249)",
},
textSecondary: {
color: "rgb(244, 143, 177)",
},
disabled: {
backgroundColor: "#333",
color: "#fff",
cursor: "default",
},
});
export const MuiButton: React.FC<ButtonProps> = (props: ButtonProps) => {
return (
<Button
{...props}
classes={{
...useStyles(),
...props.classes,
}}
/>
);
};

@ -1,32 +0,0 @@
/**
* Wrapper around material-ui's Button component that styles it with
* Bitburner's UI theme
*/
import React from "react";
import { Paper, PaperProps } from "@mui/material";
import makeStyles from "@mui/styles/makeStyles";
const useStyles = makeStyles({
root: {
backgroundColor: "rgb(30, 30, 30)",
border: "2px solid #000",
borderRadius: "10px",
display: "inline-block",
flexWrap: "wrap",
padding: "10px",
},
});
export const MuiPaper: React.FC<PaperProps> = (props: PaperProps) => {
return (
<Paper
{...props}
classes={{
...useStyles(),
...props.classes,
}}
/>
);
};

@ -16,7 +16,7 @@ export function Reputation({ reputation }: { reputation: number | string }): Rea
const classes = useStyles();
return (
<span className={classes.reputation}>
{typeof reputation === "number" ? numeralWrapper.formatFavor(reputation) : reputation}
{typeof reputation === "number" ? numeralWrapper.formatReputation(reputation) : reputation}
</span>
);
}

@ -1,60 +0,0 @@
/**
* Basic stateless button
* Uses the 'std-button' css class
*/
import * as React from "react";
interface IStdButtonProps {
addClasses?: string;
autoFocus?: boolean;
disabled?: boolean;
id?: string;
onClick?: (e: React.MouseEvent<HTMLElement>) => any;
onKeyUp?: (e: React.KeyboardEvent<HTMLElement>) => any;
style?: any;
text: string | JSX.Element;
tooltip?: string | JSX.Element;
}
type IInnerHTMLMarkup = {
__html: string;
};
export function StdButton(props: IStdButtonProps): React.ReactElement {
const hasTooltip = props.tooltip != null && props.tooltip !== "";
let className = props.disabled ? "std-button-disabled" : "std-button";
if (hasTooltip) {
className += " tooltip";
}
if (typeof props.addClasses === "string") {
className += ` ${props.addClasses}`;
}
// Tooltip will be set using inner HTML
let tooltip;
if (hasTooltip) {
if (typeof props.tooltip === "string") {
const tooltipMarkup: IInnerHTMLMarkup = {
__html: props.tooltip,
};
tooltip = <span className={"tooltiptext"} dangerouslySetInnerHTML={tooltipMarkup}></span>;
} else {
tooltip = <span className={"tooltiptext"}>{props.tooltip}</span>;
}
}
return (
<button
className={className}
id={props.id}
onClick={props.onClick}
onKeyUp={props.onKeyUp}
style={props.style}
autoFocus={props.autoFocus}
>
{props.text}
{hasTooltip && tooltip}
</button>
);
}