Merge pull request #3545 from nickofolas/improvement/purchase-augs-ui

UI: Redesign purchasable Augmentations
This commit is contained in:
hydroflame 2022-04-26 11:16:13 -04:00 committed by GitHub
commit d0d700077a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 433 additions and 402 deletions

@ -251,7 +251,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
moneyCost: 1.15e8, moneyCost: 1.15e8,
repCost: 2.75e4, repCost: 2.75e4,
info: "The latest version of the 'Augmented Targeting' implant adds the ability to lock-on and track threats.", info: "The latest version of the 'Augmented Targeting' implant adds the ability to lock-on and track threats.",
prereqs: [AugmentationNames.Targeting2], prereqs: [AugmentationNames.Targeting2, AugmentationNames.Targeting1],
dexterity_mult: 1.3, dexterity_mult: 1.3,
factions: [ factions: [
FactionNames.TheDarkArmy, FactionNames.TheDarkArmy,
@ -348,7 +348,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
info: info:
"The latest version of the 'Combat Rib' augmentation releases advanced anabolic steroids that " + "The latest version of the 'Combat Rib' augmentation releases advanced anabolic steroids that " +
"improve muscle mass and physical performance while being safe and free of side effects.", "improve muscle mass and physical performance while being safe and free of side effects.",
prereqs: [AugmentationNames.CombatRib2], prereqs: [AugmentationNames.CombatRib2, AugmentationNames.CombatRib1],
strength_mult: 1.18, strength_mult: 1.18,
defense_mult: 1.18, defense_mult: 1.18,
factions: [ factions: [
@ -682,7 +682,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
"This upgraded firmware allows the Embedded Netburner Module to control information on " + "This upgraded firmware allows the Embedded Netburner Module to control information on " +
"a network by re-routing traffic, spoofing IP addresses, and altering the data inside network " + "a network by re-routing traffic, spoofing IP addresses, and altering the data inside network " +
"packets.", "packets.",
prereqs: [AugmentationNames.ENMCore], prereqs: [AugmentationNames.ENMCore, AugmentationNames.ENM],
hacking_speed_mult: 1.05, hacking_speed_mult: 1.05,
hacking_money_mult: 1.3, hacking_money_mult: 1.3,
hacking_chance_mult: 1.05, hacking_chance_mult: 1.05,
@ -707,7 +707,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
"The Core V3 library is an implant that upgrades the firmware of the Embedded Netburner Module. " + "The Core V3 library is an implant that upgrades the firmware of the Embedded Netburner Module. " +
"This upgraded firmware allows the Embedded Netburner Module to seamlessly inject code into " + "This upgraded firmware allows the Embedded Netburner Module to seamlessly inject code into " +
"any device on a network.", "any device on a network.",
prereqs: [AugmentationNames.ENMCoreV2], prereqs: [AugmentationNames.ENMCoreV2, AugmentationNames.ENMCore, AugmentationNames.ENM],
hacking_speed_mult: 1.05, hacking_speed_mult: 1.05,
hacking_money_mult: 1.4, hacking_money_mult: 1.4,
hacking_chance_mult: 1.1, hacking_chance_mult: 1.1,
@ -835,7 +835,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
"are a set of specialized microprocessors that are attached to " + "are a set of specialized microprocessors that are attached to " +
"neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " + "neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " +
"so that the brain doesn't have to.", "so that the brain doesn't have to.",
prereqs: [AugmentationNames.CranialSignalProcessorsG2], prereqs: [AugmentationNames.CranialSignalProcessorsG2, AugmentationNames.CranialSignalProcessorsG1],
hacking_speed_mult: 1.02, hacking_speed_mult: 1.02,
hacking_money_mult: 1.15, hacking_money_mult: 1.15,
hacking_mult: 1.09, hacking_mult: 1.09,
@ -850,7 +850,11 @@ export const initGeneralAugmentations = (): Augmentation[] => [
"are a set of specialized microprocessors that are attached to " + "are a set of specialized microprocessors that are attached to " +
"neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " + "neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " +
"so that the brain doesn't have to.", "so that the brain doesn't have to.",
prereqs: [AugmentationNames.CranialSignalProcessorsG3], prereqs: [
AugmentationNames.CranialSignalProcessorsG3,
AugmentationNames.CranialSignalProcessorsG2,
AugmentationNames.CranialSignalProcessorsG1,
],
hacking_speed_mult: 1.02, hacking_speed_mult: 1.02,
hacking_money_mult: 1.2, hacking_money_mult: 1.2,
hacking_grow_mult: 1.25, hacking_grow_mult: 1.25,
@ -865,7 +869,12 @@ export const initGeneralAugmentations = (): Augmentation[] => [
"are a set of specialized microprocessors that are attached to " + "are a set of specialized microprocessors that are attached to " +
"neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " + "neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " +
"so that the brain doesn't have to.", "so that the brain doesn't have to.",
prereqs: [AugmentationNames.CranialSignalProcessorsG4], prereqs: [
AugmentationNames.CranialSignalProcessorsG4,
AugmentationNames.CranialSignalProcessorsG3,
AugmentationNames.CranialSignalProcessorsG2,
AugmentationNames.CranialSignalProcessorsG1,
],
hacking_mult: 1.3, hacking_mult: 1.3,
hacking_money_mult: 1.25, hacking_money_mult: 1.25,
hacking_grow_mult: 1.75, hacking_grow_mult: 1.75,
@ -1962,7 +1971,7 @@ export const initChurchOfTheMachineGodAugmentations = (): Augmentation[] => [
"You will become greater than the sum of our parts. As One. Embrace your gift " + "You will become greater than the sum of our parts. As One. Embrace your gift " +
"fully and wholly free of it's accursed toll. Serenity brings tranquility the form " + "fully and wholly free of it's accursed toll. Serenity brings tranquility the form " +
"of no longer suffering a stat penalty. ", "of no longer suffering a stat penalty. ",
prereqs: [AugmentationNames.StaneksGift2], prereqs: [AugmentationNames.StaneksGift2, AugmentationNames.StaneksGift1],
isSpecial: true, isSpecial: true,
hacking_chance_mult: 1 / 0.95, hacking_chance_mult: 1 / 0.95,
hacking_speed_mult: 1 / 0.95, hacking_speed_mult: 1 / 0.95,

@ -0,0 +1,264 @@
/**
* React component for displaying a single augmentation for purchase through
* the faction UI
*/
import { CheckBox, CheckBoxOutlineBlank, CheckCircle, Info, NewReleases, Report } from "@mui/icons-material";
import { Box, Button, Container, Paper, Tooltip, Typography } from "@mui/material";
import React, { useState } from "react";
import { getNextNeuroFluxLevel } from "../AugmentationHelpers";
import { Faction } from "../../Faction/Faction";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Settings } from "../../Settings/Settings";
import { numeralWrapper } from "../../ui/numeralFormat";
import { Augmentation } from "../Augmentation";
import { Augmentations } from "../Augmentations";
import { AugmentationNames } from "../data/AugmentationNames";
import { PurchaseAugmentationModal } from "./PurchaseAugmentationModal";
interface IPreReqsProps {
player: IPlayer;
aug: Augmentation;
}
const PreReqs = (props: IPreReqsProps): React.ReactElement => {
const ownedPreReqs = props.aug.prereqs.filter((aug) => props.player.hasAugmentation(aug));
const hasPreReqs = props.aug.prereqs.length > 0 && ownedPreReqs.length === props.aug.prereqs.length;
return (
<Tooltip
title={
<>
<Typography sx={{ color: Settings.theme.money }}>
This Augmentation has the following pre-requisite(s):
</Typography>
{props.aug.prereqs.map((preAug) => (
<Requirement
fulfilled={props.player.hasAugmentation(preAug)}
value={preAug}
color={Settings.theme.money}
key={preAug}
/>
))}
</>
}
>
<Typography
variant="body2"
sx={{
display: "flex",
alignItems: "center",
color: hasPreReqs ? Settings.theme.successlight : Settings.theme.error,
}}
>
{hasPreReqs ? (
<>
<CheckCircle fontSize="small" sx={{ mr: 1 }} />
Pre-requisites Owned
</>
) : (
<>
<Report fontSize="small" sx={{ mr: 1 }} />
Missing {props.aug.prereqs.length - ownedPreReqs.length} pre-requisite(s)
</>
)}
</Typography>
</Tooltip>
);
};
interface IExclusiveProps {
player: IPlayer;
aug: Augmentation;
}
const Exclusive = (props: IExclusiveProps): React.ReactElement => {
return (
<Tooltip
title={
<>
<Typography sx={{ color: Settings.theme.money }}>
This Augmentation can only be acquired from the following source(s):
</Typography>
<ul>
<Typography sx={{ color: Settings.theme.money }}>
<li>
<b>{props.aug.factions[0]}</b> faction
</li>
{props.player.canAccessGang() && !props.aug.isSpecial && (
<li>
Certain <b>gangs</b>
</li>
)}
{props.player.canAccessGrafting() &&
!props.aug.isSpecial &&
props.aug.name !== AugmentationNames.TheRedPill && (
<li>
<b>Grafting</b>
</li>
)}
</Typography>
</ul>
</>
}
>
<NewReleases sx={{ ml: 1, color: Settings.theme.money, transform: "rotate(180deg)" }} />
</Tooltip>
);
};
interface IReqProps {
value: string;
color: string;
fulfilled: boolean;
}
const Requirement = (props: IReqProps): React.ReactElement => {
return (
<Typography sx={{ display: "flex", alignItems: "center", color: props.color }}>
{props.fulfilled ? <CheckBox sx={{ mr: 1 }} /> : <CheckBoxOutlineBlank sx={{ mr: 1 }} />}
{props.value}
</Typography>
);
};
interface IPurchasableAugsProps {
augNames: string[];
ownedAugNames: string[];
player: IPlayer;
canPurchase: (player: IPlayer, aug: Augmentation) => boolean;
purchaseAugmentation: (player: IPlayer, aug: Augmentation, showModal: (open: boolean) => void) => void;
rep?: number;
sleeveAugs?: boolean;
faction?: Faction;
}
export const PurchasableAugmentations = (props: IPurchasableAugsProps): React.ReactElement => {
return (
<Container
maxWidth="lg"
disableGutters
sx={{ mx: 0, display: "grid", gridTemplateColumns: "repeat(1, 1fr)", gap: 1 }}
>
{props.augNames.map((augName: string) => (
<PurchasableAugmentation key={augName} parent={props} augName={augName} owned={false} />
))}
{props.ownedAugNames.map((augName: string) => (
<PurchasableAugmentation key={augName} parent={props} augName={augName} owned={true} />
))}
</Container>
);
};
interface IPurchasableAugProps {
parent: IPurchasableAugsProps;
augName: string;
owned: boolean;
}
export function PurchasableAugmentation(props: IPurchasableAugProps): React.ReactElement {
const [open, setOpen] = useState(false);
const aug = Augmentations[props.augName];
const cost = props.parent.sleeveAugs ? aug.startingCost : aug.baseCost;
const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info;
const description = (
<>
{info}
<br />
<br />
{aug.stats}
</>
);
return (
<Paper
sx={{
p: 1,
display: "grid",
gridTemplateColumns: "minmax(0, 4fr) 1fr",
gap: 1,
opacity: props.owned ? 0.75 : 1,
}}
>
<>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Button
onClick={() =>
props.parent.purchaseAugmentation(props.parent.player, aug, (open): void => {
setOpen(open);
})
}
disabled={!props.parent.canPurchase(props.parent.player, aug) || props.owned}
sx={{ width: "48px", height: "48px", float: "left", clear: "none", mr: 1 }}
>
{props.owned ? "Owned" : "Buy"}
</Button>
<Box sx={{ maxWidth: props.owned ? "100%" : "85%" }}>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Tooltip
title={
<>
<Typography variant="h5">
{props.augName}
{props.augName === AugmentationNames.NeuroFluxGovernor && ` - Level ${getNextNeuroFluxLevel()}`}
</Typography>
<Typography>{description}</Typography>
</>
}
>
<Info sx={{ mr: 1 }} color="info" />
</Tooltip>
<Typography
variant="h6"
sx={{
textOverflow: "ellipsis",
whiteSpace: "nowrap",
overflow: "hidden",
}}
>
{aug.name}
{aug.name === AugmentationNames.NeuroFluxGovernor && ` - Level ${getNextNeuroFluxLevel()}`}
</Typography>
{aug.factions.length === 1 && !props.parent.sleeveAugs && (
<Exclusive player={props.parent.player} aug={aug} />
)}
</Box>
{aug.prereqs.length > 0 && !props.parent.sleeveAugs && <PreReqs player={props.parent.player} aug={aug} />}
</Box>
</Box>
{props.owned || (
<Box sx={{ display: "grid", alignItems: "center", justifyItems: "left" }}>
<Requirement
fulfilled={aug.baseCost === 0 || props.parent.player.money > cost}
value={numeralWrapper.formatMoney(cost)}
color={Settings.theme.money}
/>
{props.parent.rep !== undefined && (
<Requirement
fulfilled={props.parent.rep >= aug.baseRepRequirement}
value={`${numeralWrapper.formatReputation(aug.baseRepRequirement)} rep`}
color={Settings.theme.rep}
/>
)}
</Box>
)}
{Settings.SuppressBuyAugmentationConfirmation || (
<PurchaseAugmentationModal
open={open}
onClose={() => setOpen(false)}
faction={props.parent.faction}
aug={aug}
/>
)}
</>
</Paper>
);
}

@ -1,9 +1,9 @@
import React from "react"; import React from "react";
import { Augmentation } from "../../Augmentation/Augmentation"; import { Augmentation } from "../Augmentation";
import { Faction } from "../Faction"; import { Faction } from "../../Faction/Faction";
import { purchaseAugmentation } from "../FactionHelpers"; import { purchaseAugmentation } from "../../Faction/FactionHelpers";
import { isRepeatableAug } from "../../Augmentation/AugmentationHelpers"; import { isRepeatableAug } from "../AugmentationHelpers";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../ui/React/Modal";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
@ -13,21 +13,23 @@ import Button from "@mui/material/Button";
interface IProps { interface IProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
faction: Faction; faction?: Faction;
aug: Augmentation; aug?: Augmentation;
rerender: () => void;
} }
export function PurchaseAugmentationModal(props: IProps): React.ReactElement { export function PurchaseAugmentationModal(props: IProps): React.ReactElement {
if (typeof props.aug === "undefined" || typeof props.faction === "undefined") {
return <></>;
}
const player = use.Player(); const player = use.Player();
function buy(): void { function buy(): void {
if (!isRepeatableAug(props.aug) && player.hasAugmentation(props.aug)) { if (!isRepeatableAug(props.aug as Augmentation) && player.hasAugmentation(props.aug as Augmentation)) {
return; return;
} }
purchaseAugmentation(props.aug, props.faction); purchaseAugmentation(props.aug as Augmentation, props.faction as Faction);
props.rerender();
props.onClose(); props.onClose();
} }

@ -54,36 +54,15 @@ export function joinFaction(faction: Faction): void {
//Returns a boolean indicating whether the player has the prerequisites for the //Returns a boolean indicating whether the player has the prerequisites for the
//specified Augmentation //specified Augmentation
export function hasAugmentationPrereqs(aug: Augmentation): boolean { export function hasAugmentationPrereqs(aug: Augmentation): boolean {
let hasPrereqs = true; return aug.prereqs.every((aug) => Player.hasAugmentation(aug));
if (aug.prereqs && aug.prereqs.length > 0) {
for (let i = 0; i < aug.prereqs.length; ++i) {
const prereqAug = Augmentations[aug.prereqs[i]];
if (prereqAug == null) {
console.error(`Invalid prereq Augmentation ${aug.prereqs[i]}`);
continue;
}
if (Player.hasAugmentation(prereqAug, true) === false) {
hasPrereqs = false;
// Check if the aug is purchased
for (let j = 0; j < Player.queuedAugmentations.length; ++j) {
if (Player.queuedAugmentations[j].name === prereqAug.name) {
hasPrereqs = true;
break;
}
}
}
}
}
return hasPrereqs;
} }
export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = false): string { export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = false): string {
const hasPrereqs = hasAugmentationPrereqs(aug); const hasPrereqs = hasAugmentationPrereqs(aug);
if (!hasPrereqs) { if (!hasPrereqs) {
const txt = `You must first purchase or install ${aug.prereqs.join(",")} before you can purchase this one.`; const txt = `You must first purchase or install ${aug.prereqs
.filter((req) => !Player.hasAugmentation(req))
.join(",")} before you can purchase this one.`;
if (sing) { if (sing) {
return txt; return txt;
} else { } else {

@ -1,30 +1,21 @@
/** /**
* Root React Component for displaying a faction's "Purchase Augmentations" page * Root React Component for displaying a faction's "Purchase Augmentations" page
*/ */
import { Box, Button, Tooltip, Typography, Paper, Container } from "@mui/material";
import React, { useState } from "react"; import React, { useState } from "react";
import { getGenericAugmentationPriceMultiplier } from "../../Augmentation/AugmentationHelpers";
import { PurchaseableAugmentation } from "./PurchaseableAugmentation";
import { Augmentations } from "../../Augmentation/Augmentations"; import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Faction } from "../Faction"; import { PurchasableAugmentations } from "../../Augmentation/ui/PurchasableAugmentations";
import { PurchaseAugmentationsOrderSetting } from "../../Settings/SettingEnums"; import { PurchaseAugmentationsOrderSetting } from "../../Settings/SettingEnums";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { hasAugmentationPrereqs, getFactionAugmentationsFiltered } from "../FactionHelpers";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
import { Reputation } from "../../ui/React/Reputation";
import { Favor } from "../../ui/React/Favor";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { Favor } from "../../ui/React/Favor";
import Box from "@mui/material/Box"; import { Reputation } from "../../ui/React/Reputation";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
import TableBody from "@mui/material/TableBody";
import Table from "@mui/material/Table";
import { getGenericAugmentationPriceMultiplier } from "../../Augmentation/AugmentationHelpers";
import { FactionNames } from "../data/FactionNames"; import { FactionNames } from "../data/FactionNames";
import { Faction } from "../Faction";
import { getFactionAugmentationsFiltered, hasAugmentationPrereqs, purchaseAugmentation } from "../FactionHelpers";
type IProps = { type IProps = {
faction: Faction; faction: Faction;
@ -137,38 +128,12 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
aug === AugmentationNames.NeuroFluxGovernor || aug === AugmentationNames.NeuroFluxGovernor ||
(!player.augmentations.some((a) => a.name === aug) && !player.queuedAugmentations.some((a) => a.name === aug)), (!player.augmentations.some((a) => a.name === aug) && !player.queuedAugmentations.some((a) => a.name === aug)),
); );
const purchaseableAugmentation = (aug: string, owned = false): React.ReactNode => {
return (
<PurchaseableAugmentation
augName={aug}
faction={props.faction}
key={aug}
p={player}
rerender={rerender}
owned={owned}
/>
);
};
const augListElems = purchasable.map((aug) => purchaseableAugmentation(aug));
let ownedElem = <></>;
const owned = augs.filter((aug: string) => !purchasable.includes(aug)); const owned = augs.filter((aug: string) => !purchasable.includes(aug));
if (owned.length !== 0) {
ownedElem = (
<>
<br />
<Typography variant="h4">Purchased Augmentations</Typography>
<Typography>This faction also offers these augmentations but you already own them.</Typography>
{owned.map((aug) => purchaseableAugmentation(aug, true))}
</>
);
}
const multiplierComponent = const multiplierComponent =
props.faction.name !== FactionNames.ShadowsOfAnarchy ? ( props.faction.name !== FactionNames.ShadowsOfAnarchy ? (
<Typography> <Typography>
Price multiplier: x {numeralWrapper.formatMultiplier(getGenericAugmentationPriceMultiplier())} <b>Price multiplier:</b> x {numeralWrapper.formatReallyBigNumber(getGenericAugmentationPriceMultiplier())}
</Typography> </Typography>
) : ( ) : (
<></> <></>
@ -176,42 +141,77 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
return ( return (
<> <>
<Button onClick={props.routeToMainPage}>Back</Button> <Container disableGutters maxWidth="lg" sx={{ mx: 0 }}>
<Typography variant="h4">Faction Augmentations</Typography> <Button onClick={props.routeToMainPage}>Back</Button>
<Typography> <Typography variant="h4">Faction Augmentations</Typography>
These are all of the Augmentations that are available to purchase from {props.faction.name}. Augmentations are <Paper sx={{ p: 1, mb: 1 }}>
powerful upgrades that will enhance your abilities. <Typography>
<br /> These are all of the Augmentations that are available to purchase from <b>{props.faction.name}</b>.
Reputation: <Reputation reputation={props.faction.playerReputation} /> Favor:{" "} Augmentations are powerful upgrades that will enhance your abilities.
<Favor favor={Math.floor(props.faction.favor)} /> <br />
</Typography> </Typography>
<Box display="flex"> <Box
<Tooltip sx={{
title={ display: "grid",
gridTemplateColumns: `repeat(${props.faction.name === FactionNames.ShadowsOfAnarchy ? "2" : "3"}, 1fr)`,
justifyItems: "center",
my: 1,
}}
>
<Tooltip
title={
<Typography>
The price of every Augmentation increases for every queued Augmentation and it is reset when you
install them.
</Typography>
}
>
{multiplierComponent}
</Tooltip>
<Typography> <Typography>
The price of every Augmentation increases for every queued Augmentation and it is reset when you install <b>Reputation:</b> <Reputation reputation={props.faction.playerReputation} />
them.
</Typography> </Typography>
<Typography>
<b>Favor:</b> <Favor favor={Math.floor(props.faction.favor)} />
</Typography>
</Box>
<Box sx={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)" }}>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)}>Sort by Cost</Button>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Reputation)}>
Sort by Reputation
</Button>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Default)}>
Sort by Default Order
</Button>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Purchasable)}>
Sort by Purchasable
</Button>
</Box>
</Paper>
</Container>
<PurchasableAugmentations
augNames={purchasable}
ownedAugNames={owned}
player={player}
canPurchase={(player, aug) => {
return (
hasAugmentationPrereqs(aug) &&
props.faction.playerReputation >= aug.baseRepRequirement &&
(aug.baseCost === 0 || player.money > aug.baseCost)
);
}}
purchaseAugmentation={(player, aug, showModal) => {
if (!Settings.SuppressBuyAugmentationConfirmation) {
showModal(true);
} else {
purchaseAugmentation(aug, props.faction);
rerender();
} }
> }}
{multiplierComponent} rep={props.faction.playerReputation}
</Tooltip> faction={props.faction}
</Box> />
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)}>Sort by Cost</Button>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Reputation)}>Sort by Reputation</Button>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Default)}>Sort by Default Order</Button>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Purchasable)}>
Sort by Purchasable
</Button>
<br />
<Table size="small" padding="none">
<TableBody>{augListElems}</TableBody>
</Table>
<Table size="small" padding="none">
<TableBody>{ownedElem}</TableBody>
</Table>
</> </>
); );
} }

