diff --git a/doc/source/advancedgameplay/intelligence.rst b/doc/source/advancedgameplay/intelligence.rst index 0bdcc15f1..7ca645387 100644 --- a/doc/source/advancedgameplay/intelligence.rst +++ b/doc/source/advancedgameplay/intelligence.rst @@ -16,3 +16,4 @@ Intelligence will boost your production for many actions in the game, including: * Crime success rate * Bladeburner * Reputation gain for companies & factions +* Augmentation crafting speed diff --git a/doc/source/advancedgameplay/sleeves.rst b/doc/source/advancedgameplay/sleeves.rst index e7097866d..27b5d9d1b 100644 --- a/doc/source/advancedgameplay/sleeves.rst +++ b/doc/source/advancedgameplay/sleeves.rst @@ -92,18 +92,19 @@ and above, and is only available after defeating BitNode-10 at least once. Memory is a persistent stat, meaning it never gets reset back to 1. The maximum possible value for a sleeve's memory is 100. -Re-sleeving -^^^^^^^^^^^ -Re-sleeving is the process of digitizing and transferring your consciousness into a -new human body, or "sleeve". When you re-sleeve into a new body, your stat experience -and Augmentations get replaced with those of the new body. +Grafting +^^^^^^^^ +Grafting is an experimental process through which you can obtain the benefits of +Augmentations, without needing to install them. -In order to re-sleeve, you must purchase new bodies. This can be done at VitaLife in -New Tokyo. Once you purchase a body to re-sleeve into, the effects will take -place immediately. +In order to graft, you must first purchase a blueprint for and craft the Augmentation. +This can be done at VitaLife in New Tokyo, where you'll find a shady researcher with +questionable connections. Once you purchase a blueprint, you will start crafting the +Augmentation, and it will be grafted to your body once complete. -Note that resleeving **REMOVES** all of your currently-installed Augmentations, -and replaces them with the ones provided by the purchased sleeve. However, -Augmentations that are purchased but not installed will **not** be removed. If you have purchased -an Augmentation and then re-sleeve into a body which already has that Augmentation, -it will be removed since you cannot have duplicate Augmentations. +Be warned, some who have tested grafting have reported an unidentified malware. Dubbed +"Entropy", this virus seems to grow in potency as more Augmentations are grafted, +causing unpredictable affects to the victim. + +Note that when crafting an Augmentation, cancelling will **not** save your progress, +and the money spent will **not** be returned. diff --git a/doc/source/advancedgameplay/sourcefiles.rst b/doc/source/advancedgameplay/sourcefiles.rst index 150b0689f..dba38b763 100644 --- a/doc/source/advancedgameplay/sourcefiles.rst +++ b/doc/source/advancedgameplay/sourcefiles.rst @@ -53,6 +53,7 @@ List of all Source-Files +-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ || BitNode-10: Digital Carbon || * Each level of this grants a Duplicate Sleeve. | || || * Allows the player to access the `Sleeve API `_ in other BitNodes. | +|| || * Grants the player access to the VitaLife grafting laboratory in other BitNodes. Also grants access to the Grafting API. | +-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ || BitNode-11: The Big Crash || * Company favor increases both the player's salary and reputation gain at that | || || company by 1% per favor (rather than just the reputation gain). | diff --git a/src/Augmentation/ui/InstalledAugmentations.tsx b/src/Augmentation/ui/InstalledAugmentations.tsx index b83b985f7..b850e1504 100644 --- a/src/Augmentation/ui/InstalledAugmentations.tsx +++ b/src/Augmentation/ui/InstalledAugmentations.tsx @@ -17,6 +17,10 @@ import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums"; import Button from "@mui/material/Button"; import Tooltip from "@mui/material/Tooltip"; import List from "@mui/material/List"; +import { ExpandLess, ExpandMore } from "@mui/icons-material"; +import { Box, Paper, ListItemButton, ListItemText, Typography, Collapse } from "@mui/material"; +import { CONSTANTS } from "../../Constants"; +import { formatNumber } from "../../utils/StringHelperFunctions"; export function InstalledAugmentations(): React.ReactElement { const setRerender = useState(true)[1]; @@ -55,6 +59,38 @@ export function InstalledAugmentations(): React.ReactElement { + {player.entropyStacks > 0 && + (() => { + const [open, setOpen] = useState(false); + + return ( + + setOpen((old) => !old)}> + + Entropy ({player.entropyStacks} accumulated) + + } + /> + {open ? ( + + ) : ( + + )} + + + + + All multipliers decreased by:{" "} + {formatNumber((1 - CONSTANTS.EntropyEffect ** player.entropyStacks) * 100, 3)}% (multiplicative) + + + + + ); + })()} + {sourceAugs.map((e) => { const aug = Augmentations[e.name]; diff --git a/src/BitNode/BitNode.tsx b/src/BitNode/BitNode.tsx index b4a7e515d..95dc6cdc5 100644 --- a/src/BitNode/BitNode.tsx +++ b/src/BitNode/BitNode.tsx @@ -425,7 +425,7 @@ BitNodes["BitNode10"] = new BitNode( This BitNode unlocks Sleeve technology. Sleeve technology allows you to:

