Merge pull request #3544 from phyzical/feature/refactor-augmentation

REFACTOR:  augmentation cost, rep cost and level to be calculated in place
This commit is contained in:
hydroflame 2022-05-04 12:08:24 -04:00 committed by GitHub
commit 44641fee07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 348 additions and 202 deletions

@ -9,6 +9,18 @@ import { Money } from "../ui/React/Money";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
import { FactionNames } from "../Faction/data/FactionNames"; import { FactionNames } from "../Faction/data/FactionNames";
import { IPlayer } from "../PersonObjects/IPlayer";
import { AugmentationNames } from "./data/AugmentationNames";
import { CONSTANTS } from "../Constants";
import { StaticAugmentations } from "./StaticAugmentations";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { getBaseAugmentationPriceMultiplier, getGenericAugmentationPriceMultiplier } from "./AugmentationHelpers";
import { initSoAAugmentations } from "./data/AugmentationCreator";
export interface AugmentationCosts {
moneyCost: number;
repCost: number;
}
export interface IConstructorParams { export interface IConstructorParams {
info: string | JSX.Element; info: string | JSX.Element;
@ -410,10 +422,10 @@ function generateStatsDescription(mults: IMap<number>, programs?: string[], star
} }
export class Augmentation { export class Augmentation {
// How much money this costs to buy // How much money this costs to buy before multipliers
baseCost = 0; baseCost = 0;
// How much faction reputation is required to unlock this // How much faction reputation is required to unlock this before multipliers
baseRepRequirement = 0; baseRepRequirement = 0;
// Description of what this Aug is and what it does // Description of what this Aug is and what it does
@ -425,9 +437,6 @@ export class Augmentation {
// Any Augmentation not immediately available in BitNode-1 is special (e.g. Bladeburner augs) // Any Augmentation not immediately available in BitNode-1 is special (e.g. Bladeburner augs)
isSpecial = false; isSpecial = false;
// Augmentation level - for repeatable Augs like NeuroFlux Governor
level = 0;
// Name of Augmentation // Name of Augmentation
name = ""; name = "";
@ -438,12 +447,6 @@ export class Augmentation {
// The Player/Person classes // The Player/Person classes
mults: IMap<number> = {}; mults: IMap<number> = {};
// Initial cost. Doesn't change when you purchase multiple Augmentation
startingCost = 0;
// Initial rep requirement. Doesn't change when you purchase multiple Augmentation
startingRepRequirement = 0;
// Factions that offer this aug. // Factions that offer this aug.
factions: string[] = []; factions: string[] = [];
@ -461,17 +464,15 @@ export class Augmentation {
this.prereqs = params.prereqs ? params.prereqs : []; this.prereqs = params.prereqs ? params.prereqs : [];
this.baseRepRequirement = params.repCost; this.baseRepRequirement = params.repCost;
Object.freeze(this.baseRepRequirement);
this.baseCost = params.moneyCost; this.baseCost = params.moneyCost;
this.startingCost = this.baseCost; Object.freeze(this.baseCost);
this.startingRepRequirement = this.baseRepRequirement;
this.factions = params.factions; this.factions = params.factions;
if (params.isSpecial) { if (params.isSpecial) {
this.isSpecial = true; this.isSpecial = true;
} }
this.level = 0;
// Set multipliers // Set multipliers
if (params.hacking_mult) { if (params.hacking_mult) {
this.mults.hacking_mult = params.hacking_mult; this.mults.hacking_mult = params.hacking_mult;
@ -600,6 +601,61 @@ export class Augmentation {
} }
} }
getCost(player: IPlayer): AugmentationCosts {
const augmentationReference = StaticAugmentations[this.name];
let moneyCost = augmentationReference.baseCost;
let repCost = augmentationReference.baseRepRequirement;
if (augmentationReference.name === AugmentationNames.NeuroFluxGovernor) {
let nextLevel = this.getLevel(player);
--nextLevel;
const multiplier = Math.pow(CONSTANTS.NeuroFluxGovernorLevelMult, nextLevel);
repCost = augmentationReference.baseRepRequirement * multiplier * BitNodeMultipliers.AugmentationRepCost;
moneyCost = augmentationReference.baseCost * multiplier * BitNodeMultipliers.AugmentationMoneyCost;
for (let i = 0; i < player.queuedAugmentations.length; ++i) {
moneyCost *= getBaseAugmentationPriceMultiplier();
}
} else if (augmentationReference.factions.includes(FactionNames.ShadowsOfAnarchy)) {
const soaAugmentationNames = initSoAAugmentations().map((augmentation) => augmentation.name);
const soaMultiplier = Math.pow(
CONSTANTS.SoACostMult,
soaAugmentationNames.filter((augmentationName) => player.hasAugmentation(augmentationName)).length,
);
moneyCost = augmentationReference.baseCost * soaMultiplier;
if (soaAugmentationNames.find((augmentationName) => augmentationName === augmentationReference.name)) {
repCost = augmentationReference.baseRepRequirement * soaMultiplier;
}
} else {
moneyCost =
augmentationReference.baseCost *
getGenericAugmentationPriceMultiplier() *
BitNodeMultipliers.AugmentationMoneyCost;
}
return { moneyCost, repCost };
}
getLevel(player: IPlayer): number {
// Get current Neuroflux level based on Player's augmentations
if (this.name === AugmentationNames.NeuroFluxGovernor) {
let currLevel = 0;
for (let i = 0; i < player.augmentations.length; ++i) {
if (player.augmentations[i].name === AugmentationNames.NeuroFluxGovernor) {
currLevel = player.augmentations[i].level;
}
}
// Account for purchased but uninstalled Augmentations
for (let i = 0; i < player.queuedAugmentations.length; ++i) {
if (player.queuedAugmentations[i].name == AugmentationNames.NeuroFluxGovernor) {
++currLevel;
}
}
return currLevel + 1;
}
return 0;
}
// Adds this Augmentation to all Factions // Adds this Augmentation to all Factions
addToAllFactions(): void { addToAllFactions(): void {
for (const fac of Object.keys(Factions)) { for (const fac of Object.keys(Factions)) {

@ -1,5 +1,5 @@
import { Augmentation } from "./Augmentation"; import { Augmentation } from "./Augmentation";
import { Augmentations } from "./Augmentations"; import { StaticAugmentations } from "./StaticAugmentations";
import { PlayerOwnedAugmentation, IPlayerOwnedAugmentation } from "./PlayerOwnedAugmentation"; import { PlayerOwnedAugmentation, IPlayerOwnedAugmentation } from "./PlayerOwnedAugmentation";
import { AugmentationNames } from "./data/AugmentationNames"; import { AugmentationNames } from "./data/AugmentationNames";
@ -20,30 +20,11 @@ import {
initNeuroFluxGovernor, initNeuroFluxGovernor,
initUnstableCircadianModulator, initUnstableCircadianModulator,
} from "./data/AugmentationCreator"; } from "./data/AugmentationCreator";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { Router } from "../ui/GameRoot"; import { Router } from "../ui/GameRoot";
export function AddToAugmentations(aug: Augmentation): void { export function AddToStaticAugmentations(aug: Augmentation): void {
const name = aug.name; const name = aug.name;
Augmentations[name] = aug; StaticAugmentations[name] = aug;
}
export function getNextNeuroFluxLevel(): number {
// Get current Neuroflux level based on Player's augmentations
let currLevel = 0;
for (let i = 0; i < Player.augmentations.length; ++i) {
if (Player.augmentations[i].name === AugmentationNames.NeuroFluxGovernor) {
currLevel = Player.augmentations[i].level;
}
}
// Account for purchased but uninstalled Augmentations
for (let i = 0; i < Player.queuedAugmentations.length; ++i) {
if (Player.queuedAugmentations[i].name == AugmentationNames.NeuroFluxGovernor) {
++currLevel;
}
}
return currLevel + 1;
} }
function createAugmentations(): void { function createAugmentations(): void {
@ -67,96 +48,37 @@ function resetFactionAugmentations(): void {
function initAugmentations(): void { function initAugmentations(): void {
resetFactionAugmentations(); resetFactionAugmentations();
clearObject(Augmentations); clearObject(StaticAugmentations);
createAugmentations(); createAugmentations();
updateAugmentationCosts();
Player.reapplyAllAugmentations(); Player.reapplyAllAugmentations();
} }
function getBaseAugmentationPriceMultiplier(): number { export function getBaseAugmentationPriceMultiplier(): number {
return CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][Player.sourceFileLvl(11)]; return CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][Player.sourceFileLvl(11)];
} }
export function getGenericAugmentationPriceMultiplier(): number { export function getGenericAugmentationPriceMultiplier(): number {
return Math.pow(getBaseAugmentationPriceMultiplier(), Player.queuedAugmentations.length); return Math.pow(getBaseAugmentationPriceMultiplier(), Player.queuedAugmentations.length);
} }
function updateNeuroFluxGovernorCosts(neuroFluxGovernorAugmentation: Augmentation): void {
let nextLevel = getNextNeuroFluxLevel();
--nextLevel;
const multiplier = Math.pow(CONSTANTS.NeuroFluxGovernorLevelMult, nextLevel);
neuroFluxGovernorAugmentation.baseRepRequirement =
neuroFluxGovernorAugmentation.startingRepRequirement * multiplier * BitNodeMultipliers.AugmentationRepCost;
neuroFluxGovernorAugmentation.baseCost =
neuroFluxGovernorAugmentation.startingCost * multiplier * BitNodeMultipliers.AugmentationMoneyCost;
for (let i = 0; i < Player.queuedAugmentations.length; ++i) {
neuroFluxGovernorAugmentation.baseCost *= getBaseAugmentationPriceMultiplier();
}
}
function updateSoACosts(soaAugmentation: Augmentation): void {
const soaAugmentationNames = initSoAAugmentations().map((augmentation) => augmentation.name);
const soaAugCount = soaAugmentationNames.filter((augmentationName) =>
Player.hasAugmentation(augmentationName),
).length;
soaAugmentation.baseCost = soaAugmentation.startingCost * Math.pow(CONSTANTS.SoACostMult, soaAugCount);
if (soaAugmentationNames.find((augmentationName) => augmentationName === soaAugmentation.name)) {
soaAugmentation.baseRepRequirement =
soaAugmentation.startingRepRequirement * Math.pow(CONSTANTS.SoARepMult, soaAugCount);
}
}
export function updateAugmentationCosts(): void {
for (const name of Object.keys(Augmentations)) {
if (Augmentations.hasOwnProperty(name)) {
const augmentationToUpdate = Augmentations[name];
if (augmentationToUpdate.name === AugmentationNames.NeuroFluxGovernor) {
updateNeuroFluxGovernorCosts(augmentationToUpdate);
} else if (augmentationToUpdate.factions.includes(FactionNames.ShadowsOfAnarchy)) {
updateSoACosts(augmentationToUpdate);
} else {
augmentationToUpdate.baseCost =
augmentationToUpdate.startingCost *
getGenericAugmentationPriceMultiplier() *
BitNodeMultipliers.AugmentationMoneyCost;
}
}
}
}
//Resets an Augmentation during (re-initizliation) //Resets an Augmentation during (re-initizliation)
function resetAugmentation(aug: Augmentation): void { function resetAugmentation(aug: Augmentation): void {
aug.addToFactions(aug.factions); aug.addToFactions(aug.factions);
const name = aug.name; const name = aug.name;
if (augmentationExists(name)) { if (augmentationExists(name)) {
delete Augmentations[name]; delete StaticAugmentations[name];
} }
AddToAugmentations(aug); AddToStaticAugmentations(aug);
} }
function applyAugmentation(aug: IPlayerOwnedAugmentation, reapply = false): void { function applyAugmentation(aug: IPlayerOwnedAugmentation, reapply = false): void {
const augObj = Augmentations[aug.name]; const staticAugmentation = StaticAugmentations[aug.name];
// Apply multipliers // Apply multipliers
for (const mult of Object.keys(augObj.mults)) { for (const mult of Object.keys(staticAugmentation.mults)) {
const v = Player.getMult(mult) * augObj.mults[mult]; const v = Player.getMult(mult) * staticAugmentation.mults[mult];
Player.setMult(mult, v); Player.setMult(mult, v);
} }
// Special logic for NeuroFlux Governor
if (aug.name === AugmentationNames.NeuroFluxGovernor) {
if (!reapply) {
Augmentations[aug.name].level = aug.level;
for (let i = 0; i < Player.augmentations.length; ++i) {
if (Player.augmentations[i].name == AugmentationNames.NeuroFluxGovernor) {
Player.augmentations[i].level = aug.level;
return;
// break;
}
}
}
}
// Special logic for Congruity Implant // Special logic for Congruity Implant
if (aug.name === AugmentationNames.CongruityImplant && !reapply) { if (aug.name === AugmentationNames.CongruityImplant && !reapply) {
Player.entropy = 0; Player.entropy = 0;
@ -185,7 +107,7 @@ function installAugmentations(force?: boolean): boolean {
} }
for (let i = 0; i < Player.queuedAugmentations.length; ++i) { for (let i = 0; i < Player.queuedAugmentations.length; ++i) {
const ownedAug = Player.queuedAugmentations[i]; const ownedAug = Player.queuedAugmentations[i];
const aug = Augmentations[ownedAug.name]; const aug = StaticAugmentations[ownedAug.name];
if (aug == null) { if (aug == null) {
console.error(`Invalid augmentation: ${ownedAug.name}`); console.error(`Invalid augmentation: ${ownedAug.name}`);
continue; continue;
@ -215,7 +137,7 @@ function installAugmentations(force?: boolean): boolean {
} }
function augmentationExists(name: string): boolean { function augmentationExists(name: string): boolean {
return Augmentations.hasOwnProperty(name); return StaticAugmentations.hasOwnProperty(name);
} }
export function isRepeatableAug(aug: Augmentation): boolean { export function isRepeatableAug(aug: Augmentation): boolean {

@ -1,4 +1,4 @@
import { Augmentation } from "./Augmentation"; import { Augmentation } from "./Augmentation";
import { IMap } from "../types"; import { IMap } from "../types";
export const Augmentations: IMap<Augmentation> = {}; export const StaticAugmentations: IMap<Augmentation> = {};

@ -22,7 +22,7 @@ import { Settings } from "../../Settings/Settings";
import { ConfirmationModal } from "../../ui/React/ConfirmationModal"; import { ConfirmationModal } from "../../ui/React/ConfirmationModal";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { AugmentationNames } from "../data/AugmentationNames"; import { AugmentationNames } from "../data/AugmentationNames";
import { Augmentations } from "../Augmentations"; import { StaticAugmentations } from "../StaticAugmentations";
import { CONSTANTS } from "../../Constants"; import { CONSTANTS } from "../../Constants";
import { formatNumber } from "../../utils/StringHelperFunctions"; import { formatNumber } from "../../utils/StringHelperFunctions";
import { Info } from "@mui/icons-material"; import { Info } from "@mui/icons-material";
@ -39,7 +39,9 @@ const NeuroFluxDisplay = ({ player }: NFGDisplayProps): React.ReactElement => {
<Typography variant="h5" color={Settings.theme.info}> <Typography variant="h5" color={Settings.theme.info}>
NeuroFlux Governor - Level {level} NeuroFlux Governor - Level {level}
</Typography> </Typography>
<Typography color={Settings.theme.info}>{Augmentations[AugmentationNames.NeuroFluxGovernor].stats}</Typography> <Typography color={Settings.theme.info}>
{StaticAugmentations[AugmentationNames.NeuroFluxGovernor].stats}
</Typography>
</Paper> </Paper>
) : ( ) : (
<></> <></>

@ -13,7 +13,7 @@ import React, { useState } from "react";
import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums"; import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
import { Augmentations } from "../Augmentations"; import { StaticAugmentations } from "../StaticAugmentations";
import { AugmentationNames } from "../data/AugmentationNames"; import { AugmentationNames } from "../data/AugmentationNames";
export function InstalledAugmentations(): React.ReactElement { export function InstalledAugmentations(): React.ReactElement {
@ -77,7 +77,7 @@ export function InstalledAugmentations(): React.ReactElement {
</Typography> </Typography>
<Typography sx={{ maxHeight: 350, overflowY: "scroll" }}> <Typography sx={{ maxHeight: 350, overflowY: "scroll" }}>
{(() => { {(() => {
const aug = Augmentations[selectedAug.name]; const aug = StaticAugmentations[selectedAug.name];
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 = (

@ -8,7 +8,7 @@ import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { Player } from "../../Player"; import { Player } from "../../Player";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { Augmentations } from "../Augmentations"; import { StaticAugmentations } from "../StaticAugmentations";
interface IAugmentedStats { interface IAugmentedStats {
[index: string]: number; [index: string]: number;
@ -17,7 +17,7 @@ interface IAugmentedStats {
function calculateAugmentedStats(): IAugmentedStats { function calculateAugmentedStats(): IAugmentedStats {
const augP: IAugmentedStats = {}; const augP: IAugmentedStats = {};
for (const aug of Player.queuedAugmentations) { for (const aug of Player.queuedAugmentations) {
const augObj = Augmentations[aug.name]; const augObj = StaticAugmentations[aug.name];
for (const mult of Object.keys(augObj.mults)) { for (const mult of Object.keys(augObj.mults)) {
const v = augP[mult] ? augP[mult] : 1; const v = augP[mult] ? augP[mult] : 1;
augP[mult] = v * augObj.mults[mult]; augP[mult] = v * augObj.mults[mult];

@ -5,15 +5,14 @@
import { CheckBox, CheckBoxOutlineBlank, CheckCircle, Info, NewReleases, Report } from "@mui/icons-material"; import { CheckBox, CheckBoxOutlineBlank, CheckCircle, Info, NewReleases, Report } from "@mui/icons-material";
import { Box, Button, Container, Paper, Tooltip, Typography } from "@mui/material"; import { Box, Button, Container, Paper, Tooltip, Typography } from "@mui/material";
import React, { useState } from "react"; import React, { useState } from "react";
import { getNextNeuroFluxLevel } from "../AugmentationHelpers";
import { Faction } from "../../Faction/Faction"; import { Faction } from "../../Faction/Faction";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { Augmentation } from "../Augmentation"; import { Augmentation } from "../Augmentation";
import { Augmentations } from "../Augmentations";
import { AugmentationNames } from "../data/AugmentationNames"; import { AugmentationNames } from "../data/AugmentationNames";
import { PurchaseAugmentationModal } from "./PurchaseAugmentationModal"; import { PurchaseAugmentationModal } from "./PurchaseAugmentationModal";
import { StaticAugmentations } from "../StaticAugmentations";
interface IPreReqsProps { interface IPreReqsProps {
player: IPlayer; player: IPlayer;
@ -160,10 +159,10 @@ interface IPurchasableAugProps {
export function PurchasableAugmentation(props: IPurchasableAugProps): React.ReactElement { export function PurchasableAugmentation(props: IPurchasableAugProps): React.ReactElement {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const aug = Augmentations[props.augName]; const aug = StaticAugmentations[props.augName];
const augCosts = aug.getCost(props.parent.player);
const cost = props.parent.sleeveAugs ? aug.startingCost : aug.baseCost; const cost = props.parent.sleeveAugs ? aug.baseCost : augCosts.moneyCost;
const repCost = augCosts.repCost;
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 description = ( const description = (
<> <>
@ -205,7 +204,8 @@ export function PurchasableAugmentation(props: IPurchasableAugProps): React.Reac
<> <>
<Typography variant="h5"> <Typography variant="h5">
{props.augName} {props.augName}
{props.augName === AugmentationNames.NeuroFluxGovernor && ` - Level ${getNextNeuroFluxLevel()}`} {props.augName === AugmentationNames.NeuroFluxGovernor &&
` - Level ${aug.getLevel(props.parent.player)}`}
</Typography> </Typography>
<Typography>{description}</Typography> <Typography>{description}</Typography>
</> </>
@ -222,7 +222,7 @@ export function PurchasableAugmentation(props: IPurchasableAugProps): React.Reac
}} }}
> >
{aug.name} {aug.name}
{aug.name === AugmentationNames.NeuroFluxGovernor && ` - Level ${getNextNeuroFluxLevel()}`} {aug.name === AugmentationNames.NeuroFluxGovernor && ` - Level ${aug.getLevel(props.parent.player)}`}
</Typography> </Typography>
{aug.factions.length === 1 && !props.parent.sleeveAugs && ( {aug.factions.length === 1 && !props.parent.sleeveAugs && (
<Exclusive player={props.parent.player} aug={aug} /> <Exclusive player={props.parent.player} aug={aug} />
@ -236,14 +236,14 @@ export function PurchasableAugmentation(props: IPurchasableAugProps): React.Reac
{props.owned || ( {props.owned || (
<Box sx={{ display: "grid", alignItems: "center", justifyItems: "left" }}> <Box sx={{ display: "grid", alignItems: "center", justifyItems: "left" }}>
<Requirement <Requirement
fulfilled={aug.baseCost === 0 || props.parent.player.money > cost} fulfilled={cost === 0 || props.parent.player.money > cost}
value={numeralWrapper.formatMoney(cost)} value={numeralWrapper.formatMoney(cost)}
color={Settings.theme.money} color={Settings.theme.money}
/> />
{props.parent.rep !== undefined && ( {props.parent.rep !== undefined && (
<Requirement <Requirement
fulfilled={props.parent.rep >= aug.baseRepRequirement} fulfilled={props.parent.rep >= repCost}
value={`${numeralWrapper.formatReputation(aug.baseRepRequirement)} rep`} value={`${numeralWrapper.formatReputation(repCost)} rep`}
color={Settings.theme.rep} color={Settings.theme.rep}
/> />
)} )}

@ -44,7 +44,7 @@ export function PurchaseAugmentationModal(props: IProps): React.ReactElement {
<br /> <br />
<br /> <br />
Would you like to purchase the {props.aug.name} Augmentation for&nbsp; Would you like to purchase the {props.aug.name} Augmentation for&nbsp;
<Money money={props.aug.baseCost} />? <Money money={props.aug.getCost(player).moneyCost} />?
<br /> <br />
<br /> <br />
</Typography> </Typography>

@ -5,7 +5,7 @@
import { List, ListItemText, Paper, Tooltip, Typography } from "@mui/material"; import { List, ListItemText, Paper, Tooltip, Typography } from "@mui/material";
import * as React from "react"; import * as React from "react";
import { Player } from "../../Player"; import { Player } from "../../Player";
import { Augmentations } from "../Augmentations"; import { StaticAugmentations } from "../StaticAugmentations";
import { AugmentationNames } from "../data/AugmentationNames"; import { AugmentationNames } from "../data/AugmentationNames";
export function PurchasedAugmentations(): React.ReactElement { export function PurchasedAugmentations(): React.ReactElement {
@ -23,7 +23,7 @@ export function PurchasedAugmentations(): React.ReactElement {
let displayName = ownedAug.name; let displayName = ownedAug.name;
if (ownedAug.name === AugmentationNames.NeuroFluxGovernor && i !== nfgIndex) continue; if (ownedAug.name === AugmentationNames.NeuroFluxGovernor && i !== nfgIndex) continue;
const aug = Augmentations[ownedAug.name]; const aug = StaticAugmentations[ownedAug.name];
let level = null; let level = null;
if (ownedAug.name === AugmentationNames.NeuroFluxGovernor) { if (ownedAug.name === AugmentationNames.NeuroFluxGovernor) {

@ -1,4 +1,4 @@
import { Augmentations } from "../Augmentation/Augmentations"; import { StaticAugmentations } from "../Augmentation/StaticAugmentations";
import { Augmentation } from "../Augmentation/Augmentation"; import { Augmentation } from "../Augmentation/Augmentation";
import { PlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation"; import { PlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
@ -18,7 +18,6 @@ import {
import { dialogBoxCreate } from "../ui/React/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
import { InvitationEvent } from "./ui/InvitationModal"; import { InvitationEvent } from "./ui/InvitationModal";
import { FactionNames } from "./data/FactionNames"; import { FactionNames } from "./data/FactionNames";
import { updateAugmentationCosts, getNextNeuroFluxLevel } from "../Augmentation/AugmentationHelpers";
import { SFC32RNG } from "../Casino/RNG"; import { SFC32RNG } from "../Casino/RNG";
export function inviteToFaction(faction: Faction): void { export function inviteToFaction(faction: Faction): void {
@ -59,6 +58,7 @@ export function hasAugmentationPrereqs(aug: Augmentation): boolean {
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);
const augCosts = aug.getCost(Player);
if (!hasPrereqs) { if (!hasPrereqs) {
const txt = `You must first purchase or install ${aug.prereqs const txt = `You must first purchase or install ${aug.prereqs
.filter((req) => !Player.hasAugmentation(req)) .filter((req) => !Player.hasAugmentation(req))
@ -68,28 +68,26 @@ export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = fal
} else { } else {
dialogBoxCreate(txt); dialogBoxCreate(txt);
} }
} else if (aug.baseCost !== 0 && Player.money < aug.baseCost) { } else if (augCosts.moneyCost !== 0 && Player.money < augCosts.moneyCost) {
const txt = "You don't have enough money to purchase " + aug.name; const txt = "You don't have enough money to purchase " + aug.name;
if (sing) { if (sing) {
return txt; return txt;
} }
dialogBoxCreate(txt); dialogBoxCreate(txt);
} else if (fac.playerReputation < aug.baseRepRequirement) { } else if (fac.playerReputation < augCosts.repCost) {
const txt = "You don't have enough faction reputation to purchase " + aug.name; const txt = "You don't have enough faction reputation to purchase " + aug.name;
if (sing) { if (sing) {
return txt; return txt;
} }
dialogBoxCreate(txt); dialogBoxCreate(txt);
} else if (aug.baseCost === 0 || Player.money >= aug.baseCost) { } else if (augCosts.moneyCost === 0 || Player.money >= augCosts.moneyCost) {
const queuedAugmentation = new PlayerOwnedAugmentation(aug.name); const queuedAugmentation = new PlayerOwnedAugmentation(aug.name);
if (aug.name == AugmentationNames.NeuroFluxGovernor) { if (aug.name == AugmentationNames.NeuroFluxGovernor) {
queuedAugmentation.level = getNextNeuroFluxLevel(); queuedAugmentation.level = aug.getLevel(Player);
} }
Player.queuedAugmentations.push(queuedAugmentation); Player.queuedAugmentations.push(queuedAugmentation);
Player.loseMoney(aug.baseCost, "augmentations"); Player.loseMoney(augCosts.moneyCost, "augmentations");
updateAugmentationCosts();
if (sing) { if (sing) {
return "You purchased " + aug.name; return "You purchased " + aug.name;
@ -141,14 +139,14 @@ export function processPassiveFactionRepGain(numCycles: number): void {
export const getFactionAugmentationsFiltered = (player: IPlayer, faction: Faction): string[] => { export const getFactionAugmentationsFiltered = (player: IPlayer, faction: Faction): string[] => {
// If player has a gang with this faction, return (almost) all augmentations // If player has a gang with this faction, return (almost) all augmentations
if (player.hasGangWith(faction.name)) { if (player.hasGangWith(faction.name)) {
let augs = Object.values(Augmentations); let augs = Object.values(StaticAugmentations);
// Remove special augs // Remove special augs
augs = augs.filter((a) => !a.isSpecial && a.name !== AugmentationNames.CongruityImplant); augs = augs.filter((a) => !a.isSpecial && a.name !== AugmentationNames.CongruityImplant);
if (player.bitNodeN === 2) { if (player.bitNodeN === 2) {
// TRP is not available outside of BN2 for Gangs // TRP is not available outside of BN2 for Gangs
augs.push(Augmentations[AugmentationNames.TheRedPill]); augs.push(StaticAugmentations[AugmentationNames.TheRedPill]);
} }
const rng = SFC32RNG(`BN${player.bitNodeN}.${player.sourceFileLvl(player.bitNodeN)}`); const rng = SFC32RNG(`BN${player.bitNodeN}.${player.sourceFileLvl(player.bitNodeN)}`);

@ -3,8 +3,9 @@
*/ */
import { Box, Button, Tooltip, Typography, Paper, Container } from "@mui/material"; import { Box, Button, Tooltip, Typography, Paper, Container } from "@mui/material";
import React, { useState } from "react"; import React, { useState } from "react";
import { StaticAugmentations } from "../../Augmentation/StaticAugmentations";
import { getGenericAugmentationPriceMultiplier } from "../../Augmentation/AugmentationHelpers"; import { getGenericAugmentationPriceMultiplier } from "../../Augmentation/AugmentationHelpers";
import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { PurchasableAugmentations } from "../../Augmentation/ui/PurchasableAugmentations"; import { PurchasableAugmentations } from "../../Augmentation/ui/PurchasableAugmentations";
import { PurchaseAugmentationsOrderSetting } from "../../Settings/SettingEnums"; import { PurchaseAugmentationsOrderSetting } from "../../Settings/SettingEnums";
@ -54,13 +55,13 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
function getAugsSortedByCost(): string[] { function getAugsSortedByCost(): string[] {
const augs = getAugs(); const augs = getAugs();
augs.sort((augName1, augName2) => { augs.sort((augName1, augName2) => {
const aug1 = Augmentations[augName1], const aug1 = StaticAugmentations[augName1],
aug2 = Augmentations[augName2]; aug2 = StaticAugmentations[augName2];
if (aug1 == null || aug2 == null) { if (aug1 == null || aug2 == null) {
throw new Error("Invalid Augmentation Names"); throw new Error("Invalid Augmentation Names");
} }
return aug1.baseCost - aug2.baseCost; return aug1.getCost(player).moneyCost - aug2.getCost(player).moneyCost;
}); });
return augs; return augs;
@ -69,31 +70,32 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
function getAugsSortedByPurchasable(): string[] { function getAugsSortedByPurchasable(): string[] {
const augs = getAugs(); const augs = getAugs();
function canBuy(augName: string): boolean { function canBuy(augName: string): boolean {
const aug = Augmentations[augName]; const aug = StaticAugmentations[augName];
const repCost = aug.baseRepRequirement; const augCosts = aug.getCost(player);
const repCost = augCosts.repCost;
const hasReq = props.faction.playerReputation >= repCost; const hasReq = props.faction.playerReputation >= repCost;
const hasRep = hasAugmentationPrereqs(aug); const hasRep = hasAugmentationPrereqs(aug);
const hasCost = aug.baseCost !== 0 && player.money > aug.baseCost; const hasCost = augCosts.moneyCost !== 0 && player.money > augCosts.moneyCost;
return hasCost && hasReq && hasRep; return hasCost && hasReq && hasRep;
} }
const buy = augs.filter(canBuy).sort((augName1, augName2) => { const buy = augs.filter(canBuy).sort((augName1, augName2) => {
const aug1 = Augmentations[augName1], const aug1 = StaticAugmentations[augName1],
aug2 = Augmentations[augName2]; aug2 = StaticAugmentations[augName2];
if (aug1 == null || aug2 == null) { if (aug1 == null || aug2 == null) {
throw new Error("Invalid Augmentation Names"); throw new Error("Invalid Augmentation Names");
} }
return aug1.baseCost - aug2.baseCost; return aug1.getCost(player).moneyCost - aug2.getCost(player).moneyCost;
}); });
const cantBuy = augs const cantBuy = augs
.filter((aug) => !canBuy(aug)) .filter((aug) => !canBuy(aug))
.sort((augName1, augName2) => { .sort((augName1, augName2) => {
const aug1 = Augmentations[augName1], const aug1 = StaticAugmentations[augName1],
aug2 = Augmentations[augName2]; aug2 = StaticAugmentations[augName2];
if (aug1 == null || aug2 == null) { if (aug1 == null || aug2 == null) {
throw new Error("Invalid Augmentation Names"); throw new Error("Invalid Augmentation Names");
} }
return aug1.baseRepRequirement - aug2.baseRepRequirement; return aug1.getCost(player).repCost - aug2.getCost(player).repCost;
}); });
return buy.concat(cantBuy); return buy.concat(cantBuy);
@ -102,12 +104,12 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
function getAugsSortedByReputation(): string[] { function getAugsSortedByReputation(): string[] {
const augs = getAugs(); const augs = getAugs();
augs.sort((augName1, augName2) => { augs.sort((augName1, augName2) => {
const aug1 = Augmentations[augName1], const aug1 = StaticAugmentations[augName1],
aug2 = Augmentations[augName2]; aug2 = StaticAugmentations[augName2];
if (aug1 == null || aug2 == null) { if (aug1 == null || aug2 == null) {
throw new Error("Invalid Augmentation Names"); throw new Error("Invalid Augmentation Names");
} }
return aug1.baseRepRequirement - aug2.baseRepRequirement; return aug1.getCost(player).repCost - aug2.getCost(player).repCost;
}); });
return augs; return augs;
@ -195,10 +197,11 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
ownedAugNames={owned} ownedAugNames={owned}
player={player} player={player}
canPurchase={(player, aug) => { canPurchase={(player, aug) => {
const augCost = aug.getCost(player).moneyCost;
return ( return (
hasAugmentationPrereqs(aug) && hasAugmentationPrereqs(aug) &&
props.faction.playerReputation >= aug.baseRepRequirement && props.faction.playerReputation >= aug.baseRepRequirement &&
(aug.baseCost === 0 || player.money > aug.baseCost) (augCost === 0 || player.money > augCost)
); );
}} }}
purchaseAugmentation={(player, aug, showModal) => { purchaseAugmentation={(player, aug, showModal) => {

@ -16,12 +16,10 @@ import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { Faction } from "../Faction"; import { Faction } from "../Faction";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
import { CreateGangModal } from "./CreateGangModal";
import { Box, Paper, Typography, Button, Tooltip } from "@mui/material"; import { Typography, Button } from "@mui/material";
import { CovenantPurchasesRoot } from "../../PersonObjects/Sleeve/ui/CovenantPurchasesRoot"; import { CovenantPurchasesRoot } from "../../PersonObjects/Sleeve/ui/CovenantPurchasesRoot";
import { FactionNames } from "../data/FactionNames"; import { FactionNames } from "../data/FactionNames";
import { GangConstants } from "../../Gang/data/Constants";
import { GangButton } from "./GangButton"; import { GangButton } from "./GangButton";
type IProps = { type IProps = {
@ -30,7 +28,6 @@ type IProps = {
}; };
// Info text for all options on the UI // Info text for all options on the UI
const gangInfo = "Create and manage a gang for this Faction. Gangs will earn you money and faction reputation";
const hackingContractsInfo = const hackingContractsInfo =
"Complete hacking contracts for your faction. " + "Complete hacking contracts for your faction. " +
"Your effectiveness, which determines how much " + "Your effectiveness, which determines how much " +

@ -0,0 +1,166 @@
/**
* React component for displaying a single augmentation for purchase through
* the faction UI
*/
import React, { useState } from "react";
import { hasAugmentationPrereqs, purchaseAugmentation } from "../FactionHelpers";
import { StaticAugmentations } from "../../Augmentation/StaticAugmentations";
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 { use } from "../../ui/Context";
import { PurchaseAugmentationModal } from "../../Augmentation/ui/PurchaseAugmentationModal";
interface IReqProps {
augName: string;
p: IPlayer;
hasReq: boolean;
rep: number;
hasRep: boolean;
cost: number;
hasCost: boolean;
}
function Requirements(props: IReqProps): React.ReactElement {
const aug = StaticAugmentations[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 player = use.Player();
const [open, setOpen] = useState(false);
const aug = StaticAugmentations[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 repCosts = aug.getCost(player);
const moneyCost = repCosts.moneyCost;
const repCost = repCosts.repCost;
const hasReq = hasAugmentationPrereqs(aug);
const hasRep = props.faction.playerReputation >= repCost;
const hasCost = moneyCost === 0 || props.p.money > moneyCost;
// 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 ${aug.getLevel(player)}`;
}
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} />
</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>
);
}

@ -1,4 +1,4 @@
import { Augmentations } from "../Augmentation/Augmentations"; import { StaticAugmentations } from "../Augmentation/StaticAugmentations";
import { hasAugmentationPrereqs } from "../Faction/FactionHelpers"; import { hasAugmentationPrereqs } from "../Faction/FactionHelpers";
import { CityName } from "../Locations/data/CityNames"; import { CityName } from "../Locations/data/CityNames";
import { getRamCost } from "../Netscript/RamCostGenerator"; import { getRamCost } from "../Netscript/RamCostGenerator";
@ -28,10 +28,10 @@ export function NetscriptGrafting(player: IPlayer, workerScript: WorkerScript, h
updateRam("getAugmentationGraftPrice"); updateRam("getAugmentationGraftPrice");
const augName = helper.string("getAugmentationGraftPrice", "augName", _augName); const augName = helper.string("getAugmentationGraftPrice", "augName", _augName);
checkGraftingAPIAccess("getAugmentationGraftPrice"); checkGraftingAPIAccess("getAugmentationGraftPrice");
if (!getGraftingAvailableAugs(player).includes(augName) || !Augmentations.hasOwnProperty(augName)) { if (!getGraftingAvailableAugs(player).includes(augName) || !StaticAugmentations.hasOwnProperty(augName)) {
throw helper.makeRuntimeErrorMsg("grafting.getAugmentationGraftPrice", `Invalid aug: ${augName}`); throw helper.makeRuntimeErrorMsg("grafting.getAugmentationGraftPrice", `Invalid aug: ${augName}`);
} }
const graftableAug = new GraftableAugmentation(Augmentations[augName]); const graftableAug = new GraftableAugmentation(StaticAugmentations[augName]);
return graftableAug.cost; return graftableAug.cost;
}, },
@ -39,10 +39,10 @@ export function NetscriptGrafting(player: IPlayer, workerScript: WorkerScript, h
updateRam("getAugmentationGraftTime"); updateRam("getAugmentationGraftTime");
const augName = helper.string("getAugmentationGraftTime", "augName", _augName); const augName = helper.string("getAugmentationGraftTime", "augName", _augName);
checkGraftingAPIAccess("getAugmentationGraftTime"); checkGraftingAPIAccess("getAugmentationGraftTime");
if (!getGraftingAvailableAugs(player).includes(augName) || !Augmentations.hasOwnProperty(augName)) { if (!getGraftingAvailableAugs(player).includes(augName) || !StaticAugmentations.hasOwnProperty(augName)) {
throw helper.makeRuntimeErrorMsg("grafting.getAugmentationGraftTime", `Invalid aug: ${augName}`); throw helper.makeRuntimeErrorMsg("grafting.getAugmentationGraftTime", `Invalid aug: ${augName}`);
} }
const graftableAug = new GraftableAugmentation(Augmentations[augName]); const graftableAug = new GraftableAugmentation(StaticAugmentations[augName]);
return calculateGraftingTimeWithBonus(player, graftableAug); return calculateGraftingTimeWithBonus(player, graftableAug);
}, },
@ -64,7 +64,7 @@ export function NetscriptGrafting(player: IPlayer, workerScript: WorkerScript, h
"You must be in New Tokyo to begin grafting an Augmentation.", "You must be in New Tokyo to begin grafting an Augmentation.",
); );
} }
if (!getGraftingAvailableAugs(player).includes(augName) || !Augmentations.hasOwnProperty(augName)) { if (!getGraftingAvailableAugs(player).includes(augName) || !StaticAugmentations.hasOwnProperty(augName)) {
workerScript.log("grafting.graftAugmentation", () => `Invalid aug: ${augName}`); workerScript.log("grafting.graftAugmentation", () => `Invalid aug: ${augName}`);
return false; return false;
} }
@ -75,7 +75,7 @@ export function NetscriptGrafting(player: IPlayer, workerScript: WorkerScript, h
workerScript.log("graftAugmentation", () => txt); workerScript.log("graftAugmentation", () => txt);
} }
const craftableAug = new GraftableAugmentation(Augmentations[augName]); const craftableAug = new GraftableAugmentation(StaticAugmentations[augName]);
if (player.money < craftableAug.cost) { if (player.money < craftableAug.cost) {
workerScript.log("grafting.graftAugmentation", () => `You don't have enough money to craft ${augName}`); workerScript.log("grafting.graftAugmentation", () => `You don't have enough money to craft ${augName}`);
return false; return false;

@ -3,7 +3,7 @@ import { IPlayer } from "../PersonObjects/IPlayer";
import { purchaseAugmentation, joinFaction, getFactionAugmentationsFiltered } from "../Faction/FactionHelpers"; import { purchaseAugmentation, joinFaction, getFactionAugmentationsFiltered } from "../Faction/FactionHelpers";
import { startWorkerScript } from "../NetscriptWorker"; import { startWorkerScript } from "../NetscriptWorker";
import { Augmentation } from "../Augmentation/Augmentation"; import { Augmentation } from "../Augmentation/Augmentation";
import { Augmentations } from "../Augmentation/Augmentations"; import { StaticAugmentations } from "../Augmentation/StaticAugmentations";
import { augmentationExists, installAugmentations } from "../Augmentation/AugmentationHelpers"; import { augmentationExists, installAugmentations } from "../Augmentation/AugmentationHelpers";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { killWorkerScript } from "../Netscript/killWorkerScript"; import { killWorkerScript } from "../Netscript/killWorkerScript";
@ -56,7 +56,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
throw _ctx.helper.makeRuntimeErrorMsg(`Invalid augmentation: '${name}'`); throw _ctx.helper.makeRuntimeErrorMsg(`Invalid augmentation: '${name}'`);
} }
return Augmentations[name]; return StaticAugmentations[name];
}; };
const getFaction = function (_ctx: NetscriptContext, name: string): Faction { const getFaction = function (_ctx: NetscriptContext, name: string): Faction {
@ -122,7 +122,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const augName = _ctx.helper.string("augName", _augName); const augName = _ctx.helper.string("augName", _augName);
const aug = getAugmentation(_ctx, augName); const aug = getAugmentation(_ctx, augName);
return [aug.baseRepRequirement, aug.baseCost]; return [aug.getCost(player).moneyCost, aug.getCost(player).repCost];
}, },
getAugmentationPrereq: (_ctx: NetscriptContext) => getAugmentationPrereq: (_ctx: NetscriptContext) =>
function (_augName: unknown): string[] { function (_augName: unknown): string[] {
@ -136,14 +136,14 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const augName = _ctx.helper.string("augName", _augName); const augName = _ctx.helper.string("augName", _augName);
const aug = getAugmentation(_ctx, augName); const aug = getAugmentation(_ctx, augName);
return aug.baseCost; return aug.getCost(player).moneyCost;
}, },
getAugmentationRepReq: (_ctx: NetscriptContext) => getAugmentationRepReq: (_ctx: NetscriptContext) =>
function (_augName: unknown): number { function (_augName: unknown): number {
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const augName = _ctx.helper.string("augName", _augName); const augName = _ctx.helper.string("augName", _augName);
const aug = getAugmentation(_ctx, augName); const aug = getAugmentation(_ctx, augName);
return aug.baseRepRequirement; return aug.getCost(player).repCost;
}, },
getAugmentationStats: (_ctx: NetscriptContext) => getAugmentationStats: (_ctx: NetscriptContext) =>
function (_augName: unknown): AugmentationStats { function (_augName: unknown): AugmentationStats {
@ -183,7 +183,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
} }
} }
if (fac.playerReputation < aug.baseRepRequirement) { if (fac.playerReputation < aug.getCost(player).repCost) {
_ctx.log(() => `You do not have enough reputation with '${fac.name}'.`); _ctx.log(() => `You do not have enough reputation with '${fac.name}'.`);
return false; return false;
} }
@ -1085,7 +1085,6 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
_ctx.log(() => `Invalid work type: '${type}`); _ctx.log(() => `Invalid work type: '${type}`);
return false; return false;
} }
return true;
}, },
getFactionRep: (_ctx: NetscriptContext) => getFactionRep: (_ctx: NetscriptContext) =>
function (_facName: unknown): number { function (_facName: unknown): number {

@ -5,7 +5,7 @@ import { FactionWorkType } from "../Faction/FactionWorkTypeEnum";
import { SleeveTaskType } from "../PersonObjects/Sleeve/SleeveTaskTypesEnum"; import { SleeveTaskType } from "../PersonObjects/Sleeve/SleeveTaskTypesEnum";
import { WorkerScript } from "../Netscript/WorkerScript"; import { WorkerScript } from "../Netscript/WorkerScript";
import { findSleevePurchasableAugs } from "../PersonObjects/Sleeve/SleeveHelpers"; import { findSleevePurchasableAugs } from "../PersonObjects/Sleeve/SleeveHelpers";
import { Augmentations } from "../Augmentation/Augmentations"; import { StaticAugmentations } from "../Augmentation/StaticAugmentations";
import { CityName } from "../Locations/data/CityNames"; import { CityName } from "../Locations/data/CityNames";
import { findCrime } from "../Crime/CrimeHelpers"; import { findCrime } from "../Crime/CrimeHelpers";
@ -286,7 +286,7 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
const aug = purchasableAugs[i]; const aug = purchasableAugs[i];
augs.push({ augs.push({
name: aug.name, name: aug.name,
cost: aug.startingCost, cost: aug.baseCost,
}); });
} }
@ -303,7 +303,7 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
throw helper.makeRuntimeErrorMsg("sleeve.purchaseSleeveAug", `Sleeve shock too high: Sleeve ${sleeveNumber}`); throw helper.makeRuntimeErrorMsg("sleeve.purchaseSleeveAug", `Sleeve shock too high: Sleeve ${sleeveNumber}`);
} }
const aug = Augmentations[augName]; const aug = StaticAugmentations[augName];
if (!aug) { if (!aug) {
throw helper.makeRuntimeErrorMsg("sleeve.purchaseSleeveAug", `Invalid aug: ${augName}`); throw helper.makeRuntimeErrorMsg("sleeve.purchaseSleeveAug", `Invalid aug: ${augName}`);
} }

@ -18,7 +18,7 @@ export class GraftableAugmentation {
} }
get cost(): number { get cost(): number {
return this.augmentation.startingCost * CONSTANTS.AugmentationGraftingCostMult; return this.augmentation.baseCost * CONSTANTS.AugmentationGraftingCostMult;
} }
get time(): number { get time(): number {

@ -1,11 +1,11 @@
import { Augmentations } from "../../Augmentation/Augmentations"; import { StaticAugmentations } from "../../Augmentation/StaticAugmentations";
import { GraftableAugmentation } from "./GraftableAugmentation"; import { GraftableAugmentation } from "./GraftableAugmentation";
import { IPlayer } from "../IPlayer"; import { IPlayer } from "../IPlayer";
export const getGraftingAvailableAugs = (player: IPlayer): string[] => { export const getGraftingAvailableAugs = (player: IPlayer): string[] => {
const augs: string[] = []; const augs: string[] = [];
for (const [augName, aug] of Object.entries(Augmentations)) { for (const [augName, aug] of Object.entries(StaticAugmentations)) {
if (aug.isSpecial) continue; if (aug.isSpecial) continue;
augs.push(augName); augs.push(augName);
} }

@ -2,7 +2,7 @@ import { Construction, CheckBox, CheckBoxOutlineBlank } from "@mui/icons-materia
import { Box, Button, Container, List, ListItemButton, Paper, Typography } from "@mui/material"; import { Box, Button, Container, List, ListItemButton, Paper, Typography } from "@mui/material";
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Augmentation } from "../../../Augmentation/Augmentation"; import { Augmentation } from "../../../Augmentation/Augmentation";
import { Augmentations } from "../../../Augmentation/Augmentations"; import { StaticAugmentations } from "../../../Augmentation/StaticAugmentations";
import { AugmentationNames } from "../../../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../../../Augmentation/data/AugmentationNames";
import { CONSTANTS } from "../../../Constants"; import { CONSTANTS } from "../../../Constants";
import { hasAugmentationPrereqs } from "../../../Faction/FactionHelpers"; import { hasAugmentationPrereqs } from "../../../Faction/FactionHelpers";
@ -54,7 +54,7 @@ export const GraftingRoot = (): React.ReactElement => {
const player = use.Player(); const player = use.Player();
const router = use.Router(); const router = use.Router();
for (const aug of Object.values(Augmentations)) { for (const aug of Object.values(StaticAugmentations)) {
const name = aug.name; const name = aug.name;
const graftableAug = new GraftableAugmentation(aug); const graftableAug = new GraftableAugmentation(aug);
GraftableAugmentations[name] = graftableAug; GraftableAugmentations[name] = graftableAug;
@ -62,6 +62,7 @@ export const GraftingRoot = (): React.ReactElement => {
const [selectedAug, setSelectedAug] = useState(getGraftingAvailableAugs(player)[0]); const [selectedAug, setSelectedAug] = useState(getGraftingAvailableAugs(player)[0]);
const [graftOpen, setGraftOpen] = useState(false); const [graftOpen, setGraftOpen] = useState(false);
const selectedAugmentation = StaticAugmentations[selectedAug];
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
function rerender(): void { function rerender(): void {
@ -148,22 +149,26 @@ export const GraftingRoot = (): React.ReactElement => {
{/* Use formula so the displayed creation time is accurate to player bonus */} {/* Use formula so the displayed creation time is accurate to player bonus */}
</Typography> </Typography>
{Augmentations[selectedAug].prereqs.length > 0 && ( {selectedAugmentation.prereqs.length > 0 && (
<AugPreReqsChecklist player={player} aug={Augmentations[selectedAug]} /> <AugPreReqsChecklist player={player} aug={selectedAugmentation} />
)} )}
<br /> <br />
<Typography> <Typography>
{(() => { {(() => {
const aug = Augmentations[selectedAug]; const info =
typeof selectedAugmentation.info === "string" ? (
const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info; <span>{selectedAugmentation.info}</span>
) : (
selectedAugmentation.info
);
const tooltip = ( const tooltip = (
<> <>
{info} {info}
<br /> <br />
<br /> <br />
{aug.stats} {selectedAugmentation.stats}
</> </>
); );
return tooltip; return tooltip;

@ -1,6 +1,5 @@
import { IPlayer } from "../IPlayer"; import { IPlayer } from "../IPlayer";
import { PlayerObject } from "./PlayerObject"; import { PlayerObject } from "./PlayerObject";
import { Augmentations } from "../../Augmentation/Augmentations";
import { applyAugmentation } from "../../Augmentation/AugmentationHelpers"; import { applyAugmentation } from "../../Augmentation/AugmentationHelpers";
import { PlayerOwnedAugmentation } from "../../Augmentation/PlayerOwnedAugmentation"; import { PlayerOwnedAugmentation } from "../../Augmentation/PlayerOwnedAugmentation";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
@ -1367,7 +1366,7 @@ export function craftAugmentationWork(this: IPlayer, numCycles: number): boolean
export function finishGraftAugmentationWork(this: IPlayer, cancelled: boolean, singularity = false): string { export function finishGraftAugmentationWork(this: IPlayer, cancelled: boolean, singularity = false): string {
const augName = this.graftAugmentationName; const augName = this.graftAugmentationName;
if (cancelled === false) { if (cancelled === false) {
applyAugmentation(Augmentations[augName]); applyAugmentation({ name: augName, level: 1 });
if (!this.hasAugmentation(AugmentationNames.CongruityImplant)) { if (!this.hasAugmentation(AugmentationNames.CongruityImplant)) {
this.entropy += 1; this.entropy += 1;

@ -686,7 +686,7 @@ export class Sleeve extends Person {
} }
tryBuyAugmentation(p: IPlayer, aug: Augmentation): boolean { tryBuyAugmentation(p: IPlayer, aug: Augmentation): boolean {
if (!p.canAfford(aug.startingCost)) { if (!p.canAfford(aug.baseCost)) {
return false; return false;
} }
@ -695,7 +695,7 @@ export class Sleeve extends Person {
return false; return false;
} }
p.loseMoney(aug.startingCost, "sleeves"); p.loseMoney(aug.baseCost, "sleeves");
this.installAugmentation(aug); this.installAugmentation(aug);
return true; return true;
} }

@ -4,7 +4,7 @@ import { Sleeve } from "./Sleeve";
import { IPlayer } from "../IPlayer"; import { IPlayer } from "../IPlayer";
import { Augmentation } from "../../Augmentation/Augmentation"; import { Augmentation } from "../../Augmentation/Augmentation";
import { Augmentations } from "../../Augmentation/Augmentations"; import { StaticAugmentations } from "../../Augmentation/StaticAugmentations";
import { Faction } from "../../Faction/Faction"; import { Faction } from "../../Faction/Faction";
import { Factions } from "../../Faction/Factions"; import { Factions } from "../../Faction/Factions";
@ -64,13 +64,13 @@ export function findSleevePurchasableAugs(sleeve: Sleeve, p: IPlayer): Augmentat
if (p.inGang()) { if (p.inGang()) {
const fac = p.getGangFaction(); const fac = p.getGangFaction();
for (const augName of Object.keys(Augmentations)) { for (const augName of Object.keys(StaticAugmentations)) {
const aug = Augmentations[augName]; const aug = StaticAugmentations[augName];
if (!isAvailableForSleeve(aug)) { if (!isAvailableForSleeve(aug)) {
continue; continue;
} }
if (fac.playerReputation > aug.baseRepRequirement) { if (fac.playerReputation > aug.getCost(p).repCost) {
availableAugs.push(aug); availableAugs.push(aug);
} }
} }
@ -89,12 +89,12 @@ export function findSleevePurchasableAugs(sleeve: Sleeve, p: IPlayer): Augmentat
} }
for (const augName of fac.augmentations) { for (const augName of fac.augmentations) {
const aug: Augmentation = Augmentations[augName]; const aug: Augmentation = StaticAugmentations[augName];
if (!isAvailableForSleeve(aug)) { if (!isAvailableForSleeve(aug)) {
continue; continue;
} }
if (fac.playerReputation > aug.baseRepRequirement) { if (fac.playerReputation > aug.getCost(p).repCost) {
availableAugs.push(aug); availableAugs.push(aug);
} }
} }

@ -52,7 +52,7 @@ export function SleeveAugmentationsModal(props: IProps): React.ReactElement {
ownedAugNames={ownedAugNames} ownedAugNames={ownedAugNames}
player={player} player={player}
canPurchase={(player, aug) => { canPurchase={(player, aug) => {
return player.money > aug.startingCost; return player.money > aug.baseCost;
}} }}
purchaseAugmentation={(player, aug, _showModal) => { purchaseAugmentation={(player, aug, _showModal) => {
props.sleeve.tryBuyAugmentation(player, aug); props.sleeve.tryBuyAugmentation(player, aug);

@ -1,6 +1,6 @@
import { FactionNames } from "./Faction/data/FactionNames"; import { FactionNames } from "./Faction/data/FactionNames";
import { CityName } from "./Locations/data/CityNames"; import { CityName } from "./Locations/data/CityNames";
import { Augmentations } from "./Augmentation/Augmentations"; import { StaticAugmentations } from "./Augmentation/StaticAugmentations";
import { augmentationExists, initAugmentations } from "./Augmentation/AugmentationHelpers"; import { augmentationExists, initAugmentations } from "./Augmentation/AugmentationHelpers";
import { AugmentationNames } from "./Augmentation/data/AugmentationNames"; import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
import { initBitNodeMultipliers } from "./BitNode/BitNode"; import { initBitNodeMultipliers } from "./BitNode/BitNode";
@ -225,9 +225,9 @@ export function prestigeSourceFile(flume: boolean): void {
} }
// Delete all Augmentations // Delete all Augmentations
for (const name of Object.keys(Augmentations)) { for (const name of Object.keys(StaticAugmentations)) {
if (Augmentations.hasOwnProperty(name)) { if (StaticAugmentations.hasOwnProperty(name)) {
delete Augmentations[name]; delete StaticAugmentations[name];
} }
} }

@ -4,7 +4,6 @@ import ReactDOM from "react-dom";
import { TTheme as Theme, ThemeEvents, refreshTheme } from "./Themes/ui/Theme"; import { TTheme as Theme, ThemeEvents, refreshTheme } from "./Themes/ui/Theme";
import { LoadingScreen } from "./ui/LoadingScreen"; import { LoadingScreen } from "./ui/LoadingScreen";
import { initElectron } from "./Electron"; import { initElectron } from "./Electron";
import { AlertEvents } from "./ui/React/AlertManager";
initElectron(); initElectron();
globalThis["React"] = React; globalThis["React"] = React;
globalThis["ReactDOM"] = ReactDOM; globalThis["ReactDOM"] = ReactDOM;