@ -1,170 +0,0 @@
/**
* React component for displaying a single augmentation for purchase through
* the faction UI
*/
import React, { useState } from "react";
import { hasAugmentationPrereqs, purchaseAugmentation } from "../FactionHelpers";
import { PurchaseAugmentationModal } from "./PurchaseAugmentationModal";
import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Faction } from "../Faction";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Settings } from "../../Settings/Settings";
import { Money } from "../../ui/React/Money";
import { Reputation } from "../../ui/React/Reputation";
import { Augmentation as AugFormat } from "../../ui/React/Augmentation";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box";
import { TableCell } from "../../ui/React/Table";
import TableRow from "@mui/material/TableRow";
import { getNextNeuroFluxLevel } from "../../Augmentation/AugmentationHelpers";
interface IReqProps {
augName: string;
p: IPlayer;
hasReq: boolean;
rep: number;
hasRep: boolean;
cost: number;
hasCost: boolean;
}
function Requirements(props: IReqProps): React.ReactElement {
const aug = Augmentations[props.augName];
if (!props.hasReq) {
return (
<TableCell key={1} colSpan={2}>
<Typography color="error">
Requires{" "}
{aug.prereqs.map((aug, i) => (
<AugFormat key={i} name={aug} />
))}
</Typography>
</TableCell>
);
}
return (
<React.Fragment key="f">
<TableCell key={1}>
<Typography>
<Money money={props.cost} player={props.p} />
</Typography>
</TableCell>
<TableCell key={2}>
<Typography color={props.hasRep ? "primary" : "error"}>
Requires <Reputation reputation={props.rep} /> faction reputation
</Typography>
</TableCell>
</React.Fragment>
);
}
interface IProps {
augName: string;
faction: Faction;
p: IPlayer;
rerender: () => void;
owned?: boolean;
}
export function PurchaseableAugmentation(props: IProps): React.ReactElement {
const [open, setOpen] = useState(false);
const aug = Augmentations[props.augName];
if (aug == null) throw new Error(`aug ${props.augName} does not exists`);
if (aug == null) {
console.error(
`Invalid Augmentation when trying to create PurchaseableAugmentation display element: ${props.augName}`,
);
return <></>;
}
const moneyCost = aug.baseCost;
const repCost = aug.baseRepRequirement;
const hasReq = hasAugmentationPrereqs(aug);
const hasRep = props.faction.playerReputation >= repCost;
const hasCost = aug.baseCost === 0 || props.p.money > aug.baseCost;
// Determine UI properties
const color: "error" | "primary" = !hasReq || !hasRep || !hasCost ? "error" : "primary";
// Determine button txt
let btnTxt = aug.name;
if (aug.name === AugmentationNames.NeuroFluxGovernor) {
btnTxt += ` - Level ${getNextNeuroFluxLevel()}`;
}
let tooltip = <></>;
if (typeof aug.info === "string") {
tooltip = (
<>
<span>{aug.info}</span>
<br />
<br />
{aug.stats}
</>
);
} else
tooltip = (
<>
{aug.info}
<br />
<br />
{aug.stats}
</>
);
function handleClick(): void {
if (color === "error") return;
if (!Settings.SuppressBuyAugmentationConfirmation) {
setOpen(true);
} else {
purchaseAugmentation(aug, props.faction);
props.rerender();
}
}
return (
<TableRow>
{!props.owned && (
<TableCell key={0}>
<Button onClick={handleClick} color={color}>
Buy
</Button>
<PurchaseAugmentationModal
open={open}
onClose={() => setOpen(false)}
aug={aug}
faction={props.faction}
rerender={props.rerender}
/>
</TableCell>
)}
<TableCell key={1}>
<Box display="flex">
<Tooltip title={<Typography>{tooltip}</Typography>} placement="top">
<Typography>{btnTxt}</Typography>
</Tooltip>
</Box>
</TableCell>
{!props.owned && (
<Requirements
key={2}
augName={props.augName}
p={props.p}
cost={moneyCost}
rep={repCost}
hasReq={hasReq}
hasRep={hasRep}
hasCost={hasCost}
/>
)}
</TableRow>
);
}