- 1. Re-sleeve: Purchase and transfer your consciousness into a new body + 1. Grafting: Visit VitaLife in New Tokyo to be able to obtain Augmentations without needing to install
2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks synchronously diff --git a/src/Constants.ts b/src/Constants.ts index febc03e46..8a3716783 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -41,6 +41,7 @@ export const CONSTANTS: { IntelligenceInfiltrationWeight: number; IntelligenceCrimeBaseExpGain: number; IntelligenceProgramBaseExpGain: number; + IntelligenceCraftBaseExpGain: number; IntelligenceTerminalHackBaseExpGain: number; IntelligenceSingFnBaseExpGain: number; IntelligenceClassBaseExpGain: number; @@ -71,6 +72,7 @@ export const CONSTANTS: { WorkTypeCreateProgram: string; WorkTypeStudyClass: string; WorkTypeCrime: string; + WorkTypeCraftAugmentation: string; ClassStudyComputerScience: string; ClassDataStructures: string; ClassNetworks: string; @@ -108,6 +110,9 @@ export const CONSTANTS: { CodingContractBaseFactionRepGain: number; CodingContractBaseCompanyRepGain: number; CodingContractBaseMoneyGain: number; + AugmentationCraftingCostMult: number; + AugmentationCraftingTimeBase: number; + EntropyEffect: number; TotalNumBitNodes: number; LatestUpdate: string; } = { @@ -180,6 +185,7 @@ export const CONSTANTS: { IntelligenceInfiltrationWeight: 0.1, // Weight for how much int affects infiltration success rates IntelligenceCrimeBaseExpGain: 0.05, IntelligenceProgramBaseExpGain: 0.1, // Program required hack level divided by this to determine int exp gain + IntelligenceCraftBaseExpGain: 0.05, IntelligenceTerminalHackBaseExpGain: 200, // Hacking exp divided by this to determine int exp gain IntelligenceSingFnBaseExpGain: 1.5, IntelligenceClassBaseExpGain: 0.01, @@ -224,6 +230,7 @@ export const CONSTANTS: { WorkTypeCreateProgram: "Working on Create a Program", WorkTypeStudyClass: "Studying or Taking a class at university", WorkTypeCrime: "Committing a crime", + WorkTypeCraftAugmentation: "Crafting an Augmentation", ClassStudyComputerScience: "studying Computer Science", ClassDataStructures: "taking a Data Structures course", @@ -269,6 +276,13 @@ export const CONSTANTS: { CodingContractBaseCompanyRepGain: 4000, CodingContractBaseMoneyGain: 75e6, + // Augmentation crafting multipliers + AugmentationCraftingCostMult: 1.2, + AugmentationCraftingTimeBase: 3600000, + + // Value raised to the number of entropy stacks, then multiplied to player multipliers + EntropyEffect: 0.99, + // BitNode/Source-File related stuff TotalNumBitNodes: 24, diff --git a/src/DevMenu.tsx b/src/DevMenu.tsx index 62300a430..2c9611941 100644 --- a/src/DevMenu.tsx +++ b/src/DevMenu.tsx @@ -23,6 +23,7 @@ import { Sleeves } from "./DevMenu/ui/Sleeves"; import { Stanek } from "./DevMenu/ui/Stanek"; import { TimeSkip } from "./DevMenu/ui/TimeSkip"; import { Achievements } from "./DevMenu/ui/Achievements"; +import { Entropy } from "./DevMenu/ui/Entropy"; import Typography from "@mui/material/Typography"; import { Exploit } from "./Exploits/Exploit"; @@ -63,6 +64,7 @@ export function DevMenuRoot(props: IProps): React.ReactElement { + ); } diff --git a/src/DevMenu/ui/Entropy.tsx b/src/DevMenu/ui/Entropy.tsx new file mode 100644 index 000000000..93c63df71 --- /dev/null +++ b/src/DevMenu/ui/Entropy.tsx @@ -0,0 +1,50 @@ +import React from "react"; + +import Accordion from "@mui/material/Accordion"; +import AccordionSummary from "@mui/material/AccordionSummary"; +import AccordionDetails from "@mui/material/AccordionDetails"; +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; + +import Typography from "@mui/material/Typography"; +import { IPlayer } from "../../PersonObjects/IPlayer"; +import { Adjuster } from "./Adjuster"; +import { IEngine } from "../../IEngine"; + +// Update as additional BitNodes get implemented + +interface IProps { + player: IPlayer; + engine: IEngine; +} + +export function Entropy(props: IProps): React.ReactElement { + return ( + + }> + Entropy + + + { + props.player.entropyStacks += num; + props.player.applyEntropy(props.player.entropyStacks); + }} + subtract={num => { + props.player.entropyStacks -= num; + props.player.applyEntropy(props.player.entropyStacks); + }} + tons={() => { + props.player.entropyStacks += 1e12; + props.player.applyEntropy(props.player.entropyStacks); + }} + reset={() => { + props.player.entropyStacks = 0; + props.player.applyEntropy(props.player.entropyStacks); + }} + /> + + + ); +} diff --git a/src/Faction/ui/AugmentationsPage.tsx b/src/Faction/ui/AugmentationsPage.tsx index 1d519b124..d29f239d7 100644 --- a/src/Faction/ui/AugmentationsPage.tsx +++ b/src/Faction/ui/AugmentationsPage.tsx @@ -45,12 +45,17 @@ export function AugmentationsPage(props: IProps): React.ReactElement { if (isPlayersGang) { const augs: string[] = []; for (const augName of Object.keys(Augmentations)) { - if (augName === AugmentationNames.NeuroFluxGovernor) continue; - if (augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) continue; const aug = Augmentations[augName]; - if (!aug.isSpecial) { - augs.push(augName); - } + if ( + augName === AugmentationNames.NeuroFluxGovernor || + (augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) || + // Special augs (i.e. Bladeburner augs) + aug.isSpecial || + // Exclusive augs (i.e. QLink) + (aug.factions.length <= 1 && !props.faction.augmentations.includes(augName) && player.bitNodeN !== 2) + ) + continue; + augs.push(augName); } return augs; diff --git a/src/Faction/ui/FactionsRoot.tsx b/src/Faction/ui/FactionsRoot.tsx index 4d2ccc367..e5c5b292f 100644 --- a/src/Faction/ui/FactionsRoot.tsx +++ b/src/Faction/ui/FactionsRoot.tsx @@ -1,14 +1,6 @@ import React, { useEffect, useState } from "react"; -import { - Box, - Button, - Container, - Paper, - TableBody, - TableRow, - Typography -} from "@mui/material"; +import { Box, Button, Container, Paper, TableBody, TableRow, Typography } from "@mui/material"; import { Augmentations } from "../../Augmentation/Augmentations"; import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; @@ -65,26 +57,28 @@ export function FactionsRoot(props: IProps): React.ReactElement { if (isPlayersGang) { for (const augName of Object.keys(Augmentations)) { + const aug = Augmentations[augName]; if ( augName === AugmentationNames.NeuroFluxGovernor || - augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2 || - Augmentations[augName].isSpecial - ) continue; - augs.push(augName) + (augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) || + // Special augs (i.e. Bladeburner augs) + aug.isSpecial || + // Exclusive augs (i.e. QLink) + (aug.factions.length <= 1 && !faction.augmentations.includes(augName) && player.bitNodeN !== 2) + ) + continue; + augs.push(augName); } } else { augs = faction.augmentations.slice(); } - return augs.filter( - (augmentation: string) => !player.hasAugmentation(augmentation) - ).length; - } + return augs.filter((augmentation: string) => !player.hasAugmentation(augmentation)).length; + }; - const allFactions = Object.values(FactionNames).map(faction => faction as string) + const allFactions = Object.values(FactionNames).map((faction) => faction as string); const allJoinedFactions = props.player.factions.slice(0); - allJoinedFactions.sort((a, b) => - allFactions.indexOf(a) - allFactions.indexOf(b)); + allJoinedFactions.sort((a, b) => allFactions.indexOf(a) - allFactions.indexOf(b)); return ( @@ -116,7 +110,7 @@ export function FactionsRoot(props: IProps): React.ReactElement { - diff --git a/src/Locations/ui/SpecialLocation.tsx b/src/Locations/ui/SpecialLocation.tsx index 7f9b5b91d..3466d960d 100644 --- a/src/Locations/ui/SpecialLocation.tsx +++ b/src/Locations/ui/SpecialLocation.tsx @@ -72,8 +72,8 @@ export function SpecialLocation(props: IProps): React.ReactElement { /** * Click handler for Resleeving button at New Tokyo VitaLife */ - function handleResleeving(): void { - router.toResleeves(); + function handleGrafting(): void { + router.toGrafting(); } function renderBladeburner(): React.ReactElement { @@ -151,11 +151,11 @@ export function SpecialLocation(props: IProps): React.ReactElement { ); } - function renderResleeving(): React.ReactElement { - if (!player.canAccessResleeving()) { + function renderGrafting(): React.ReactElement { + if (!player.canAccessGrafting()) { return <>; } - return ; + return ; } function handleCotMG(): void { @@ -299,7 +299,7 @@ export function SpecialLocation(props: IProps): React.ReactElement { switch (props.loc.name) { case LocationName.NewTokyoVitaLife: { - return renderResleeving(); + return renderGrafting(); } case LocationName.Sector12CityHall: { return ; diff --git a/src/Netscript/RamCostGenerator.ts b/src/Netscript/RamCostGenerator.ts index af50c8c5c..31524d5b4 100644 --- a/src/Netscript/RamCostGenerator.ts +++ b/src/Netscript/RamCostGenerator.ts @@ -386,6 +386,12 @@ export const RamCosts: IMap = { getGameInfo: 0, }, + grafting: { + getAugmentationCraftPrice: 3.75, + getAugmentationCraftTime: 3.75, + craftAugmentation: 7.5, + }, + heart: { // Easter egg function break: 0, diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index 65a4b3d2f..7b292a209 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -70,6 +70,7 @@ import { NetscriptCodingContract } from "./NetscriptFunctions/CodingContract"; import { NetscriptCorporation } from "./NetscriptFunctions/Corporation"; import { NetscriptFormulas } from "./NetscriptFunctions/Formulas"; import { NetscriptStockMarket } from "./NetscriptFunctions/StockMarket"; +import { NetscriptGrafting } from "./NetscriptFunctions/Grafting"; import { IPort } from "./NetscriptPort"; import { @@ -480,6 +481,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS { const singularity = NetscriptSingularity(Player, workerScript, helper); const stockmarket = NetscriptStockMarket(Player, workerScript, helper); const ui = NetscriptUserInterface(Player, workerScript, helper); + const grafting = NetscriptGrafting(Player, workerScript, helper); const base: INS = { ...singularity, @@ -493,6 +495,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS { ui: ui, formulas: formulas, stock: stockmarket, + grafting: grafting, args: workerScript.args, hacknet: hacknet, sprintf: sprintf, @@ -2315,6 +2318,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS { tor: Player.hasTorRouter(), inBladeburner: Player.inBladeburner(), hasCorporation: Player.hasCorporation(), + entropyStacks: Player.entropyStacks, }; Object.assign(data.jobs, Player.jobs); return data; diff --git a/src/NetscriptFunctions/Grafting.ts b/src/NetscriptFunctions/Grafting.ts new file mode 100644 index 000000000..fa1e2339d --- /dev/null +++ b/src/NetscriptFunctions/Grafting.ts @@ -0,0 +1,85 @@ +import { CityName } from "../Locations/data/CityNames"; +import { Augmentations } from "../Augmentation/Augmentations"; +import { getRamCost } from "../Netscript/RamCostGenerator"; +import { WorkerScript } from "../Netscript/WorkerScript"; +import { CraftableAugmentation } from "../PersonObjects/Grafting/CraftableAugmentation"; +import { getAvailableAugs } from "../PersonObjects/Grafting/ui/GraftingRoot"; +import { IPlayer } from "../PersonObjects/IPlayer"; +import { Grafting as IGrafting } from "../ScriptEditor/NetscriptDefinitions"; +import { SourceFileFlags } from "../SourceFile/SourceFileFlags"; +import { Router } from "../ui/GameRoot"; +import { INetscriptHelper } from "./INetscriptHelper"; + +export function NetscriptGrafting(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): IGrafting { + const checkGraftingAPIAccess = (func: any): void => { + if (!player.canAccessGrafting()) { + throw helper.makeRuntimeErrorMsg( + `grafting.${func}`, + "You do not currently have access to the Grafting API. This is either because you are not in BitNode 10 or because you do not have Source-File 10", + ); + } + }; + + return { + getAugmentationCraftPrice: (augName: string): number => { + helper.updateDynamicRam("getAugmentationCraftPrice", getRamCost(player, "grafting", "getAugmentationCraftPrice")); + checkGraftingAPIAccess("getAugmentationCraftPrice"); + if (!Augmentations.hasOwnProperty(augName)) { + throw helper.makeRuntimeErrorMsg("grafting.getAugmentationCraftPrice", `Invalid aug: ${augName}`); + } + const craftableAug = new CraftableAugmentation(Augmentations[augName]); + return craftableAug.cost; + }, + + getAugmentationCraftTime: (augName: string): number => { + helper.updateDynamicRam("getAugmentationCraftTime", getRamCost(player, "grafting", "getAugmentationCraftTime")); + checkGraftingAPIAccess("getAugmentationCraftTime"); + if (!Augmentations.hasOwnProperty(augName)) { + throw helper.makeRuntimeErrorMsg("grafting.getAugmentationCraftTime", `Invalid aug: ${augName}`); + } + const craftableAug = new CraftableAugmentation(Augmentations[augName]); + return craftableAug.time; + }, + + craftAugmentation: (augName: string, focus = true): boolean => { + helper.updateDynamicRam("craftAugmentation", getRamCost(player, "grafting", "craftAugmentation")); + checkGraftingAPIAccess("craftAugmentation"); + if (player.city !== CityName.NewTokyo) { + throw helper.makeRuntimeErrorMsg( + "grafting.craftAugmentation", + "You must be in New Tokyo to begin crafting an Augmentation.", + ); + } + if (!getAvailableAugs(player).includes(augName)) { + workerScript.log("grafting.craftAugmentation", () => `Invalid aug: ${augName}`); + return false; + } + + const wasFocusing = player.focus; + if (player.isWorking) { + const txt = player.singularityStopWork(); + workerScript.log("craftAugmentation", () => txt); + } + + const craftableAug = new CraftableAugmentation(Augmentations[augName]); + if (player.money < craftableAug.cost) { + workerScript.log("grafting.craftAugmentation", () => `You don't have enough money to craft ${augName}`); + return false; + } + + player.loseMoney(craftableAug.cost, "augmentations"); + player.startCraftAugmentationWork(augName, craftableAug.time); + + if (focus) { + player.startFocusing(); + Router.toWork(); + } else if (wasFocusing) { + player.stopFocusing(); + Router.toTerminal(); + } + + workerScript.log("grafting.craftAugmentation", () => `Began crafting Augmentation ${augName}.`); + return true; + }, + }; +} diff --git a/src/NetscriptFunctions/Singularity.ts b/src/NetscriptFunctions/Singularity.ts index 3972d0bb2..0bb8456f1 100644 --- a/src/NetscriptFunctions/Singularity.ts +++ b/src/NetscriptFunctions/Singularity.ts @@ -167,12 +167,17 @@ export function NetscriptSingularity( let augs = []; if (player.hasGangWith(faction)) { for (const augName of Object.keys(Augmentations)) { - if (augName === AugmentationNames.NeuroFluxGovernor) continue; - if (augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) continue; - const tempAug = Augmentations[augName]; - if (!tempAug.isSpecial) { - augs.push(augName); - } + const aug = Augmentations[augName]; + if ( + augName === AugmentationNames.NeuroFluxGovernor || + (augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) || + // Special augs (i.e. Bladeburner augs) + aug.isSpecial || + // Exclusive augs (i.e. QLink) + (aug.factions.length <= 1 && !fac.augmentations.includes(augName) && player.bitNodeN !== 2) + ) + continue; + augs.push(augName); } } else { augs = fac.augmentations; diff --git a/src/PersonObjects/Grafting/CraftableAugmentation.ts b/src/PersonObjects/Grafting/CraftableAugmentation.ts new file mode 100644 index 000000000..0d365ebd7 --- /dev/null +++ b/src/PersonObjects/Grafting/CraftableAugmentation.ts @@ -0,0 +1,31 @@ +import { sum } from "lodash"; + +import { Augmentation } from "../../Augmentation/Augmentation"; +import { CONSTANTS } from "../../Constants"; + +export interface IConstructorParams { + augmentation: Augmentation; + readonly cost: number; + readonly time: number; +} + +export class CraftableAugmentation { + // The augmentation that this craftable corresponds to + augmentation: Augmentation; + + constructor(augmentation: Augmentation) { + this.augmentation = augmentation; + } + + get cost(): number { + return this.augmentation.startingCost * CONSTANTS.AugmentationCraftingCostMult; + } + + get time(): number { + // Time = 1 hour * log_2(sum(aug multipliers) || 1) + 30 minutes + const antiLog = Math.max(sum(Object.values(this.augmentation.mults)), 1); + + const mult = Math.log2(antiLog); + return CONSTANTS.AugmentationCraftingTimeBase * mult + CONSTANTS.MillisecondsPerHalfHour; + } +} diff --git a/src/PersonObjects/Grafting/EntropyAccumulation.ts b/src/PersonObjects/Grafting/EntropyAccumulation.ts new file mode 100644 index 000000000..8887df5a0 --- /dev/null +++ b/src/PersonObjects/Grafting/EntropyAccumulation.ts @@ -0,0 +1,52 @@ +import { IMap } from "../../types"; +import { CONSTANTS } from "../../Constants"; + +import { IPlayer } from "../IPlayer"; + +export const calculateEntropy = (player: IPlayer, stacks = 1): IMap => { + const multipliers: IMap = { + hacking_chance_mult: player.hacking_chance_mult, + hacking_speed_mult: player.hacking_speed_mult, + hacking_money_mult: player.hacking_money_mult, + hacking_grow_mult: player.hacking_grow_mult, + + hacking_mult: player.hacking_mult, + strength_mult: player.strength_mult, + defense_mult: player.defense_mult, + dexterity_mult: player.dexterity_mult, + agility_mult: player.agility_mult, + charisma_mult: player.charisma_mult, + + hacking_exp_mult: player.hacking_exp_mult, + strength_exp_mult: player.strength_exp_mult, + defense_exp_mult: player.defense_exp_mult, + dexterity_exp_mult: player.dexterity_exp_mult, + agility_exp_mult: player.agility_exp_mult, + charisma_exp_mult: player.charisma_exp_mult, + + company_rep_mult: player.company_rep_mult, + faction_rep_mult: player.faction_rep_mult, + + crime_money_mult: player.crime_money_mult, + crime_success_mult: player.crime_success_mult, + + hacknet_node_money_mult: player.hacknet_node_money_mult, + hacknet_node_purchase_cost_mult: player.hacknet_node_purchase_cost_mult, + hacknet_node_ram_cost_mult: player.hacknet_node_ram_cost_mult, + hacknet_node_core_cost_mult: player.hacknet_node_core_cost_mult, + hacknet_node_level_cost_mult: player.hacknet_node_level_cost_mult, + + work_money_mult: player.work_money_mult, + + bladeburner_max_stamina_mult: player.bladeburner_max_stamina_mult, + bladeburner_stamina_gain_mult: player.bladeburner_stamina_gain_mult, + bladeburner_analysis_mult: player.bladeburner_analysis_mult, + bladeburner_success_chance_mult: player.bladeburner_success_chance_mult, + }; + + for (const [mult, val] of Object.entries(multipliers)) { + multipliers[mult] = val * CONSTANTS.EntropyEffect ** stacks; + } + + return multipliers; +}; diff --git a/src/PersonObjects/Grafting/ui/GraftingRoot.tsx b/src/PersonObjects/Grafting/ui/GraftingRoot.tsx new file mode 100644 index 000000000..838fc8f7e --- /dev/null +++ b/src/PersonObjects/Grafting/ui/GraftingRoot.tsx @@ -0,0 +1,160 @@ +import React, { useState } from "react"; + +import { Typography, Container, Box, Paper, List, ListItemButton, Button } from "@mui/material"; +import { Construction } from "@mui/icons-material"; + +import { use } from "../../../ui/Context"; +import { Money } from "../../../ui/React/Money"; +import { ConfirmationModal } from "../../../ui/React/ConfirmationModal"; +import { Augmentations } from "../../../Augmentation/Augmentations"; +import { AugmentationNames } from "../../../Augmentation/data/AugmentationNames"; +import { Settings } from "../../../Settings/Settings"; +import { IMap } from "../../../types"; +import { convertTimeMsToTimeElapsedString, formatNumber } from "../../../utils/StringHelperFunctions"; +import { LocationName } from "../../../Locations/data/LocationNames"; +import { Locations } from "../../../Locations/Locations"; +import { CONSTANTS } from "../../../Constants"; + +import { IPlayer } from "../../IPlayer"; + +import { CraftableAugmentation } from "../CraftableAugmentation"; + +const CraftableAugmentations: IMap = {}; + +export const getAvailableAugs = (player: IPlayer): string[] => { + const augs: string[] = []; + + for (const [augName, aug] of Object.entries(Augmentations)) { + if (augName === AugmentationNames.NeuroFluxGovernor || augName === AugmentationNames.TheRedPill || aug.isSpecial) + continue; + augs.push(augName); + } + + return augs.filter((augmentation: string) => !player.hasAugmentation(augmentation)); +}; + +export const GraftingRoot = (): React.ReactElement => { + const player = use.Player(); + const router = use.Router(); + + for (const aug of Object.values(Augmentations)) { + const name = aug.name; + const craftableAug = new CraftableAugmentation(aug); + CraftableAugmentations[name] = craftableAug; + } + + const [selectedAug, setSelectedAug] = useState(getAvailableAugs(player)[0]); + const [craftOpen, setCraftOpen] = useState(false); + + return ( + + + Grafting Laboratory + + You find yourself in a secret laboratory, owned by a mysterious researcher. +
+ The scientist explains that they've been studying Augmentation grafting, the process of applying Augmentations + without requiring a body reset. +
+
+ Through legally questionable connections, the scientist has access to a vast array of Augmentation blueprints, + even private designs. They offer to build and graft the Augmentations to you, in exchange for both a hefty sum + of money, and being a lab rat. +
+ + + Craft Augmentations + + + {getAvailableAugs(player).map((k, i) => ( + setSelectedAug(k)} selected={selectedAug === k}> + {k} + + ))} + + + + {selectedAug} + + + setCraftOpen(false)} + onConfirm={() => { + const craftableAug = CraftableAugmentations[selectedAug]; + player.loseMoney(craftableAug.cost, "augmentations"); + player.startCraftAugmentationWork(selectedAug, craftableAug.time); + player.startFocusing(); + router.toWork(); + }} + confirmationText={ + <> + Cancelling crafting will not save crafting progress, and the money you spend will not be + returned. +
+
+ Additionally, grafting an Augmentation will increase the potency of the Entropy virus. + + } + /> + + Time to Craft:{" "} + {convertTimeMsToTimeElapsedString( + CraftableAugmentations[selectedAug].time / (1 + (player.getIntelligenceBonus(3) - 1) / 3), + )} + {/* Use formula so the displayed creation time is accurate to player bonus */} + + + {(() => { + const aug = Augmentations[selectedAug]; + + const info = typeof aug.info === "string" ? {aug.info} : aug.info; + const tooltip = ( + <> + {info} +
+
+ {aug.stats} + + ); + return tooltip; + })()} +
+
+
+
+ + + Entropy Accumulation + + + + Accumulated Entropy: {player.entropyStacks} +
+ All multipliers decreased by:{" "} + {formatNumber((1 - CONSTANTS.EntropyEffect ** player.entropyStacks) * 100, 3)}% (multiplicative) +
+
+ + + When installed on an unconscious individual, Augmentations are scanned by the body on awakening, eliminating + hidden malware. However, grafted Augmentations do not provide this security measure. +
+
+ Individuals who tested Augmentation grafting have reported symptoms of an unknown virus, which they've dubbed + "Entropy". This virus seems to grow more potent with each grafted Augmentation... +
+
+
+ ); +}; diff --git a/src/PersonObjects/IPlayer.ts b/src/PersonObjects/IPlayer.ts index 7b5053adc..41881462a 100644 --- a/src/PersonObjects/IPlayer.ts +++ b/src/PersonObjects/IPlayer.ts @@ -3,7 +3,6 @@ * Used because at the time of implementation, the PlayerObject * cant be converted to TypeScript. */ -import { Resleeve } from "./Resleeving/Resleeve"; import { Sleeve } from "./Sleeve/Sleeve"; import { IMap } from "../types"; @@ -65,7 +64,6 @@ export interface IPlayer { playtimeSinceLastBitnode: number; purchasedServers: any[]; queuedAugmentations: IPlayerOwnedAugmentation[]; - resleeves: Resleeve[]; scriptProdSinceLastAug: number; sleeves: Sleeve[]; sleevesFromCovenant: number; @@ -130,6 +128,8 @@ export interface IPlayer { factionWorkType: string; createProgramName: string; timeWorkedCreateProgram: number; + craftAugmentationName: string; + timeWorkedCraftAugmentation: number; crimeType: string; committingCrimeThruSingFn: boolean; singFnCrimeWorkerScript: WorkerScript | null; @@ -160,6 +160,8 @@ export interface IPlayer { workChaExpGainRate: number; workMoneyLossRate: number; + entropyStacks: number; + // Methods work(numCycles: number): boolean; workPartTime(numCycles: number): boolean; @@ -181,7 +183,7 @@ export interface IPlayer { canAccessBladeburner(): boolean; canAccessCorporation(): boolean; canAccessGang(): boolean; - canAccessResleeving(): boolean; + canAccessGrafting(): boolean; canAfford(cost: number): boolean; gainHackingExp(exp: number): void; gainStrengthExp(exp: number): void; @@ -286,4 +288,8 @@ export interface IPlayer { setMult(name: string, mult: number): void; canAccessCotMG(): boolean; sourceFileLvl(n: number): number; + startCraftAugmentationWork(augmentationName: string, time: number): void; + craftAugmentationWork(numCycles: number): boolean; + finishCraftAugmentationWork(cancelled: boolean): string; + applyEntropy(stacks?: number): void; } diff --git a/src/PersonObjects/Player/PlayerObject.ts b/src/PersonObjects/Player/PlayerObject.ts index 0ecf6628c..65603c3af 100644 --- a/src/PersonObjects/Player/PlayerObject.ts +++ b/src/PersonObjects/Player/PlayerObject.ts @@ -6,7 +6,6 @@ import * as generalMethods from "./PlayerObjectGeneralMethods"; import * as serverMethods from "./PlayerObjectServerMethods"; import { IMap } from "../../types"; -import { Resleeve } from "../Resleeving/Resleeve"; import { Sleeve } from "../Sleeve/Sleeve"; import { IPlayerOwnedSourceFile } from "../../SourceFile/PlayerOwnedSourceFile"; import { Exploit } from "../../Exploits/Exploit"; @@ -72,7 +71,6 @@ export class PlayerObject implements IPlayer { playtimeSinceLastBitnode: number; purchasedServers: any[]; queuedAugmentations: IPlayerOwnedAugmentation[]; - resleeves: Resleeve[]; scriptProdSinceLastAug: number; sleeves: Sleeve[]; sleevesFromCovenant: number; @@ -139,6 +137,8 @@ export class PlayerObject implements IPlayer { factionWorkType: string; createProgramName: string; timeWorkedCreateProgram: number; + craftAugmentationName: string; + timeWorkedCraftAugmentation: number; crimeType: string; committingCrimeThruSingFn: boolean; singFnCrimeWorkerScript: WorkerScript | null; @@ -169,6 +169,8 @@ export class PlayerObject implements IPlayer { workChaExpGainRate: number; workMoneyLossRate: number; + entropyStacks: number; + // Methods work: (numCycles: number) => boolean; workPartTime: (numCycles: number) => boolean; @@ -190,7 +192,7 @@ export class PlayerObject implements IPlayer { canAccessBladeburner: () => boolean; canAccessCorporation: () => boolean; canAccessGang: () => boolean; - canAccessResleeving: () => boolean; + canAccessGrafting: () => boolean; canAfford: (cost: number) => boolean; gainHackingExp: (exp: number) => void; gainStrengthExp: (exp: number) => void; @@ -296,6 +298,10 @@ export class PlayerObject implements IPlayer { setMult: (name: string, mult: number) => void; canAccessCotMG: () => boolean; sourceFileLvl: (n: number) => number; + startCraftAugmentationWork: (augmentationName: string, time: number) => void; + craftAugmentationWork: (numCycles: number) => boolean; + finishCraftAugmentationWork: (cancelled: boolean) => string; + applyEntropy: (stacks?: number) => void; constructor() { //Skills and stats @@ -419,6 +425,9 @@ export class PlayerObject implements IPlayer { this.createProgramName = ""; this.createProgramReqLvl = 0; + this.craftAugmentationName = ""; + this.timeWorkedCraftAugmentation = 0; + this.className = ""; this.crimeType = ""; @@ -457,11 +466,12 @@ export class PlayerObject implements IPlayer { // Sleeves & Re-sleeving this.sleeves = []; - this.resleeves = []; this.sleevesFromCovenant = 0; // # of Duplicate sleeves purchased from the covenan; //bitnode this.bitNodeN = 1; + this.entropyStacks = 0; + //Used to store the last update time. this.lastUpdate = 0; this.lastSave = 0; @@ -541,6 +551,9 @@ export class PlayerObject implements IPlayer { this.startCreateProgramWork = generalMethods.startCreateProgramWork; this.createProgramWork = generalMethods.createProgramWork; this.finishCreateProgramWork = generalMethods.finishCreateProgramWork; + this.startCraftAugmentationWork = generalMethods.startCraftAugmentationWork; + this.craftAugmentationWork = generalMethods.craftAugmentationWork; + this.finishCraftAugmentationWork = generalMethods.finishCraftAugmentationWork; this.startClass = generalMethods.startClass; this.takeClass = generalMethods.takeClass; this.finishClass = generalMethods.finishClass; @@ -577,7 +590,7 @@ export class PlayerObject implements IPlayer { this.gainCodingContractReward = generalMethods.gainCodingContractReward; this.travel = generalMethods.travel; this.gotoLocation = generalMethods.gotoLocation; - this.canAccessResleeving = generalMethods.canAccessResleeving; + this.canAccessGrafting = generalMethods.canAccessGrafting; this.giveExploit = generalMethods.giveExploit; this.giveAchievement = generalMethods.giveAchievement; this.getIntelligenceBonus = generalMethods.getIntelligenceBonus; @@ -611,6 +624,8 @@ export class PlayerObject implements IPlayer { this.canAccessCotMG = generalMethods.canAccessCotMG; this.sourceFileLvl = generalMethods.sourceFileLvl; + + this.applyEntropy = augmentationMethods.applyEntropy; } /** diff --git a/src/PersonObjects/Player/PlayerObjectAugmentationMethods.ts b/src/PersonObjects/Player/PlayerObjectAugmentationMethods.ts index acccb38df..999689eef 100644 --- a/src/PersonObjects/Player/PlayerObjectAugmentationMethods.ts +++ b/src/PersonObjects/Player/PlayerObjectAugmentationMethods.ts @@ -5,6 +5,8 @@ import { IPlayer } from "../IPlayer"; import { Augmentation } from "../../Augmentation/Augmentation"; +import { calculateEntropy } from "../Grafting/EntropyAccumulation"; + export function hasAugmentation(this: IPlayer, aug: string | Augmentation, installed = false): boolean { const augName: string = aug instanceof Augmentation ? aug.name : aug; @@ -24,3 +26,14 @@ export function hasAugmentation(this: IPlayer, aug: string | Augmentation, insta return false; } + +export function applyEntropy(this: IPlayer, stacks = 1): void { + // Re-apply all multipliers + this.reapplyAllAugmentations(); + this.reapplyAllSourceFiles(); + + const newMultipliers = calculateEntropy(this, stacks); + for (const [mult, val] of Object.entries(newMultipliers)) { + this.setMult(mult, val); + } +} diff --git a/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx b/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx index 968e50cde..2a862085e 100644 --- a/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx +++ b/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx @@ -121,8 +121,6 @@ export function prestigeAugmentation(this: PlayerObject): void { this.queuedAugmentations = []; - this.resleeves = []; - const numSleeves = Math.min(3, SourceFileFlags[10] + (this.bitNodeN === 10 ? 1 : 0)) + this.sleevesFromCovenant; if (this.sleeves.length > numSleeves) this.sleeves.length = numSleeves; for (let i = this.sleeves.length; i < numSleeves; i++) { @@ -182,6 +180,7 @@ export function prestigeAugmentation(this: PlayerObject): void { } export function prestigeSourceFile(this: IPlayer): void { + this.entropyStacks = 0; this.prestigeAugmentation(); this.karma = 0; // Duplicate sleeves are reset to level 1 every Bit Node (but the number of sleeves you have persists) @@ -529,10 +528,12 @@ export function resetWorkStatus(this: IPlayer, generalType?: string, group?: str this.timeWorked = 0; this.timeWorkedCreateProgram = 0; + this.timeWorkedCraftAugmentation = 0; this.currentWorkFactionName = ""; this.currentWorkFactionDescription = ""; this.createProgramName = ""; + this.craftAugmentationName = ""; this.className = ""; this.workType = ""; } @@ -609,6 +610,10 @@ export function process(this: IPlayer, router: IRouter, numCycles = 1): void { if (this.workPartTime(numCycles)) { router.toCity(); } + } else if (this.workType === CONSTANTS.WorkTypeCraftAugmentation) { + if (this.craftAugmentationWork(numCycles)) { + router.toGrafting(); + } } else if (this.work(numCycles)) { router.toCity(); } @@ -1329,6 +1334,61 @@ export function finishCreateProgramWork(this: IPlayer, cancelled: boolean): stri this.resetWorkStatus(); return "You've finished creating " + programName + "! The new program can be found on your home computer."; } + +export function startCraftAugmentationWork( + this: IPlayer, + augmentationName: string, + time: number, +): void { + this.resetWorkStatus() + this.isWorking = true; + this.workType = CONSTANTS.WorkTypeCraftAugmentation; + + this.timeNeededToCompleteWork = time; + this.craftAugmentationName = augmentationName; +} + +export function craftAugmentationWork(this: IPlayer, numCycles: number): boolean { + let focusBonus = 1; + if (!this.hasAugmentation(AugmentationNames.NeuroreceptorManager)) { + focusBonus = this.focus ? 1 : CONSTANTS.BaseFocusBonus; + } + + let skillMult = 1 + (this.getIntelligenceBonus(3) - 1) / 3; + skillMult *= focusBonus; + + this.timeWorked += CONSTANTS._idleSpeed * numCycles; + this.timeWorkedCraftAugmentation += CONSTANTS._idleSpeed * numCycles * skillMult; + + if (this.timeWorkedCraftAugmentation >= this.timeNeededToCompleteWork) { + this.finishCraftAugmentationWork(false); + return true; + } + return false; +} + +export function finishCraftAugmentationWork(this: IPlayer, cancelled: boolean): string { + const augName = this.craftAugmentationName; + if (cancelled === false) { + dialogBoxCreate(`You've finished crafting ${augName}.
The augmentation has been grafted to your body, but you feel a bit off.`) + + applyAugmentation(Augmentations[augName]); + this.entropyStacks += 1; + this.applyEntropy(this.entropyStacks); + } else { + dialogBoxCreate(`You cancelled the crafting of ${augName}.
Your money was not returned to you.`) + } + + // Intelligence gain + if (!cancelled) { + this.gainIntelligenceExp((CONSTANTS.IntelligenceCraftBaseExpGain * this.timeWorked) / 10000); + } + + this.isWorking = false; + this.resetWorkStatus(); + return `Crafting of ${augName} has ended.` +} + /* Studying/Taking Classes */ export function startClass(this: IPlayer, costMult: number, expMult: number, className: string): void { this.resetWorkStatus(); @@ -1507,20 +1567,20 @@ export function finishCrime(this: IPlayer, cancelled: boolean): string { if (ws.disableLogs.ALL == null && ws.disableLogs.commitCrime == null) { ws.scriptRef.log( "SUCCESS: Crime successful! Gained " + - numeralWrapper.formatMoney(this.workMoneyGained) + - ", " + - numeralWrapper.formatExp(this.workHackExpGained) + - " hack exp, " + - numeralWrapper.formatExp(this.workStrExpGained) + - " str exp, " + - numeralWrapper.formatExp(this.workDefExpGained) + - " def exp, " + - numeralWrapper.formatExp(this.workDexExpGained) + - " dex exp, " + - numeralWrapper.formatExp(this.workAgiExpGained) + - " agi exp, " + - numeralWrapper.formatExp(this.workChaExpGained) + - " cha exp.", + numeralWrapper.formatMoney(this.workMoneyGained) + + ", " + + numeralWrapper.formatExp(this.workHackExpGained) + + " hack exp, " + + numeralWrapper.formatExp(this.workStrExpGained) + + " str exp, " + + numeralWrapper.formatExp(this.workDefExpGained) + + " def exp, " + + numeralWrapper.formatExp(this.workDexExpGained) + + " dex exp, " + + numeralWrapper.formatExp(this.workAgiExpGained) + + " agi exp, " + + numeralWrapper.formatExp(this.workChaExpGained) + + " cha exp.", ); } } else { @@ -1559,18 +1619,18 @@ export function finishCrime(this: IPlayer, cancelled: boolean): string { if (ws.disableLogs.ALL == null && ws.disableLogs.commitCrime == null) { ws.scriptRef.log( "FAIL: Crime failed! Gained " + - numeralWrapper.formatExp(this.workHackExpGained) + - " hack exp, " + - numeralWrapper.formatExp(this.workStrExpGained) + - " str exp, " + - numeralWrapper.formatExp(this.workDefExpGained) + - " def exp, " + - numeralWrapper.formatExp(this.workDexExpGained) + - " dex exp, " + - numeralWrapper.formatExp(this.workAgiExpGained) + - " agi exp, " + - numeralWrapper.formatExp(this.workChaExpGained) + - " cha exp.", + numeralWrapper.formatExp(this.workHackExpGained) + + " hack exp, " + + numeralWrapper.formatExp(this.workStrExpGained) + + " str exp, " + + numeralWrapper.formatExp(this.workDefExpGained) + + " def exp, " + + numeralWrapper.formatExp(this.workDexExpGained) + + " dex exp, " + + numeralWrapper.formatExp(this.workAgiExpGained) + + " agi exp, " + + numeralWrapper.formatExp(this.workChaExpGained) + + " cha exp.", ); } } else { @@ -2640,7 +2700,7 @@ export function gotoLocation(this: IPlayer, to: LocationName): boolean { return true; } -export function canAccessResleeving(this: IPlayer): boolean { +export function canAccessGrafting(this: IPlayer): boolean { return this.bitNodeN === 10 || SourceFileFlags[10] > 0; } diff --git a/src/PersonObjects/Resleeving/README.md b/src/PersonObjects/Resleeving/README.md deleted file mode 100644 index ba3116b94..000000000 --- a/src/PersonObjects/Resleeving/README.md +++ /dev/null @@ -1,10 +0,0 @@ -Implements the Re-sleeving feature, which allows players to purchase a new body -that comes with pre-existing Augmentations and experience. Note that purchasing -a new body causes you to lose all of your old Augmentations and experience - -This feature is introduced in BitNode-10, and destroying BitNode-10 allows -the user to use it in other BitNodes (provided that they purchase the required -cortical stack Augmentation) - -While they are based on the same concept, this feature is different than the -"Duplicate Sleeve" mechanic (which is referred to as just "Sleeve" in the source code). diff --git a/src/PersonObjects/Resleeving/Resleeve.ts b/src/PersonObjects/Resleeving/Resleeve.ts deleted file mode 100644 index 165ab2bda..000000000 --- a/src/PersonObjects/Resleeving/Resleeve.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Implements the Resleeve class, which defines a new body - * that the player can "re-sleeve" into. - */ -import { Person } from "../Person"; - -import { Augmentation } from "../../Augmentation/Augmentation"; -import { Augmentations } from "../../Augmentation/Augmentations"; - -import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; - -export class Resleeve extends Person { - constructor() { - super(); - } - - getCost(): number { - // Each experience point adds this to the cost - const CostPerExp = 25e3; - - // Final cost is multiplied by this constant ^ # Augs - const NumAugsExponent = 1.2; - - // Get total exp in this re-sleeve - const totalExp: number = - this.hacking_exp + - this.strength_exp + - this.defense_exp + - this.dexterity_exp + - this.agility_exp + - this.charisma_exp; - - // Get total base Augmentation cost for this re-sleeve - let totalAugmentationCost = 0; - for (let i = 0; i < this.augmentations.length; ++i) { - const aug: Augmentation | null = Augmentations[this.augmentations[i].name]; - if (aug == null) { - console.error(`Could not find Augmentation ${this.augmentations[i].name}`); - continue; - } - totalAugmentationCost += aug.startingCost; - } - - return totalExp * CostPerExp + totalAugmentationCost * Math.pow(NumAugsExponent, this.augmentations.length); - } - - /** - * Serialize the current object to a JSON save state. - */ - toJSON(): any { - return Generic_toJSON("Resleeve", this); - } - - /** - * Initiatizes a Resleeve object from a JSON save state. - */ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): Resleeve { - return Generic_fromJSON(Resleeve, value.data); - } -} - -Reviver.constructors.Resleeve = Resleeve; diff --git a/src/PersonObjects/Resleeving/Resleeving.ts b/src/PersonObjects/Resleeving/Resleeving.ts deleted file mode 100644 index 2d3f2daa6..000000000 --- a/src/PersonObjects/Resleeving/Resleeving.ts +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Implements the Re-sleeving mechanic for BitNode-10. - * This allows the player to purchase and "use" new sleeves at VitaLife. - * These new sleeves come with different starting experience and Augmentations - * The cost of these new sleeves scales based on the exp and Augs. - * - * Note that this is different from the "Sleeve mechanic". The "Sleeve" mechanic - * provides new sleeves, essentially clones. This Re-sleeving mechanic lets - * the player purchase a new body with pre-existing Augmentations and experience - * - * As of right now, this feature is only available in BitNode 10 - */ -import { Resleeve } from "./Resleeve"; -import { IPlayer } from "../IPlayer"; - -import { Augmentation } from "../../Augmentation/Augmentation"; -import { Augmentations } from "../../Augmentation/Augmentations"; -import { IPlayerOwnedAugmentation, PlayerOwnedAugmentation } from "../../Augmentation/PlayerOwnedAugmentation"; -import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; - -import { getRandomInt } from "../../utils/helpers/getRandomInt"; - -// Executes the actual re-sleeve when one is purchased -export function purchaseResleeve(r: Resleeve, p: IPlayer): boolean { - const cost: number = r.getCost(); - if (!p.canAfford(cost)) { - return false; - } - p.loseMoney(cost, "other"); - - // Set the player's exp - p.hacking_exp = r.hacking_exp; - p.strength_exp = r.strength_exp; - p.defense_exp = r.defense_exp; - p.dexterity_exp = r.dexterity_exp; - p.agility_exp = r.agility_exp; - p.charisma_exp = r.charisma_exp; - - // Reset Augmentation "owned" data - for (const augKey of Object.keys(Augmentations)) { - Augmentations[augKey].owned = false; - } - - // Clear all of the player's augmentations, except the NeuroFlux Governor - // which is kept - for (let i = p.augmentations.length - 1; i >= 0; --i) { - if (p.augmentations[i].name !== AugmentationNames.NeuroFluxGovernor) { - p.augmentations.splice(i, 1); - } else { - // NeuroFlux Governor - Augmentations[AugmentationNames.NeuroFluxGovernor].owned = true; - } - } - - for (let i = 0; i < r.augmentations.length; ++i) { - p.augmentations.push(new PlayerOwnedAugmentation(r.augmentations[i].name)); - Augmentations[r.augmentations[i].name].owned = true; - } - - // The player's purchased Augmentations should remain the same, but any purchased - // Augmentations that are given by the resleeve should be removed so there are no duplicates - for (let i = p.queuedAugmentations.length - 1; i >= 0; --i) { - const name: string = p.queuedAugmentations[i].name; - - if ( - p.augmentations.filter((e: IPlayerOwnedAugmentation) => { - return e.name !== AugmentationNames.NeuroFluxGovernor && e.name === name; - }).length >= 1 - ) { - p.queuedAugmentations.splice(i, 1); - } - } - - p.reapplyAllAugmentations(true); - p.reapplyAllSourceFiles(); //Multipliers get reset, so have to re-process source files too - return true; -} - -// Creates all of the Re-sleeves that will be available for purchase at VitaLife -export function generateResleeves(): Resleeve[] { - const NumResleeves = 40; // Total number of Resleeves to generate - - const ret: Resleeve[] = []; - for (let i = 0; i < NumResleeves; ++i) { - // i will be a number indicating how "powerful" the Re-sleeve should be - const r: Resleeve = new Resleeve(); - - // Generate experience - const expMult: number = 5 * i + 1; - r.hacking_exp = expMult * getRandomInt(1000, 5000); - r.strength_exp = expMult * getRandomInt(1000, 5000); - r.defense_exp = expMult * getRandomInt(1000, 5000); - r.dexterity_exp = expMult * getRandomInt(1000, 5000); - r.agility_exp = expMult * getRandomInt(1000, 5000); - r.charisma_exp = expMult * getRandomInt(1000, 5000); - - // Generate Augs - // Augmentation prequisites will be ignored for this - const baseNumAugs: number = Math.max(2, Math.ceil((i + 3) / 2)); - const numAugs: number = getRandomInt(baseNumAugs, baseNumAugs + 2); - const augKeys: string[] = Object.keys(Augmentations); - for (let a = 0; a < numAugs; ++a) { - // Get a random aug - const randIndex: number = getRandomInt(0, augKeys.length - 1); - const randKey: string = augKeys[randIndex]; - - // Forbidden augmentations - const forbidden = [ - AugmentationNames.TheRedPill, - AugmentationNames.NeuroFluxGovernor, - AugmentationNames.StaneksGift1, - AugmentationNames.StaneksGift2, - AugmentationNames.StaneksGift3, - ]; - if (forbidden.includes(randKey)) { - continue; - } - - const randAug: Augmentation | null = Augmentations[randKey]; - if (randAug === null) throw new Error(`null augmentation: ${randKey}`); - r.augmentations.push({ name: randAug.name, level: 1 }); - r.applyAugmentation(Augmentations[randKey]); - r.updateStatLevels(); - - // Remove Augmentation so that there are no duplicates - augKeys.splice(randIndex, 1); - } - - ret.push(r); - } - - return ret; -} diff --git a/src/PersonObjects/Resleeving/ui/ResleeveElem.tsx b/src/PersonObjects/Resleeving/ui/ResleeveElem.tsx deleted file mode 100644 index 69d58961b..000000000 --- a/src/PersonObjects/Resleeving/ui/ResleeveElem.tsx +++ /dev/null @@ -1,166 +0,0 @@ -import React, { useState } from "react"; -import { IPlayer } from "../../IPlayer"; -import { Resleeve } from "../Resleeve"; -import { Augmentations } from "../../../Augmentation/Augmentations"; -import { purchaseResleeve } from "../Resleeving"; -import { Money } from "../../../ui/React/Money"; - -import { numeralWrapper } from "../../../ui/numeralFormat"; -import { dialogBoxCreate } from "../../../ui/React/DialogBox"; - -import Typography from "@mui/material/Typography"; -import Paper from "@mui/material/Paper"; -import Button from "@mui/material/Button"; -import Select, { SelectChangeEvent } from "@mui/material/Select"; -import MenuItem from "@mui/material/MenuItem"; -import Grid from "@mui/material/Grid"; - -interface IProps { - resleeve: Resleeve; - player: IPlayer; -} - -export function ResleeveElem(props: IProps): React.ReactElement { - const [aug, setAug] = useState(props.resleeve.augmentations[0].name); - - function openStats(): void { - dialogBoxCreate( - <> - - Total Multipliers: - - - Hacking Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_mult)} -
- Hacking Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_exp_mult)} -
- Strength Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.strength_mult)} -
- Strength Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.strength_exp_mult)} -
- Defense Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.defense_mult)} -
- Defense Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.defense_exp_mult)} -
- Dexterity Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.dexterity_mult)} -
- Dexterity Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.dexterity_exp_mult)} -
- Agility Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.agility_mult)} -
- Agility Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.agility_exp_mult)} -
- Charisma Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.charisma_mult)} -
- Charisma Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.charisma_exp_mult)} -
- Hacking Chance multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_chance_mult)} -
- Hacking Speed multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_speed_mult)} -
- Hacking Money multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_money_mult)} -
- Hacking Growth multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_grow_mult)} -
- Salary multiplier: {numeralWrapper.formatPercentage(props.resleeve.work_money_mult)} -
- Company Reputation Gain multiplier: {numeralWrapper.formatPercentage(props.resleeve.company_rep_mult)} -
- Faction Reputation Gain multiplier: {numeralWrapper.formatPercentage(props.resleeve.faction_rep_mult)} -
- Crime Money multiplier: {numeralWrapper.formatPercentage(props.resleeve.crime_money_mult)} -
- Crime Success multiplier: {numeralWrapper.formatPercentage(props.resleeve.crime_success_mult)} -
- Hacknet Income multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacknet_node_money_mult)} -
- Hacknet Purchase Cost multiplier: - {numeralWrapper.formatPercentage(props.resleeve.hacknet_node_purchase_cost_mult)} -
- Hacknet Level Upgrade Cost multiplier: - {numeralWrapper.formatPercentage(props.resleeve.hacknet_node_level_cost_mult)} -
- Hacknet Ram Upgrade Cost multiplier: - {numeralWrapper.formatPercentage(props.resleeve.hacknet_node_ram_cost_mult)} -
- Hacknet Core Upgrade Cost multiplier: - {numeralWrapper.formatPercentage(props.resleeve.hacknet_node_core_cost_mult)} -
- Bladeburner Max Stamina multiplier: - {numeralWrapper.formatPercentage(props.resleeve.bladeburner_max_stamina_mult)} -
- Bladeburner Stamina Gain multiplier: - {numeralWrapper.formatPercentage(props.resleeve.bladeburner_stamina_gain_mult)} -
- Bladeburner Field Analysis multiplier: - {numeralWrapper.formatPercentage(props.resleeve.bladeburner_analysis_mult)} -
- Bladeburner Success Chance multiplier: - {numeralWrapper.formatPercentage(props.resleeve.bladeburner_success_chance_mult)} -
- , - ); - } - - function onAugChange(event: SelectChangeEvent): void { - setAug(event.target.value); - } - - const currentAug = Augmentations[aug]; - const cost = props.resleeve.getCost(); - - function purchase(): void { - if (!purchaseResleeve(props.resleeve, props.player)) return; - dialogBoxCreate( - <> - You re-sleeved for ! - , - ); - } - - return ( - - - - - Hacking: {numeralWrapper.formatSkill(props.resleeve.hacking)} ( - {numeralWrapper.formatExp(props.resleeve.hacking_exp)} exp) -
- Strength: {numeralWrapper.formatSkill(props.resleeve.strength)} ( - {numeralWrapper.formatExp(props.resleeve.strength_exp)} exp) -
- Defense: {numeralWrapper.formatSkill(props.resleeve.defense)} ( - {numeralWrapper.formatExp(props.resleeve.defense_exp)} exp) -
- Dexterity: {numeralWrapper.formatSkill(props.resleeve.dexterity)} ( - {numeralWrapper.formatExp(props.resleeve.dexterity_exp)} exp) -
- Agility: {numeralWrapper.formatSkill(props.resleeve.agility)} ( - {numeralWrapper.formatExp(props.resleeve.agility_exp)} exp) -
- Charisma: {numeralWrapper.formatSkill(props.resleeve.charisma)} ( - {numeralWrapper.formatExp(props.resleeve.charisma_exp)} exp) -
# Augmentations: {props.resleeve.augmentations.length} -
- -
- - - {currentAug !== undefined && currentAug.info} - - - - It costs to purchase this Sleeve. - - - -
-
- ); -} diff --git a/src/PersonObjects/Resleeving/ui/ResleeveRoot.tsx b/src/PersonObjects/Resleeving/ui/ResleeveRoot.tsx deleted file mode 100644 index 5e0a7d139..000000000 --- a/src/PersonObjects/Resleeving/ui/ResleeveRoot.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import React, { useState } from "react"; - -import { generateResleeves } from "../Resleeving"; -import { Resleeve } from "../Resleeve"; -import { ResleeveElem } from "./ResleeveElem"; -import { use } from "../../../ui/Context"; -import Typography from "@mui/material/Typography"; -import Select, { SelectChangeEvent } from "@mui/material/Select"; -import MenuItem from "@mui/material/MenuItem"; -import Box from "@mui/material/Box"; - -const SortOption: { - [key: string]: string | undefined; - Cost: string; - Hacking: string; - Strength: string; - Defense: string; - Dexterity: string; - Agility: string; - Charisma: string; - AverageCombatStats: string; - AverageAllStats: string; - TotalNumAugmentations: string; -} = { - Cost: "Cost", - Hacking: "Hacking Level", - Strength: "Strength Level", - Defense: "Defense Level", - Dexterity: "Dexterity Level", - Agility: "Agility Level", - Charisma: "Charisma Level", - AverageCombatStats: "Average Combat Stats", - AverageAllStats: "Average Stats", - TotalNumAugmentations: "Number of Augmentations", -}; - -// Helper function for averaging -function getAverage(...values: number[]): number { - let sum = 0; - for (let i = 0; i < values.length; ++i) { - sum += values[i]; - } - - return sum / values.length; -} - -const SortFunctions: { - [key: string]: ((a: Resleeve, b: Resleeve) => number) | undefined; - Cost: (a: Resleeve, b: Resleeve) => number; - Hacking: (a: Resleeve, b: Resleeve) => number; - Strength: (a: Resleeve, b: Resleeve) => number; - Defense: (a: Resleeve, b: Resleeve) => number; - Dexterity: (a: Resleeve, b: Resleeve) => number; - Agility: (a: Resleeve, b: Resleeve) => number; - Charisma: (a: Resleeve, b: Resleeve) => number; - AverageCombatStats: (a: Resleeve, b: Resleeve) => number; - AverageAllStats: (a: Resleeve, b: Resleeve) => number; - TotalNumAugmentations: (a: Resleeve, b: Resleeve) => number; -} = { - Cost: (a: Resleeve, b: Resleeve): number => a.getCost() - b.getCost(), - Hacking: (a: Resleeve, b: Resleeve): number => a.hacking - b.hacking, - Strength: (a: Resleeve, b: Resleeve): number => a.strength - b.strength, - Defense: (a: Resleeve, b: Resleeve): number => a.defense - b.defense, - Dexterity: (a: Resleeve, b: Resleeve): number => a.dexterity - b.dexterity, - Agility: (a: Resleeve, b: Resleeve): number => a.agility - b.agility, - Charisma: (a: Resleeve, b: Resleeve): number => a.charisma - b.charisma, - AverageCombatStats: (a: Resleeve, b: Resleeve): number => - getAverage(a.strength, a.defense, a.dexterity, a.agility) - - getAverage(b.strength, b.defense, b.dexterity, b.agility), - AverageAllStats: (a: Resleeve, b: Resleeve): number => - getAverage(a.hacking, a.strength, a.defense, a.dexterity, a.agility, a.charisma) - - getAverage(b.hacking, b.strength, b.defense, b.dexterity, b.agility, b.charisma), - TotalNumAugmentations: (a: Resleeve, b: Resleeve): number => a.augmentations.length - b.augmentations.length, -}; - -export function ResleeveRoot(): React.ReactElement { - const player = use.Player(); - const [sort, setSort] = useState(SortOption.Cost); - // Randomly create all Resleeves if they dont already exist - if (player.resleeves.length === 0) { - player.resleeves = generateResleeves(); - } - - function onSortChange(event: SelectChangeEvent): void { - setSort(event.target.value); - } - - const sortFunction = SortFunctions[sort]; - if (sortFunction === undefined) throw new Error(`sort function '${sort}' is undefined`); - player.resleeves.sort(sortFunction); - - return ( - <> - - Re-sleeving is the process of digitizing and transferring your consciousness into a new human body, or 'sleeve'. - Here at VitaLife, you can purchase new specially-engineered bodies for the re-sleeve process. Many of these - bodies even come with genetic and cybernetic Augmentations! -
-
- Re-sleeving will change your experience for every stat. It will also REMOVE all of your currently-installed - Augmentations, and replace them with the ones provided by the purchased sleeve. However, Augmentations that you - have purchased but not installed will NOT be removed. If you have purchased an Augmentation and then re-sleeve - into a body which already has that Augmentation, it will be removed (since you cannot have duplicate - Augmentations). -
-
- NOTE: The stats and multipliers displayed on this page do NOT include your bonuses from Source-File. -
- - Sort By: - - - {player.resleeves.map((resleeve, i) => ( - - ))} - - ); -} diff --git a/src/Prestige.ts b/src/Prestige.ts index ab8641a85..066fb1d59 100755 --- a/src/Prestige.ts +++ b/src/Prestige.ts @@ -108,6 +108,9 @@ export function prestigeAugmentation(): void { // Messages initMessages(); + // Apply entropy from grafting + Player.applyEntropy(Player.entropyStacks); + // Gang const gang = Player.gang; if (Player.inGang() && gang !== null) { diff --git a/src/Script/RamCalculations.ts b/src/Script/RamCalculations.ts index 0bb432581..94f25aaea 100644 --- a/src/Script/RamCalculations.ts +++ b/src/Script/RamCalculations.ts @@ -225,6 +225,9 @@ async function parseOnlyRamCalculate( } else if (ref in workerScript.env.vars.ui) { func = workerScript.env.vars.ui[ref]; refDetail = `ui.${ref}`; + } else if (ref in workerScript.env.vars.grafting) { + func = workerScript.env.vars.grafting[ref]; + refDetail = `grafting.${ref}`; } else { func = workerScript.env.vars[ref]; refDetail = `${ref}`; diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index 1782e8c8c..cc6d664cd 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -95,6 +95,7 @@ interface Player { tor: boolean; hasCorporation: boolean; inBladeburner: boolean; + entropyStacks: number; } /** @@ -3719,6 +3720,43 @@ export interface Sleeve { purchaseSleeveAug(sleeveNumber: number, augName: string): boolean; } +export interface Grafting { + /** + * Retrieve the crafting cost of an aug. + * @remarks + * RAM cost: 3.75 GB + * + * @param augName - Name of the aug to check the price of. Must be an exact match. + * @returns The cost required to craft the named augmentation. + * @throws Will error if an invalid Augmentation name is provided. + */ + getAugmentationCraftPrice(augName: string): number; + + /** + * Retrieves the time required to craft an aug. + * @remarks + * RAM cost: 3.75 GB + * + * @param augName - Name of the aug to check the crafting time of. Must be an exact match. + * @returns The time required, in millis, to craft the named augmentation. + * @throws Will error if an invalid Augmentation name is provided. + */ + getAugmentationCraftTime(augName: string): number; + + /** + * Begins crafting the named aug. You must be in New Tokyo to use this. + * @remarks + * RAM cost: 7.5 GB + * + * @param augName - The name of the aug to begin crafting. Must be an exact match. + * @param focus - Acquire player focus on this Augmentation crafting. Optional. Defaults to true. + * @returns True if the aug successfully began crafting, false otherwise (e.g. not enough money, or + * invalid Augmentation name provided). + * @throws Will error if called while you are not in New Tokyo. + */ + craftAugmentation(augName: string, focus?: boolean): boolean; +} + /** * Skills formulas * @public @@ -4280,6 +4318,13 @@ export interface NS extends Singularity { */ readonly ui: UserInterface; + /** + * Namespace for grafting functions. + * @remarks + * RAM cost: 0 GB + */ + readonly grafting: Grafting; + /** * Arguments passed into the script. * diff --git a/src/Sidebar/ui/SidebarRoot.tsx b/src/Sidebar/ui/SidebarRoot.tsx index 81f724304..32bd2b18a 100644 --- a/src/Sidebar/ui/SidebarRoot.tsx +++ b/src/Sidebar/ui/SidebarRoot.tsx @@ -617,7 +617,7 @@ export function SidebarRoot(props: IProps): React.ReactElement { key={"City"} className={clsx({ [classes.active]: - props.page === Page.City || props.page === Page.Resleeves || props.page === Page.Location, + props.page === Page.City || props.page === Page.Grafting || props.page === Page.Location, })} onClick={clickCity} > diff --git a/src/SourceFile/SourceFiles.tsx b/src/SourceFile/SourceFiles.tsx index 20cc9f452..a8bd75811 100644 --- a/src/SourceFile/SourceFiles.tsx +++ b/src/SourceFile/SourceFiles.tsx @@ -167,8 +167,8 @@ SourceFiles["SourceFile10"] = new SourceFile( 10, ( <> - This Source-File unlocks Sleeve technology in other BitNodes. Each level of this Source-File also grants you a - Duplicate Sleeve + This Source-File unlocks Sleeve technology, and the Grafting API in other BitNodes. + Each level of this Source-File also grants you a Duplicate Sleeve ), ); diff --git a/src/engine.tsx b/src/engine.tsx index 4b4a38e52..c8303f54c 100644 --- a/src/engine.tsx +++ b/src/engine.tsx @@ -263,6 +263,9 @@ const Engine: { initSymbolToStockMap(); } + // Apply penalty for entropy accumulation + Player.applyEntropy(Player.entropyStacks); + // Calculate the number of cycles have elapsed while offline Engine._lastUpdate = new Date().getTime(); const lastUpdate = Player.lastUpdate; @@ -302,6 +305,8 @@ const Engine: { Player.commitCrime(numCyclesOffline); } else if (Player.workType == CONSTANTS.WorkTypeCompanyPartTime) { Player.workPartTime(numCyclesOffline); + } else if (Player.workType === CONSTANTS.WorkTypeCraftAugmentation) { + Player.craftAugmentationWork(numCyclesOffline); } else { Player.work(numCyclesOffline); } diff --git a/src/ui/GameRoot.tsx b/src/ui/GameRoot.tsx index 40790ab3e..ed0114ccc 100644 --- a/src/ui/GameRoot.tsx +++ b/src/ui/GameRoot.tsx @@ -42,7 +42,7 @@ import { BladeburnerRoot } from "../Bladeburner/ui/BladeburnerRoot"; import { GangRoot } from "../Gang/ui/GangRoot"; import { CorporationRoot } from "../Corporation/ui/CorporationRoot"; import { InfiltrationRoot } from "../Infiltration/ui/InfiltrationRoot"; -import { ResleeveRoot } from "../PersonObjects/Resleeving/ui/ResleeveRoot"; +import { GraftingRoot } from "../PersonObjects/Grafting/ui/GraftingRoot"; import { WorkInProgressRoot } from "./WorkInProgressRoot"; import { GameOptionsRoot } from "./React/GameOptionsRoot"; import { SleeveRoot } from "../PersonObjects/Sleeve/ui/SleeveRoot"; @@ -135,7 +135,7 @@ export let Router: IRouter = { toInfiltration: uninitialized, toJob: uninitialized, toMilestones: uninitialized, - toResleeves: uninitialized, + toGrafting: uninitialized, toScriptEditor: uninitialized, toSleeves: uninitialized, toStockMarket: uninitialized, @@ -226,7 +226,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme toGang: () => setPage(Page.Gang), toHacknetNodes: () => setPage(Page.Hacknet), toMilestones: () => setPage(Page.Milestones), - toResleeves: () => setPage(Page.Resleeves), + toGrafting: () => setPage(Page.Grafting), toScriptEditor: (files: Record, options?: ScriptEditorRouteOptions) => { setEditorOptions({ files, @@ -429,8 +429,8 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme mainPage = ; break; } - case Page.Resleeves: { - mainPage = ; + case Page.Grafting: { + mainPage = ; break; } case Page.Travel: { diff --git a/src/ui/React/CharacterOverview.tsx b/src/ui/React/CharacterOverview.tsx index f653df270..5cc6ba828 100644 --- a/src/ui/React/CharacterOverview.tsx +++ b/src/ui/React/CharacterOverview.tsx @@ -143,51 +143,66 @@ function Work(): React.ReactElement { let details = <>; let header = <>; let innerText = <>; - if (player.workType === CONSTANTS.WorkTypeCompanyPartTime || player.workType === CONSTANTS.WorkTypeCompany) { - details = ( - <> - {player.jobs[player.companyName]} at {player.companyName} - - ); - header = ( - <> - Working at {player.companyName} - - ); - innerText = ( - <> - + rep - - ); - } else if (player.workType === CONSTANTS.WorkTypeFaction) { - details = ( - <> - {player.factionWorkType} for {player.currentWorkFactionName} - - ); - header = ( - <> - Working for {player.currentWorkFactionName} - - ); - innerText = ( - <> - + rep - - ); - } else if (player.workType === CONSTANTS.WorkTypeStudyClass) { - details = <>{player.workType}; - header = <>You are {player.className}; - innerText = <>{convertTimeMsToTimeElapsedString(player.timeWorked)}; - } else if (player.workType === CONSTANTS.WorkTypeCreateProgram) { - details = <>Coding {player.createProgramName}; - header = <>Creating a program; - innerText = ( - <> - {player.createProgramName}{" "} - {((player.timeWorkedCreateProgram / player.timeNeededToCompleteWork) * 100).toFixed(2)}% - - ); + switch (player.workType) { + case CONSTANTS.WorkTypeCompanyPartTime: + case CONSTANTS.WorkTypeCompany: + details = ( + <> + {player.jobs[player.companyName]} at {player.companyName} + + ); + header = ( + <> + Working at {player.companyName} + + ); + innerText = ( + <> + + rep + + ); + break; + case CONSTANTS.WorkTypeFaction: + details = ( + <> + {player.factionWorkType} for {player.currentWorkFactionName} + + ); + header = ( + <> + Working for {player.currentWorkFactionName} + + ); + innerText = ( + <> + + rep + + ); + break; + case CONSTANTS.WorkTypeStudyClass: + details = <>{player.workType}; + header = <>You are {player.className}; + innerText = <>{convertTimeMsToTimeElapsedString(player.timeWorked)}; + break; + case CONSTANTS.WorkTypeCreateProgram: + details = <>Coding {player.createProgramName}; + header = <>Creating a program; + innerText = ( + <> + {player.createProgramName}{" "} + {((player.timeWorkedCreateProgram / player.timeNeededToCompleteWork) * 100).toFixed(2)}% + + ); + break; + case CONSTANTS.WorkTypeCraftAugmentation: + details = <>Crafting {player.craftAugmentationName}; + header = <>Crafting an Augmentation; + innerText = ( + <> + {((player.timeWorkedCraftAugmentation / player.timeNeededToCompleteWork) * 100).toFixed(2)}% + {" "}done + + ); } return ( diff --git a/src/ui/Router.ts b/src/ui/Router.ts index 2e4613611..e761d917c 100644 --- a/src/ui/Router.ts +++ b/src/ui/Router.ts @@ -23,7 +23,7 @@ export enum Page { Job, Milestones, Options, - Resleeves, + Grafting, Sleeves, Stats, StockMarket, @@ -74,7 +74,7 @@ export interface IRouter { toInfiltration(location: Location): void; toJob(): void; toMilestones(): void; - toResleeves(): void; + toGrafting(): void; toScriptEditor(files?: Record, options?: ScriptEditorRouteOptions): void; toSleeves(): void; toStockMarket(): void; diff --git a/src/ui/WorkInProgressRoot.tsx b/src/ui/WorkInProgressRoot.tsx index b1d2cf871..e263790e4 100644 --- a/src/ui/WorkInProgressRoot.tsx +++ b/src/ui/WorkInProgressRoot.tsx @@ -41,8 +41,8 @@ export function WorkInProgressRoot(): React.ReactElement { return ( <> - You have not joined {player.currentWorkFactionName || "(Faction not found)"} yet or cannot work at this time, - please try again if you think this should have worked + You have not joined {player.currentWorkFactionName || "(Faction not found)"} yet or cannot work at this + time, please try again if you think this should have worked @@ -483,6 +483,42 @@ export function WorkInProgressRoot(): React.ReactElement { ); } + if (player.craftAugmentationName !== "") { + function cancel(): void { + player.finishCraftAugmentationWork(true); + router.toTerminal(); + } + function unfocus(): void { + router.toTerminal(); + player.stopFocusing(); + } + return ( + + + + You are currently working on crafting {player.craftAugmentationName}. +
+
+ You have been working for {convertTimeMsToTimeElapsedString(player.timeWorked)} +
+
+ The augmentation is{" "} + {((player.timeWorkedCraftAugmentation / player.timeNeededToCompleteWork) * 100).toFixed(2)}% done being + crafted. +
+ If you cancel, your work will not be saved, and the money you spent will not be returned. +
+
+ + + + +
+ ); + } + if (!player.workType) router.toTerminal(); return <>;