mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-12-20 21:25:47 +01:00
Merge pull request #3170 from nickofolas/feature/grafting
[Feature] Grafting
This commit is contained in:
commit
fb1bce579f
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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 <https://github.com/danielyxie/bitburner/blob/dev/markdown/bitburner.sleeve.md>`_ 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). |
|
||||
|
@ -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 {
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<List dense>
|
||||
{player.entropyStacks > 0 &&
|
||||
(() => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<Box component={Paper}>
|
||||
<ListItemButton onClick={() => setOpen((old) => !old)}>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Typography color={Settings.theme.hp} style={{ whiteSpace: "pre-wrap" }}>
|
||||
Entropy ({player.entropyStacks} accumulated)
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
{open ? (
|
||||
<ExpandLess sx={{ color: Settings.theme.hp }} />
|
||||
) : (
|
||||
<ExpandMore sx={{ color: Settings.theme.hp }} />
|
||||
)}
|
||||
</ListItemButton>
|
||||
<Collapse in={open} unmountOnExit>
|
||||
<Box m={4}>
|
||||
<Typography color={Settings.theme.hp}>
|
||||
<b>All multipliers decreased by:</b>{" "}
|
||||
{formatNumber((1 - CONSTANTS.EntropyEffect ** player.entropyStacks) * 100, 3)}% (multiplicative)
|
||||
</Typography>
|
||||
</Box>
|
||||
</Collapse>
|
||||
</Box>
|
||||
);
|
||||
})()}
|
||||
|
||||
{sourceAugs.map((e) => {
|
||||
const aug = Augmentations[e.name];
|
||||
|
||||
|
@ -425,7 +425,7 @@ BitNodes["BitNode10"] = new BitNode(
|
||||
This BitNode unlocks Sleeve technology. Sleeve technology allows you to:
|
||||
<br />
|
||||
<br />
|
||||
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
|
||||
<br />
|
||||
2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks
|
||||
synchronously
|
||||
|
@ -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,
|
||||
|
||||
|
@ -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 {
|
||||
|
||||
<TimeSkip player={props.player} engine={props.engine} />
|
||||
<Achievements player={props.player} engine={props.engine} />
|
||||
<Entropy player={props.player} engine={props.engine} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
50
src/DevMenu/ui/Entropy.tsx
Normal file
50
src/DevMenu/ui/Entropy.tsx
Normal file
@ -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 (
|
||||
<Accordion TransitionProps={{ unmountOnExit: true }}>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Typography>Entropy</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Adjuster
|
||||
label="Set entropy"
|
||||
placeholder="entropy"
|
||||
add={num => {
|
||||
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);
|
||||
}}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
@ -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;
|
||||
|
@ -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 (
|
||||
<Container disableGutters maxWidth="md" sx={{ mx: 0, mb: 10 }}>
|
||||
@ -116,7 +110,7 @@ export function FactionsRoot(props: IProps): React.ReactElement {
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Box ml={1} mb={1}>
|
||||
<Button sx={{ width: '100%' }} onClick={() => openFactionAugPage(Factions[faction])}>
|
||||
<Button sx={{ width: "100%" }} onClick={() => openFactionAugPage(Factions[faction])}>
|
||||
Augmentations Left: {getAugsLeft(Factions[faction], props.player)}
|
||||
</Button>
|
||||
</Box>
|
||||
|
@ -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 <Button onClick={handleResleeving}>Re-Sleeve</Button>;
|
||||
return <Button onClick={handleGrafting} sx={{ my: 5 }}>Enter the secret lab</Button>;
|
||||
}
|
||||
|
||||
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 <CreateCorporation />;
|
||||
|
@ -386,6 +386,12 @@ export const RamCosts: IMap<any> = {
|
||||
getGameInfo: 0,
|
||||
},
|
||||
|
||||
grafting: {
|
||||
getAugmentationCraftPrice: 3.75,
|
||||
getAugmentationCraftTime: 3.75,
|
||||
craftAugmentation: 7.5,
|
||||
},
|
||||
|
||||
heart: {
|
||||
// Easter egg function
|
||||
break: 0,
|
||||
|
@ -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;
|
||||
|
85
src/NetscriptFunctions/Grafting.ts
Normal file
85
src/NetscriptFunctions/Grafting.ts
Normal file
@ -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;
|
||||
},
|
||||
};
|
||||
}
|
@ -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;
|
||||
|
31
src/PersonObjects/Grafting/CraftableAugmentation.ts
Normal file
31
src/PersonObjects/Grafting/CraftableAugmentation.ts
Normal file
@ -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;
|
||||
}
|
||||
}
|
52
src/PersonObjects/Grafting/EntropyAccumulation.ts
Normal file
52
src/PersonObjects/Grafting/EntropyAccumulation.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { IMap } from "../../types";
|
||||
import { CONSTANTS } from "../../Constants";
|
||||
|
||||
import { IPlayer } from "../IPlayer";
|
||||
|
||||
export const calculateEntropy = (player: IPlayer, stacks = 1): IMap<number> => {
|
||||
const multipliers: IMap<number> = {
|
||||
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;
|
||||
};
|
160
src/PersonObjects/Grafting/ui/GraftingRoot.tsx
Normal file
160
src/PersonObjects/Grafting/ui/GraftingRoot.tsx
Normal file
@ -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<CraftableAugmentation> = {};
|
||||
|
||||
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 (
|
||||
<Container disableGutters maxWidth="lg" sx={{ mx: 0 }}>
|
||||
<Button onClick={() => router.toLocation(Locations[LocationName.NewTokyoVitaLife])}>Back</Button>
|
||||
<Typography variant="h4">Grafting Laboratory</Typography>
|
||||
<Typography>
|
||||
You find yourself in a secret laboratory, owned by a mysterious researcher.
|
||||
<br />
|
||||
The scientist explains that they've been studying Augmentation grafting, the process of applying Augmentations
|
||||
without requiring a body reset.
|
||||
<br />
|
||||
<br />
|
||||
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.
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ my: 3 }}>
|
||||
<Typography variant="h5">Craft Augmentations</Typography>
|
||||
<Paper sx={{ my: 1, width: "fit-content", display: "grid", gridTemplateColumns: "1fr 3fr" }}>
|
||||
<List sx={{ maxHeight: 400, overflowY: "scroll", borderRight: `1px solid ${Settings.theme.welllight}` }}>
|
||||
{getAvailableAugs(player).map((k, i) => (
|
||||
<ListItemButton key={i + 1} onClick={() => setSelectedAug(k)} selected={selectedAug === k}>
|
||||
<Typography>{k}</Typography>
|
||||
</ListItemButton>
|
||||
))}
|
||||
</List>
|
||||
<Box sx={{ m: 1 }}>
|
||||
<Typography variant="h6" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
|
||||
<Construction sx={{ mr: 1 }} /> {selectedAug}
|
||||
</Typography>
|
||||
<Button
|
||||
onClick={() => setCraftOpen(true)}
|
||||
sx={{ width: "100%" }}
|
||||
disabled={player.money < CraftableAugmentations[selectedAug].cost}
|
||||
>
|
||||
Craft Augmentation (
|
||||
<Typography color={Settings.theme.money}>
|
||||
<Money money={CraftableAugmentations[selectedAug].cost} player={player} />
|
||||
</Typography>
|
||||
)
|
||||
</Button>
|
||||
<ConfirmationModal
|
||||
open={craftOpen}
|
||||
onClose={() => 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 <b>not</b> save crafting progress, and the money you spend will <b>not</b> be
|
||||
returned.
|
||||
<br />
|
||||
<br />
|
||||
Additionally, grafting an Augmentation will increase the potency of the Entropy virus.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Typography color={Settings.theme.info}>
|
||||
<b>Time to Craft:</b>{" "}
|
||||
{convertTimeMsToTimeElapsedString(
|
||||
CraftableAugmentations[selectedAug].time / (1 + (player.getIntelligenceBonus(3) - 1) / 3),
|
||||
)}
|
||||
{/* Use formula so the displayed creation time is accurate to player bonus */}
|
||||
</Typography>
|
||||
<Typography sx={{ maxHeight: 305, overflowY: "scroll" }}>
|
||||
{(() => {
|
||||
const aug = Augmentations[selectedAug];
|
||||
|
||||
const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info;
|
||||
const tooltip = (
|
||||
<>
|
||||
{info}
|
||||
<br />
|
||||
<br />
|
||||
{aug.stats}
|
||||
</>
|
||||
);
|
||||
return tooltip;
|
||||
})()}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ my: 3 }}>
|
||||
<Typography variant="h5">Entropy Accumulation</Typography>
|
||||
|
||||
<Paper sx={{ my: 1, p: 1, width: "fit-content" }}>
|
||||
<Typography>
|
||||
<b>Accumulated Entropy:</b> {player.entropyStacks}
|
||||
<br />
|
||||
<b>All multipliers decreased by:</b>{" "}
|
||||
{formatNumber((1 - CONSTANTS.EntropyEffect ** player.entropyStacks) * 100, 3)}% (multiplicative)
|
||||
</Typography>
|
||||
</Paper>
|
||||
|
||||
<Typography>
|
||||
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.
|
||||
<br />
|
||||
<br />
|
||||
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...
|
||||
</Typography>
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
};
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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}.<br>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}.<br>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;
|
||||
}
|
||||
|
||||
|
@ -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).
|
@ -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;
|
@ -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;
|
||||
}
|
@ -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(
|
||||
<>
|
||||
<Typography variant="h5" color="primary">
|
||||
Total Multipliers:
|
||||
</Typography>
|
||||
<Typography>
|
||||
Hacking Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_mult)}
|
||||
<br />
|
||||
Hacking Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_exp_mult)}
|
||||
<br />
|
||||
Strength Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.strength_mult)}
|
||||
<br />
|
||||
Strength Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.strength_exp_mult)}
|
||||
<br />
|
||||
Defense Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.defense_mult)}
|
||||
<br />
|
||||
Defense Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.defense_exp_mult)}
|
||||
<br />
|
||||
Dexterity Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.dexterity_mult)}
|
||||
<br />
|
||||
Dexterity Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.dexterity_exp_mult)}
|
||||
<br />
|
||||
Agility Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.agility_mult)}
|
||||
<br />
|
||||
Agility Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.agility_exp_mult)}
|
||||
<br />
|
||||
Charisma Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.charisma_mult)}
|
||||
<br />
|
||||
Charisma Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.charisma_exp_mult)}
|
||||
<br />
|
||||
Hacking Chance multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_chance_mult)}
|
||||
<br />
|
||||
Hacking Speed multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_speed_mult)}
|
||||
<br />
|
||||
Hacking Money multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_money_mult)}
|
||||
<br />
|
||||
Hacking Growth multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_grow_mult)}
|
||||
<br />
|
||||
Salary multiplier: {numeralWrapper.formatPercentage(props.resleeve.work_money_mult)}
|
||||
<br />
|
||||
Company Reputation Gain multiplier: {numeralWrapper.formatPercentage(props.resleeve.company_rep_mult)}
|
||||
<br />
|
||||
Faction Reputation Gain multiplier: {numeralWrapper.formatPercentage(props.resleeve.faction_rep_mult)}
|
||||
<br />
|
||||
Crime Money multiplier: {numeralWrapper.formatPercentage(props.resleeve.crime_money_mult)}
|
||||
<br />
|
||||
Crime Success multiplier: {numeralWrapper.formatPercentage(props.resleeve.crime_success_mult)}
|
||||
<br />
|
||||
Hacknet Income multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacknet_node_money_mult)}
|
||||
<br />
|
||||
Hacknet Purchase Cost multiplier:
|
||||
{numeralWrapper.formatPercentage(props.resleeve.hacknet_node_purchase_cost_mult)}
|
||||
<br />
|
||||
Hacknet Level Upgrade Cost multiplier:
|
||||
{numeralWrapper.formatPercentage(props.resleeve.hacknet_node_level_cost_mult)}
|
||||
<br />
|
||||
Hacknet Ram Upgrade Cost multiplier:
|
||||
{numeralWrapper.formatPercentage(props.resleeve.hacknet_node_ram_cost_mult)}
|
||||
<br />
|
||||
Hacknet Core Upgrade Cost multiplier:
|
||||
{numeralWrapper.formatPercentage(props.resleeve.hacknet_node_core_cost_mult)}
|
||||
<br />
|
||||
Bladeburner Max Stamina multiplier:
|
||||
{numeralWrapper.formatPercentage(props.resleeve.bladeburner_max_stamina_mult)}
|
||||
<br />
|
||||
Bladeburner Stamina Gain multiplier:
|
||||
{numeralWrapper.formatPercentage(props.resleeve.bladeburner_stamina_gain_mult)}
|
||||
<br />
|
||||
Bladeburner Field Analysis multiplier:
|
||||
{numeralWrapper.formatPercentage(props.resleeve.bladeburner_analysis_mult)}
|
||||
<br />
|
||||
Bladeburner Success Chance multiplier:
|
||||
{numeralWrapper.formatPercentage(props.resleeve.bladeburner_success_chance_mult)}
|
||||
</Typography>
|
||||
</>,
|
||||
);
|
||||
}
|
||||
|
||||
function onAugChange(event: SelectChangeEvent<string>): 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 <Money money={cost} />!
|
||||
</>,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper sx={{ my: 1 }}>
|
||||
<Grid container>
|
||||
<Grid item xs={3}>
|
||||
<Typography>
|
||||
Hacking: {numeralWrapper.formatSkill(props.resleeve.hacking)} (
|
||||
{numeralWrapper.formatExp(props.resleeve.hacking_exp)} exp)
|
||||
<br />
|
||||
Strength: {numeralWrapper.formatSkill(props.resleeve.strength)} (
|
||||
{numeralWrapper.formatExp(props.resleeve.strength_exp)} exp)
|
||||
<br />
|
||||
Defense: {numeralWrapper.formatSkill(props.resleeve.defense)} (
|
||||
{numeralWrapper.formatExp(props.resleeve.defense_exp)} exp)
|
||||
<br />
|
||||
Dexterity: {numeralWrapper.formatSkill(props.resleeve.dexterity)} (
|
||||
{numeralWrapper.formatExp(props.resleeve.dexterity_exp)} exp)
|
||||
<br />
|
||||
Agility: {numeralWrapper.formatSkill(props.resleeve.agility)} (
|
||||
{numeralWrapper.formatExp(props.resleeve.agility_exp)} exp)
|
||||
<br />
|
||||
Charisma: {numeralWrapper.formatSkill(props.resleeve.charisma)} (
|
||||
{numeralWrapper.formatExp(props.resleeve.charisma_exp)} exp)
|
||||
<br /># Augmentations: {props.resleeve.augmentations.length}
|
||||
</Typography>
|
||||
<Button onClick={openStats}>Multipliers</Button>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Select value={aug} onChange={onAugChange}>
|
||||
{props.resleeve.augmentations.map((aug) => (
|
||||
<MenuItem key={aug.name} value={aug.name}>
|
||||
{aug.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<Typography>{currentAug !== undefined && currentAug.info}</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Typography>
|
||||
It costs <Money money={cost} player={props.player} /> to purchase this Sleeve.
|
||||
</Typography>
|
||||
<Button onClick={purchase}>Purchase</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
);
|
||||
}
|
@ -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<string>): 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 (
|
||||
<>
|
||||
<Typography>
|
||||
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!
|
||||
<br />
|
||||
<br />
|
||||
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).
|
||||
<br />
|
||||
<br />
|
||||
NOTE: The stats and multipliers displayed on this page do NOT include your bonuses from Source-File.
|
||||
</Typography>
|
||||
<Box display="flex" alignItems="center">
|
||||
<Typography>Sort By: </Typography>
|
||||
<Select value={sort} onChange={onSortChange}>
|
||||
{Object.keys(SortOption).map((opt) => (
|
||||
<MenuItem key={opt} value={opt}>
|
||||
{SortOption[opt]}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</Box>
|
||||
{player.resleeves.map((resleeve, i) => (
|
||||
<ResleeveElem key={i} player={player} resleeve={resleeve} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
@ -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) {
|
||||
|
@ -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}`;
|
||||
|
45
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
45
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
@ -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.
|
||||
*
|
||||
|
@ -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}
|
||||
>
|
||||
|
@ -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
|
||||
</>
|
||||
),
|
||||
);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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<string, string>, options?: ScriptEditorRouteOptions) => {
|
||||
setEditorOptions({
|
||||
files,
|
||||
@ -429,8 +429,8 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
||||
mainPage = <BladeburnerRoot />;
|
||||
break;
|
||||
}
|
||||
case Page.Resleeves: {
|
||||
mainPage = <ResleeveRoot />;
|
||||
case Page.Grafting: {
|
||||
mainPage = <GraftingRoot />;
|
||||
break;
|
||||
}
|
||||
case Page.Travel: {
|
||||
|
@ -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 <strong>{player.companyName}</strong>
|
||||
</>
|
||||
);
|
||||
header = (
|
||||
<>
|
||||
Working at <strong>{player.companyName}</strong>
|
||||
</>
|
||||
);
|
||||
innerText = (
|
||||
<>
|
||||
+<Reputation reputation={player.workRepGained} /> rep
|
||||
</>
|
||||
);
|
||||
} else if (player.workType === CONSTANTS.WorkTypeFaction) {
|
||||
details = (
|
||||
<>
|
||||
{player.factionWorkType} for <strong>{player.currentWorkFactionName}</strong>
|
||||
</>
|
||||
);
|
||||
header = (
|
||||
<>
|
||||
Working for <strong>{player.currentWorkFactionName}</strong>
|
||||
</>
|
||||
);
|
||||
innerText = (
|
||||
<>
|
||||
+<Reputation reputation={player.workRepGained} /> 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 <strong>{player.companyName}</strong>
|
||||
</>
|
||||
);
|
||||
header = (
|
||||
<>
|
||||
Working at <strong>{player.companyName}</strong>
|
||||
</>
|
||||
);
|
||||
innerText = (
|
||||
<>
|
||||
+<Reputation reputation={player.workRepGained} /> rep
|
||||
</>
|
||||
);
|
||||
break;
|
||||
case CONSTANTS.WorkTypeFaction:
|
||||
details = (
|
||||
<>
|
||||
{player.factionWorkType} for <strong>{player.currentWorkFactionName}</strong>
|
||||
</>
|
||||
);
|
||||
header = (
|
||||
<>
|
||||
Working for <strong>{player.currentWorkFactionName}</strong>
|
||||
</>
|
||||
);
|
||||
innerText = (
|
||||
<>
|
||||
+<Reputation reputation={player.workRepGained} /> 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 = (
|
||||
<>
|
||||
<strong>{((player.timeWorkedCraftAugmentation / player.timeNeededToCompleteWork) * 100).toFixed(2)}%</strong>
|
||||
{" "}done
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -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<string, string>, options?: ScriptEditorRouteOptions): void;
|
||||
toSleeves(): void;
|
||||
toStockMarket(): void;
|
||||
|
@ -41,8 +41,8 @@ export function WorkInProgressRoot(): React.ReactElement {
|
||||
return (
|
||||
<>
|
||||
<Typography variant="h4" color="primary">
|
||||
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
|
||||
</Typography>
|
||||
<Button onClick={() => router.toFactions()}>Back to Factions</Button>
|
||||
</>
|
||||
@ -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 (
|
||||
<Grid container direction="column" justifyContent="center" alignItems="center" style={{ minHeight: "100vh" }}>
|
||||
<Grid item>
|
||||
<Typography>
|
||||
You are currently working on crafting {player.craftAugmentationName}.
|
||||
<br />
|
||||
<br />
|
||||
You have been working for {convertTimeMsToTimeElapsedString(player.timeWorked)}
|
||||
<br />
|
||||
<br />
|
||||
The augmentation is{" "}
|
||||
{((player.timeWorkedCraftAugmentation / player.timeNeededToCompleteWork) * 100).toFixed(2)}% done being
|
||||
crafted.
|
||||
<br />
|
||||
If you cancel, your work will <b>not</b> be saved, and the money you spent will <b>not</b> be returned.
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Button sx={{ mx: 2 }} onClick={cancel}>
|
||||
Cancel work on crafting Augmentation
|
||||
</Button>
|
||||
<Button onClick={unfocus}>Do something else simultaneously</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
if (!player.workType) router.toTerminal();
|
||||
|
||||
return <></>;
|
||||
|
Loading…
Reference in New Issue
Block a user