@ -139,34 +139,37 @@ export const GraftingRoot = (): React.ReactElement => {
</> </>
} }
/> />
<Typography color={Settings.theme.info}> <Box sx={{ maxHeight: 330, overflowY: "scroll" }}>
<b>Time to Graft:</b>{" "} <Typography color={Settings.theme.info}>
{convertTimeMsToTimeElapsedString( <b>Time to Graft:</b>{" "}
calculateGraftingTimeWithBonus(player, GraftableAugmentations[selectedAug]), {convertTimeMsToTimeElapsedString(
calculateGraftingTimeWithBonus(player, GraftableAugmentations[selectedAug]),
)}
{/* Use formula so the displayed creation time is accurate to player bonus */}
</Typography>
{Augmentations[selectedAug].prereqs.length > 0 && (
<AugPreReqsChecklist player={player} aug={Augmentations[selectedAug]} />
)} )}
{/* Use formula so the displayed creation time is accurate to player bonus */} <br />
</Typography>
{Augmentations[selectedAug].prereqs.length > 0 && (
<AugPreReqsChecklist player={player} aug={Augmentations[selectedAug]} />
)}
<br /> <Typography>
<Typography sx={{ maxHeight: 305, overflowY: "scroll" }}> {(() => {
{(() => { const aug = Augmentations[selectedAug];
const aug = Augmentations[selectedAug];
const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info; const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info;
const tooltip = ( const tooltip = (
<> <>
{info} {info}
<br /> <br />
<br /> <br />
{aug.stats} {aug.stats}
</> </>
); );
return tooltip; return tooltip;
})()} })()}
</Typography> </Typography>
</Box>
</Box> </Box>
</Paper> </Paper>
) : ( ) : (

@ -1,20 +1,10 @@
import React, { useState, useEffect } from "react"; import { Container, Typography, Paper } from "@mui/material";
import React, { useEffect, useState } from "react";
import { PurchasableAugmentations } from "../../../Augmentation/ui/PurchasableAugmentations";
import { use } from "../../../ui/Context";
import { Modal } from "../../../ui/React/Modal";
import { Sleeve } from "../Sleeve"; import { Sleeve } from "../Sleeve";
import { findSleevePurchasableAugs } from "../SleeveHelpers"; 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 { interface IProps {
open: boolean; open: boolean;
@ -42,80 +32,34 @@ export function SleeveAugmentationsModal(props: IProps): React.ReactElement {
// and you must also have enough rep in that faction in order to purchase it. // and you must also have enough rep in that faction in order to purchase it.
const availableAugs = findSleevePurchasableAugs(props.sleeve, player); const availableAugs = findSleevePurchasableAugs(props.sleeve, player);
function purchaseAugmentation(aug: Augmentation): void {
props.sleeve.tryBuyAugmentation(player, aug);
rerender();
}
return ( return (
<Modal open={props.open} onClose={props.onClose}> <Modal open={props.open} onClose={props.onClose}>
<> <Container component={Paper} disableGutters maxWidth="lg" sx={{ mx: 0, mb: 1, p: 1 }}>
<Box sx={{ mx: 1 }}> <Typography>
<Typography> You can purchase Augmentations for your Duplicate Sleeves. These Augmentations have the same effect as they
You can purchase Augmentations for your Duplicate Sleeves. These Augmentations have the same effect as they would for you. You can only purchase Augmentations that you have unlocked through Factions.
would for you. You can only purchase Augmentations that you have unlocked through Factions. <br />
<br /> <br />
<br /> When purchasing an Augmentation for a Duplicate Sleeve, they are immediately installed. This means that the
When purchasing an Augmentation for a Duplicate Sleeve, they are immediately installed. This means that the Duplicate Sleeve will immediately lose all of its stat experience.
Duplicate Sleeve will immediately lose all of its stat experience. <br />
</Typography> <br />
<Box component={Paper} sx={{ my: 1, p: 1 }}> Augmentations will appear below as they become available.
<Table size="small" padding="none"> </Typography>
<TableBody> </Container>
{availableAugs.map((aug) => { <PurchasableAugmentations
return ( augNames={availableAugs.map((aug) => aug.name)}
<TableRow key={aug.name}> ownedAugNames={ownedAugNames}
<TableCell> player={player}
<Button onClick={() => purchaseAugmentation(aug)} disabled={player.money < aug.startingCost}> canPurchase={(player, aug) => {
Buy return player.money > aug.startingCost;
</Button> }}
</TableCell> purchaseAugmentation={(player, aug, _showModal) => {
<TableCell> props.sleeve.tryBuyAugmentation(player, aug);
<Box display="flex"> rerender();
<Tooltip title={aug.stats || ""}> }}
<Typography>{aug.name}</Typography> sleeveAugs
</Tooltip> />
</Box>
</TableCell>
<TableCell>
<Money money={aug.startingCost} player={player} />
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</Box>
</Box>
{ownedAugNames.length > 0 && (
<>
<Typography sx={{ mx: 1 }}>Owned Augmentations:</Typography>
<Box display="grid" sx={{ gridTemplateColumns: "repeat(5, 1fr)", m: 1 }}>
{ownedAugNames.map((augName) => {
const aug = Augmentations[augName];
const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info;
const tooltip = (
<>
{info}
<br />
<br />
{aug.stats}
</>
);
return (
<Tooltip key={augName} title={<Typography>{tooltip}</Typography>}>
<Paper sx={{ p: 1 }}>
<Typography>{augName}</Typography>
</Paper>
</Tooltip>
);
})}
</Box>
</>
)}
</>
</Modal> </Modal>
); );
} }