mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-12-21 05:35:45 +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
|
* Crime success rate
|
||||||
* Bladeburner
|
* Bladeburner
|
||||||
* Reputation gain for companies & factions
|
* 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.
|
Memory is a persistent stat, meaning it never gets reset back to 1.
|
||||||
The maximum possible value for a sleeve's memory is 100.
|
The maximum possible value for a sleeve's memory is 100.
|
||||||
|
|
||||||
Re-sleeving
|
Grafting
|
||||||
^^^^^^^^^^^
|
^^^^^^^^
|
||||||
Re-sleeving is the process of digitizing and transferring your consciousness into a
|
Grafting is an experimental process through which you can obtain the benefits of
|
||||||
new human body, or "sleeve". When you re-sleeve into a new body, your stat experience
|
Augmentations, without needing to install them.
|
||||||
and Augmentations get replaced with those of the new body.
|
|
||||||
|
|
||||||
In order to re-sleeve, you must purchase new bodies. This can be done at VitaLife in
|
In order to graft, you must first purchase a blueprint for and craft the Augmentation.
|
||||||
New Tokyo. Once you purchase a body to re-sleeve into, the effects will take
|
This can be done at VitaLife in New Tokyo, where you'll find a shady researcher with
|
||||||
place immediately.
|
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,
|
Be warned, some who have tested grafting have reported an unidentified malware. Dubbed
|
||||||
and replaces them with the ones provided by the purchased sleeve. However,
|
"Entropy", this virus seems to grow in potency as more Augmentations are grafted,
|
||||||
Augmentations that are purchased but not installed will **not** be removed. If you have purchased
|
causing unpredictable affects to the victim.
|
||||||
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 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. |
|
|| 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. |
|
|| || * 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 |
|
|| 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). |
|
|| || 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 Button from "@mui/material/Button";
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import List from "@mui/material/List";
|
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 {
|
export function InstalledAugmentations(): React.ReactElement {
|
||||||
const setRerender = useState(true)[1];
|
const setRerender = useState(true)[1];
|
||||||
@ -55,6 +59,38 @@ export function InstalledAugmentations(): React.ReactElement {
|
|||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<List dense>
|
<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) => {
|
{sourceAugs.map((e) => {
|
||||||
const aug = Augmentations[e.name];
|
const aug = Augmentations[e.name];
|
||||||
|
|
||||||
|
@ -425,7 +425,7 @@ BitNodes["BitNode10"] = new BitNode(
|
|||||||
This BitNode unlocks Sleeve technology. Sleeve technology allows you to:
|
This BitNode unlocks Sleeve technology. Sleeve technology allows you to:
|
||||||
<br />
|
<br />
|
||||||
<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 />
|
<br />
|
||||||
2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks
|
2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks
|
||||||
synchronously
|
synchronously
|
||||||
|
@ -41,6 +41,7 @@ export const CONSTANTS: {
|
|||||||
IntelligenceInfiltrationWeight: number;
|
IntelligenceInfiltrationWeight: number;
|
||||||
IntelligenceCrimeBaseExpGain: number;
|
IntelligenceCrimeBaseExpGain: number;
|
||||||
IntelligenceProgramBaseExpGain: number;
|
IntelligenceProgramBaseExpGain: number;
|
||||||
|
IntelligenceCraftBaseExpGain: number;
|
||||||
IntelligenceTerminalHackBaseExpGain: number;
|
IntelligenceTerminalHackBaseExpGain: number;
|
||||||
IntelligenceSingFnBaseExpGain: number;
|
IntelligenceSingFnBaseExpGain: number;
|
||||||
IntelligenceClassBaseExpGain: number;
|
IntelligenceClassBaseExpGain: number;
|
||||||
@ -71,6 +72,7 @@ export const CONSTANTS: {
|
|||||||
WorkTypeCreateProgram: string;
|
WorkTypeCreateProgram: string;
|
||||||
WorkTypeStudyClass: string;
|
WorkTypeStudyClass: string;
|
||||||
WorkTypeCrime: string;
|
WorkTypeCrime: string;
|
||||||
|
WorkTypeCraftAugmentation: string;
|
||||||
ClassStudyComputerScience: string;
|
ClassStudyComputerScience: string;
|
||||||
ClassDataStructures: string;
|
ClassDataStructures: string;
|
||||||
ClassNetworks: string;
|
ClassNetworks: string;
|
||||||
@ -108,6 +110,9 @@ export const CONSTANTS: {
|
|||||||
CodingContractBaseFactionRepGain: number;
|
CodingContractBaseFactionRepGain: number;
|
||||||
CodingContractBaseCompanyRepGain: number;
|
CodingContractBaseCompanyRepGain: number;
|
||||||
CodingContractBaseMoneyGain: number;
|
CodingContractBaseMoneyGain: number;
|
||||||
|
AugmentationCraftingCostMult: number;
|
||||||
|
AugmentationCraftingTimeBase: number;
|
||||||
|
EntropyEffect: number;
|
||||||
TotalNumBitNodes: number;
|
TotalNumBitNodes: number;
|
||||||
LatestUpdate: string;
|
LatestUpdate: string;
|
||||||
} = {
|
} = {
|
||||||
@ -180,6 +185,7 @@ export const CONSTANTS: {
|
|||||||
IntelligenceInfiltrationWeight: 0.1, // Weight for how much int affects infiltration success rates
|
IntelligenceInfiltrationWeight: 0.1, // Weight for how much int affects infiltration success rates
|
||||||
IntelligenceCrimeBaseExpGain: 0.05,
|
IntelligenceCrimeBaseExpGain: 0.05,
|
||||||
IntelligenceProgramBaseExpGain: 0.1, // Program required hack level divided by this to determine int exp gain
|
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
|
IntelligenceTerminalHackBaseExpGain: 200, // Hacking exp divided by this to determine int exp gain
|
||||||
IntelligenceSingFnBaseExpGain: 1.5,
|
IntelligenceSingFnBaseExpGain: 1.5,
|
||||||
IntelligenceClassBaseExpGain: 0.01,
|
IntelligenceClassBaseExpGain: 0.01,
|
||||||
@ -224,6 +230,7 @@ export const CONSTANTS: {
|
|||||||
WorkTypeCreateProgram: "Working on Create a Program",
|
WorkTypeCreateProgram: "Working on Create a Program",
|
||||||
WorkTypeStudyClass: "Studying or Taking a class at university",
|
WorkTypeStudyClass: "Studying or Taking a class at university",
|
||||||
WorkTypeCrime: "Committing a crime",
|
WorkTypeCrime: "Committing a crime",
|
||||||
|
WorkTypeCraftAugmentation: "Crafting an Augmentation",
|
||||||
|
|
||||||
ClassStudyComputerScience: "studying Computer Science",
|
ClassStudyComputerScience: "studying Computer Science",
|
||||||
ClassDataStructures: "taking a Data Structures course",
|
ClassDataStructures: "taking a Data Structures course",
|
||||||
@ -269,6 +276,13 @@ export const CONSTANTS: {
|
|||||||
CodingContractBaseCompanyRepGain: 4000,
|
CodingContractBaseCompanyRepGain: 4000,
|
||||||
CodingContractBaseMoneyGain: 75e6,
|
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
|
// BitNode/Source-File related stuff
|
||||||
TotalNumBitNodes: 24,
|
TotalNumBitNodes: 24,
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ import { Sleeves } from "./DevMenu/ui/Sleeves";
|
|||||||
import { Stanek } from "./DevMenu/ui/Stanek";
|
import { Stanek } from "./DevMenu/ui/Stanek";
|
||||||
import { TimeSkip } from "./DevMenu/ui/TimeSkip";
|
import { TimeSkip } from "./DevMenu/ui/TimeSkip";
|
||||||
import { Achievements } from "./DevMenu/ui/Achievements";
|
import { Achievements } from "./DevMenu/ui/Achievements";
|
||||||
|
import { Entropy } from "./DevMenu/ui/Entropy";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import { Exploit } from "./Exploits/Exploit";
|
import { Exploit } from "./Exploits/Exploit";
|
||||||
|
|
||||||
@ -63,6 +64,7 @@ export function DevMenuRoot(props: IProps): React.ReactElement {
|
|||||||
|
|
||||||
<TimeSkip player={props.player} engine={props.engine} />
|
<TimeSkip player={props.player} engine={props.engine} />
|
||||||
<Achievements 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) {
|
if (isPlayersGang) {
|
||||||
const augs: string[] = [];
|
const augs: string[] = [];
|
||||||
for (const augName of Object.keys(Augmentations)) {
|
for (const augName of Object.keys(Augmentations)) {
|
||||||
if (augName === AugmentationNames.NeuroFluxGovernor) continue;
|
|
||||||
if (augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) continue;
|
|
||||||
const aug = Augmentations[augName];
|
const aug = Augmentations[augName];
|
||||||
if (!aug.isSpecial) {
|
if (
|
||||||
augs.push(augName);
|
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;
|
return augs;
|
||||||
|
@ -1,14 +1,6 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
import {
|
import { Box, Button, Container, Paper, TableBody, TableRow, Typography } from "@mui/material";
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Container,
|
|
||||||
Paper,
|
|
||||||
TableBody,
|
|
||||||
TableRow,
|
|
||||||
Typography
|
|
||||||
} from "@mui/material";
|
|
||||||
|
|
||||||
import { Augmentations } from "../../Augmentation/Augmentations";
|
import { Augmentations } from "../../Augmentation/Augmentations";
|
||||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||||
@ -65,26 +57,28 @@ export function FactionsRoot(props: IProps): React.ReactElement {
|
|||||||
|
|
||||||
if (isPlayersGang) {
|
if (isPlayersGang) {
|
||||||
for (const augName of Object.keys(Augmentations)) {
|
for (const augName of Object.keys(Augmentations)) {
|
||||||
|
const aug = Augmentations[augName];
|
||||||
if (
|
if (
|
||||||
augName === AugmentationNames.NeuroFluxGovernor ||
|
augName === AugmentationNames.NeuroFluxGovernor ||
|
||||||
augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2 ||
|
(augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) ||
|
||||||
Augmentations[augName].isSpecial
|
// Special augs (i.e. Bladeburner augs)
|
||||||
) continue;
|
aug.isSpecial ||
|
||||||
augs.push(augName)
|
// Exclusive augs (i.e. QLink)
|
||||||
|
(aug.factions.length <= 1 && !faction.augmentations.includes(augName) && player.bitNodeN !== 2)
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
augs.push(augName);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
augs = faction.augmentations.slice();
|
augs = faction.augmentations.slice();
|
||||||
}
|
}
|
||||||
|
|
||||||
return augs.filter(
|
return augs.filter((augmentation: string) => !player.hasAugmentation(augmentation)).length;
|
||||||
(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);
|
const allJoinedFactions = props.player.factions.slice(0);
|
||||||
allJoinedFactions.sort((a, b) =>
|
allJoinedFactions.sort((a, b) => allFactions.indexOf(a) - allFactions.indexOf(b));
|
||||||
allFactions.indexOf(a) - allFactions.indexOf(b));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container disableGutters maxWidth="md" sx={{ mx: 0, mb: 10 }}>
|
<Container disableGutters maxWidth="md" sx={{ mx: 0, mb: 10 }}>
|
||||||
@ -116,7 +110,7 @@ export function FactionsRoot(props: IProps): React.ReactElement {
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell align="right">
|
<TableCell align="right">
|
||||||
<Box ml={1} mb={1}>
|
<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)}
|
Augmentations Left: {getAugsLeft(Factions[faction], props.player)}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -72,8 +72,8 @@ export function SpecialLocation(props: IProps): React.ReactElement {
|
|||||||
/**
|
/**
|
||||||
* Click handler for Resleeving button at New Tokyo VitaLife
|
* Click handler for Resleeving button at New Tokyo VitaLife
|
||||||
*/
|
*/
|
||||||
function handleResleeving(): void {
|
function handleGrafting(): void {
|
||||||
router.toResleeves();
|
router.toGrafting();
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderBladeburner(): React.ReactElement {
|
function renderBladeburner(): React.ReactElement {
|
||||||
@ -151,11 +151,11 @@ export function SpecialLocation(props: IProps): React.ReactElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderResleeving(): React.ReactElement {
|
function renderGrafting(): React.ReactElement {
|
||||||
if (!player.canAccessResleeving()) {
|
if (!player.canAccessGrafting()) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
return <Button onClick={handleResleeving}>Re-Sleeve</Button>;
|
return <Button onClick={handleGrafting} sx={{ my: 5 }}>Enter the secret lab</Button>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCotMG(): void {
|
function handleCotMG(): void {
|
||||||
@ -299,7 +299,7 @@ export function SpecialLocation(props: IProps): React.ReactElement {
|
|||||||
|
|
||||||
switch (props.loc.name) {
|
switch (props.loc.name) {
|
||||||
case LocationName.NewTokyoVitaLife: {
|
case LocationName.NewTokyoVitaLife: {
|
||||||
return renderResleeving();
|
return renderGrafting();
|
||||||
}
|
}
|
||||||
case LocationName.Sector12CityHall: {
|
case LocationName.Sector12CityHall: {
|
||||||
return <CreateCorporation />;
|
return <CreateCorporation />;
|
||||||
|
@ -386,6 +386,12 @@ export const RamCosts: IMap<any> = {
|
|||||||
getGameInfo: 0,
|
getGameInfo: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
grafting: {
|
||||||
|
getAugmentationCraftPrice: 3.75,
|
||||||
|
getAugmentationCraftTime: 3.75,
|
||||||
|
craftAugmentation: 7.5,
|
||||||
|
},
|
||||||
|
|
||||||
heart: {
|
heart: {
|
||||||
// Easter egg function
|
// Easter egg function
|
||||||
break: 0,
|
break: 0,
|
||||||
|
@ -70,6 +70,7 @@ import { NetscriptCodingContract } from "./NetscriptFunctions/CodingContract";
|
|||||||
import { NetscriptCorporation } from "./NetscriptFunctions/Corporation";
|
import { NetscriptCorporation } from "./NetscriptFunctions/Corporation";
|
||||||
import { NetscriptFormulas } from "./NetscriptFunctions/Formulas";
|
import { NetscriptFormulas } from "./NetscriptFunctions/Formulas";
|
||||||
import { NetscriptStockMarket } from "./NetscriptFunctions/StockMarket";
|
import { NetscriptStockMarket } from "./NetscriptFunctions/StockMarket";
|
||||||
|
import { NetscriptGrafting } from "./NetscriptFunctions/Grafting";
|
||||||
import { IPort } from "./NetscriptPort";
|
import { IPort } from "./NetscriptPort";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -480,6 +481,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
|||||||
const singularity = NetscriptSingularity(Player, workerScript, helper);
|
const singularity = NetscriptSingularity(Player, workerScript, helper);
|
||||||
const stockmarket = NetscriptStockMarket(Player, workerScript, helper);
|
const stockmarket = NetscriptStockMarket(Player, workerScript, helper);
|
||||||
const ui = NetscriptUserInterface(Player, workerScript, helper);
|
const ui = NetscriptUserInterface(Player, workerScript, helper);
|
||||||
|
const grafting = NetscriptGrafting(Player, workerScript, helper);
|
||||||
|
|
||||||
const base: INS = {
|
const base: INS = {
|
||||||
...singularity,
|
...singularity,
|
||||||
@ -493,6 +495,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
|||||||
ui: ui,
|
ui: ui,
|
||||||
formulas: formulas,
|
formulas: formulas,
|
||||||
stock: stockmarket,
|
stock: stockmarket,
|
||||||
|
grafting: grafting,
|
||||||
args: workerScript.args,
|
args: workerScript.args,
|
||||||
hacknet: hacknet,
|
hacknet: hacknet,
|
||||||
sprintf: sprintf,
|
sprintf: sprintf,
|
||||||
@ -2315,6 +2318,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
|||||||
tor: Player.hasTorRouter(),
|
tor: Player.hasTorRouter(),
|
||||||
inBladeburner: Player.inBladeburner(),
|
inBladeburner: Player.inBladeburner(),
|
||||||
hasCorporation: Player.hasCorporation(),
|
hasCorporation: Player.hasCorporation(),
|
||||||
|
entropyStacks: Player.entropyStacks,
|
||||||
};
|
};
|
||||||
Object.assign(data.jobs, Player.jobs);
|
Object.assign(data.jobs, Player.jobs);
|
||||||
return data;
|
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 = [];
|
let augs = [];
|
||||||
if (player.hasGangWith(faction)) {
|
if (player.hasGangWith(faction)) {
|
||||||
for (const augName of Object.keys(Augmentations)) {
|
for (const augName of Object.keys(Augmentations)) {
|
||||||
if (augName === AugmentationNames.NeuroFluxGovernor) continue;
|
const aug = Augmentations[augName];
|
||||||
if (augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) continue;
|
if (
|
||||||
const tempAug = Augmentations[augName];
|
augName === AugmentationNames.NeuroFluxGovernor ||
|
||||||
if (!tempAug.isSpecial) {
|
(augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) ||
|
||||||
augs.push(augName);
|
// 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 {
|
} else {
|
||||||
augs = fac.augmentations;
|
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
|
* Used because at the time of implementation, the PlayerObject
|
||||||
* cant be converted to TypeScript.
|
* cant be converted to TypeScript.
|
||||||
*/
|
*/
|
||||||
import { Resleeve } from "./Resleeving/Resleeve";
|
|
||||||
import { Sleeve } from "./Sleeve/Sleeve";
|
import { Sleeve } from "./Sleeve/Sleeve";
|
||||||
|
|
||||||
import { IMap } from "../types";
|
import { IMap } from "../types";
|
||||||
@ -65,7 +64,6 @@ export interface IPlayer {
|
|||||||
playtimeSinceLastBitnode: number;
|
playtimeSinceLastBitnode: number;
|
||||||
purchasedServers: any[];
|
purchasedServers: any[];
|
||||||
queuedAugmentations: IPlayerOwnedAugmentation[];
|
queuedAugmentations: IPlayerOwnedAugmentation[];
|
||||||
resleeves: Resleeve[];
|
|
||||||
scriptProdSinceLastAug: number;
|
scriptProdSinceLastAug: number;
|
||||||
sleeves: Sleeve[];
|
sleeves: Sleeve[];
|
||||||
sleevesFromCovenant: number;
|
sleevesFromCovenant: number;
|
||||||
@ -130,6 +128,8 @@ export interface IPlayer {
|
|||||||
factionWorkType: string;
|
factionWorkType: string;
|
||||||
createProgramName: string;
|
createProgramName: string;
|
||||||
timeWorkedCreateProgram: number;
|
timeWorkedCreateProgram: number;
|
||||||
|
craftAugmentationName: string;
|
||||||
|
timeWorkedCraftAugmentation: number;
|
||||||
crimeType: string;
|
crimeType: string;
|
||||||
committingCrimeThruSingFn: boolean;
|
committingCrimeThruSingFn: boolean;
|
||||||
singFnCrimeWorkerScript: WorkerScript | null;
|
singFnCrimeWorkerScript: WorkerScript | null;
|
||||||
@ -160,6 +160,8 @@ export interface IPlayer {
|
|||||||
workChaExpGainRate: number;
|
workChaExpGainRate: number;
|
||||||
workMoneyLossRate: number;
|
workMoneyLossRate: number;
|
||||||
|
|
||||||
|
entropyStacks: number;
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
work(numCycles: number): boolean;
|
work(numCycles: number): boolean;
|
||||||
workPartTime(numCycles: number): boolean;
|
workPartTime(numCycles: number): boolean;
|
||||||
@ -181,7 +183,7 @@ export interface IPlayer {
|
|||||||
canAccessBladeburner(): boolean;
|
canAccessBladeburner(): boolean;
|
||||||
canAccessCorporation(): boolean;
|
canAccessCorporation(): boolean;
|
||||||
canAccessGang(): boolean;
|
canAccessGang(): boolean;
|
||||||
canAccessResleeving(): boolean;
|
canAccessGrafting(): boolean;
|
||||||
canAfford(cost: number): boolean;
|
canAfford(cost: number): boolean;
|
||||||
gainHackingExp(exp: number): void;
|
gainHackingExp(exp: number): void;
|
||||||
gainStrengthExp(exp: number): void;
|
gainStrengthExp(exp: number): void;
|
||||||
@ -286,4 +288,8 @@ export interface IPlayer {
|
|||||||
setMult(name: string, mult: number): void;
|
setMult(name: string, mult: number): void;
|
||||||
canAccessCotMG(): boolean;
|
canAccessCotMG(): boolean;
|
||||||
sourceFileLvl(n: number): number;
|
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 * as serverMethods from "./PlayerObjectServerMethods";
|
||||||
|
|
||||||
import { IMap } from "../../types";
|
import { IMap } from "../../types";
|
||||||
import { Resleeve } from "../Resleeving/Resleeve";
|
|
||||||
import { Sleeve } from "../Sleeve/Sleeve";
|
import { Sleeve } from "../Sleeve/Sleeve";
|
||||||
import { IPlayerOwnedSourceFile } from "../../SourceFile/PlayerOwnedSourceFile";
|
import { IPlayerOwnedSourceFile } from "../../SourceFile/PlayerOwnedSourceFile";
|
||||||
import { Exploit } from "../../Exploits/Exploit";
|
import { Exploit } from "../../Exploits/Exploit";
|
||||||
@ -72,7 +71,6 @@ export class PlayerObject implements IPlayer {
|
|||||||
playtimeSinceLastBitnode: number;
|
playtimeSinceLastBitnode: number;
|
||||||
purchasedServers: any[];
|
purchasedServers: any[];
|
||||||
queuedAugmentations: IPlayerOwnedAugmentation[];
|
queuedAugmentations: IPlayerOwnedAugmentation[];
|
||||||
resleeves: Resleeve[];
|
|
||||||
scriptProdSinceLastAug: number;
|
scriptProdSinceLastAug: number;
|
||||||
sleeves: Sleeve[];
|
sleeves: Sleeve[];
|
||||||
sleevesFromCovenant: number;
|
sleevesFromCovenant: number;
|
||||||
@ -139,6 +137,8 @@ export class PlayerObject implements IPlayer {
|
|||||||
factionWorkType: string;
|
factionWorkType: string;
|
||||||
createProgramName: string;
|
createProgramName: string;
|
||||||
timeWorkedCreateProgram: number;
|
timeWorkedCreateProgram: number;
|
||||||
|
craftAugmentationName: string;
|
||||||
|
timeWorkedCraftAugmentation: number;
|
||||||
crimeType: string;
|
crimeType: string;
|
||||||
committingCrimeThruSingFn: boolean;
|
committingCrimeThruSingFn: boolean;
|
||||||
singFnCrimeWorkerScript: WorkerScript | null;
|
singFnCrimeWorkerScript: WorkerScript | null;
|
||||||
@ -169,6 +169,8 @@ export class PlayerObject implements IPlayer {
|
|||||||
workChaExpGainRate: number;
|
workChaExpGainRate: number;
|
||||||
workMoneyLossRate: number;
|
workMoneyLossRate: number;
|
||||||
|
|
||||||
|
entropyStacks: number;
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
work: (numCycles: number) => boolean;
|
work: (numCycles: number) => boolean;
|
||||||
workPartTime: (numCycles: number) => boolean;
|
workPartTime: (numCycles: number) => boolean;
|
||||||
@ -190,7 +192,7 @@ export class PlayerObject implements IPlayer {
|
|||||||
canAccessBladeburner: () => boolean;
|
canAccessBladeburner: () => boolean;
|
||||||
canAccessCorporation: () => boolean;
|
canAccessCorporation: () => boolean;
|
||||||
canAccessGang: () => boolean;
|
canAccessGang: () => boolean;
|
||||||
canAccessResleeving: () => boolean;
|
canAccessGrafting: () => boolean;
|
||||||
canAfford: (cost: number) => boolean;
|
canAfford: (cost: number) => boolean;
|
||||||
gainHackingExp: (exp: number) => void;
|
gainHackingExp: (exp: number) => void;
|
||||||
gainStrengthExp: (exp: number) => void;
|
gainStrengthExp: (exp: number) => void;
|
||||||
@ -296,6 +298,10 @@ export class PlayerObject implements IPlayer {
|
|||||||
setMult: (name: string, mult: number) => void;
|
setMult: (name: string, mult: number) => void;
|
||||||
canAccessCotMG: () => boolean;
|
canAccessCotMG: () => boolean;
|
||||||
sourceFileLvl: (n: number) => number;
|
sourceFileLvl: (n: number) => number;
|
||||||
|
startCraftAugmentationWork: (augmentationName: string, time: number) => void;
|
||||||
|
craftAugmentationWork: (numCycles: number) => boolean;
|
||||||
|
finishCraftAugmentationWork: (cancelled: boolean) => string;
|
||||||
|
applyEntropy: (stacks?: number) => void;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
//Skills and stats
|
//Skills and stats
|
||||||
@ -419,6 +425,9 @@ export class PlayerObject implements IPlayer {
|
|||||||
this.createProgramName = "";
|
this.createProgramName = "";
|
||||||
this.createProgramReqLvl = 0;
|
this.createProgramReqLvl = 0;
|
||||||
|
|
||||||
|
this.craftAugmentationName = "";
|
||||||
|
this.timeWorkedCraftAugmentation = 0;
|
||||||
|
|
||||||
this.className = "";
|
this.className = "";
|
||||||
|
|
||||||
this.crimeType = "";
|
this.crimeType = "";
|
||||||
@ -457,11 +466,12 @@ export class PlayerObject implements IPlayer {
|
|||||||
|
|
||||||
// Sleeves & Re-sleeving
|
// Sleeves & Re-sleeving
|
||||||
this.sleeves = [];
|
this.sleeves = [];
|
||||||
this.resleeves = [];
|
|
||||||
this.sleevesFromCovenant = 0; // # of Duplicate sleeves purchased from the covenan;
|
this.sleevesFromCovenant = 0; // # of Duplicate sleeves purchased from the covenan;
|
||||||
//bitnode
|
//bitnode
|
||||||
this.bitNodeN = 1;
|
this.bitNodeN = 1;
|
||||||
|
|
||||||
|
this.entropyStacks = 0;
|
||||||
|
|
||||||
//Used to store the last update time.
|
//Used to store the last update time.
|
||||||
this.lastUpdate = 0;
|
this.lastUpdate = 0;
|
||||||
this.lastSave = 0;
|
this.lastSave = 0;
|
||||||
@ -541,6 +551,9 @@ export class PlayerObject implements IPlayer {
|
|||||||
this.startCreateProgramWork = generalMethods.startCreateProgramWork;
|
this.startCreateProgramWork = generalMethods.startCreateProgramWork;
|
||||||
this.createProgramWork = generalMethods.createProgramWork;
|
this.createProgramWork = generalMethods.createProgramWork;
|
||||||
this.finishCreateProgramWork = generalMethods.finishCreateProgramWork;
|
this.finishCreateProgramWork = generalMethods.finishCreateProgramWork;
|
||||||
|
this.startCraftAugmentationWork = generalMethods.startCraftAugmentationWork;
|
||||||
|
this.craftAugmentationWork = generalMethods.craftAugmentationWork;
|
||||||
|
this.finishCraftAugmentationWork = generalMethods.finishCraftAugmentationWork;
|
||||||
this.startClass = generalMethods.startClass;
|
this.startClass = generalMethods.startClass;
|
||||||
this.takeClass = generalMethods.takeClass;
|
this.takeClass = generalMethods.takeClass;
|
||||||
this.finishClass = generalMethods.finishClass;
|
this.finishClass = generalMethods.finishClass;
|
||||||
@ -577,7 +590,7 @@ export class PlayerObject implements IPlayer {
|
|||||||
this.gainCodingContractReward = generalMethods.gainCodingContractReward;
|
this.gainCodingContractReward = generalMethods.gainCodingContractReward;
|
||||||
this.travel = generalMethods.travel;
|
this.travel = generalMethods.travel;
|
||||||
this.gotoLocation = generalMethods.gotoLocation;
|
this.gotoLocation = generalMethods.gotoLocation;
|
||||||
this.canAccessResleeving = generalMethods.canAccessResleeving;
|
this.canAccessGrafting = generalMethods.canAccessGrafting;
|
||||||
this.giveExploit = generalMethods.giveExploit;
|
this.giveExploit = generalMethods.giveExploit;
|
||||||
this.giveAchievement = generalMethods.giveAchievement;
|
this.giveAchievement = generalMethods.giveAchievement;
|
||||||
this.getIntelligenceBonus = generalMethods.getIntelligenceBonus;
|
this.getIntelligenceBonus = generalMethods.getIntelligenceBonus;
|
||||||
@ -611,6 +624,8 @@ export class PlayerObject implements IPlayer {
|
|||||||
|
|
||||||
this.canAccessCotMG = generalMethods.canAccessCotMG;
|
this.canAccessCotMG = generalMethods.canAccessCotMG;
|
||||||
this.sourceFileLvl = generalMethods.sourceFileLvl;
|
this.sourceFileLvl = generalMethods.sourceFileLvl;
|
||||||
|
|
||||||
|
this.applyEntropy = augmentationMethods.applyEntropy;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -5,6 +5,8 @@ import { IPlayer } from "../IPlayer";
|
|||||||
|
|
||||||
import { Augmentation } from "../../Augmentation/Augmentation";
|
import { Augmentation } from "../../Augmentation/Augmentation";
|
||||||
|
|
||||||
|
import { calculateEntropy } from "../Grafting/EntropyAccumulation";
|
||||||
|
|
||||||
export function hasAugmentation(this: IPlayer, aug: string | Augmentation, installed = false): boolean {
|
export function hasAugmentation(this: IPlayer, aug: string | Augmentation, installed = false): boolean {
|
||||||
const augName: string = aug instanceof Augmentation ? aug.name : aug;
|
const augName: string = aug instanceof Augmentation ? aug.name : aug;
|
||||||
|
|
||||||
@ -24,3 +26,14 @@ export function hasAugmentation(this: IPlayer, aug: string | Augmentation, insta
|
|||||||
|
|
||||||
return false;
|
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.queuedAugmentations = [];
|
||||||
|
|
||||||
this.resleeves = [];
|
|
||||||
|
|
||||||
const numSleeves = Math.min(3, SourceFileFlags[10] + (this.bitNodeN === 10 ? 1 : 0)) + this.sleevesFromCovenant;
|
const numSleeves = Math.min(3, SourceFileFlags[10] + (this.bitNodeN === 10 ? 1 : 0)) + this.sleevesFromCovenant;
|
||||||
if (this.sleeves.length > numSleeves) this.sleeves.length = numSleeves;
|
if (this.sleeves.length > numSleeves) this.sleeves.length = numSleeves;
|
||||||
for (let i = this.sleeves.length; i < numSleeves; i++) {
|
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 {
|
export function prestigeSourceFile(this: IPlayer): void {
|
||||||
|
this.entropyStacks = 0;
|
||||||
this.prestigeAugmentation();
|
this.prestigeAugmentation();
|
||||||
this.karma = 0;
|
this.karma = 0;
|
||||||
// Duplicate sleeves are reset to level 1 every Bit Node (but the number of sleeves you have persists)
|
// 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.timeWorked = 0;
|
||||||
this.timeWorkedCreateProgram = 0;
|
this.timeWorkedCreateProgram = 0;
|
||||||
|
this.timeWorkedCraftAugmentation = 0;
|
||||||
|
|
||||||
this.currentWorkFactionName = "";
|
this.currentWorkFactionName = "";
|
||||||
this.currentWorkFactionDescription = "";
|
this.currentWorkFactionDescription = "";
|
||||||
this.createProgramName = "";
|
this.createProgramName = "";
|
||||||
|
this.craftAugmentationName = "";
|
||||||
this.className = "";
|
this.className = "";
|
||||||
this.workType = "";
|
this.workType = "";
|
||||||
}
|
}
|
||||||
@ -609,6 +610,10 @@ export function process(this: IPlayer, router: IRouter, numCycles = 1): void {
|
|||||||
if (this.workPartTime(numCycles)) {
|
if (this.workPartTime(numCycles)) {
|
||||||
router.toCity();
|
router.toCity();
|
||||||
}
|
}
|
||||||
|
} else if (this.workType === CONSTANTS.WorkTypeCraftAugmentation) {
|
||||||
|
if (this.craftAugmentationWork(numCycles)) {
|
||||||
|
router.toGrafting();
|
||||||
|
}
|
||||||
} else if (this.work(numCycles)) {
|
} else if (this.work(numCycles)) {
|
||||||
router.toCity();
|
router.toCity();
|
||||||
}
|
}
|
||||||
@ -1329,6 +1334,61 @@ export function finishCreateProgramWork(this: IPlayer, cancelled: boolean): stri
|
|||||||
this.resetWorkStatus();
|
this.resetWorkStatus();
|
||||||
return "You've finished creating " + programName + "! The new program can be found on your home computer.";
|
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 */
|
/* Studying/Taking Classes */
|
||||||
export function startClass(this: IPlayer, costMult: number, expMult: number, className: string): void {
|
export function startClass(this: IPlayer, costMult: number, expMult: number, className: string): void {
|
||||||
this.resetWorkStatus();
|
this.resetWorkStatus();
|
||||||
@ -1507,20 +1567,20 @@ export function finishCrime(this: IPlayer, cancelled: boolean): string {
|
|||||||
if (ws.disableLogs.ALL == null && ws.disableLogs.commitCrime == null) {
|
if (ws.disableLogs.ALL == null && ws.disableLogs.commitCrime == null) {
|
||||||
ws.scriptRef.log(
|
ws.scriptRef.log(
|
||||||
"SUCCESS: Crime successful! Gained " +
|
"SUCCESS: Crime successful! Gained " +
|
||||||
numeralWrapper.formatMoney(this.workMoneyGained) +
|
numeralWrapper.formatMoney(this.workMoneyGained) +
|
||||||
", " +
|
", " +
|
||||||
numeralWrapper.formatExp(this.workHackExpGained) +
|
numeralWrapper.formatExp(this.workHackExpGained) +
|
||||||
" hack exp, " +
|
" hack exp, " +
|
||||||
numeralWrapper.formatExp(this.workStrExpGained) +
|
numeralWrapper.formatExp(this.workStrExpGained) +
|
||||||
" str exp, " +
|
" str exp, " +
|
||||||
numeralWrapper.formatExp(this.workDefExpGained) +
|
numeralWrapper.formatExp(this.workDefExpGained) +
|
||||||
" def exp, " +
|
" def exp, " +
|
||||||
numeralWrapper.formatExp(this.workDexExpGained) +
|
numeralWrapper.formatExp(this.workDexExpGained) +
|
||||||
" dex exp, " +
|
" dex exp, " +
|
||||||
numeralWrapper.formatExp(this.workAgiExpGained) +
|
numeralWrapper.formatExp(this.workAgiExpGained) +
|
||||||
" agi exp, " +
|
" agi exp, " +
|
||||||
numeralWrapper.formatExp(this.workChaExpGained) +
|
numeralWrapper.formatExp(this.workChaExpGained) +
|
||||||
" cha exp.",
|
" cha exp.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1559,18 +1619,18 @@ export function finishCrime(this: IPlayer, cancelled: boolean): string {
|
|||||||
if (ws.disableLogs.ALL == null && ws.disableLogs.commitCrime == null) {
|
if (ws.disableLogs.ALL == null && ws.disableLogs.commitCrime == null) {
|
||||||
ws.scriptRef.log(
|
ws.scriptRef.log(
|
||||||
"FAIL: Crime failed! Gained " +
|
"FAIL: Crime failed! Gained " +
|
||||||
numeralWrapper.formatExp(this.workHackExpGained) +
|
numeralWrapper.formatExp(this.workHackExpGained) +
|
||||||
" hack exp, " +
|
" hack exp, " +
|
||||||
numeralWrapper.formatExp(this.workStrExpGained) +
|
numeralWrapper.formatExp(this.workStrExpGained) +
|
||||||
" str exp, " +
|
" str exp, " +
|
||||||
numeralWrapper.formatExp(this.workDefExpGained) +
|
numeralWrapper.formatExp(this.workDefExpGained) +
|
||||||
" def exp, " +
|
" def exp, " +
|
||||||
numeralWrapper.formatExp(this.workDexExpGained) +
|
numeralWrapper.formatExp(this.workDexExpGained) +
|
||||||
" dex exp, " +
|
" dex exp, " +
|
||||||
numeralWrapper.formatExp(this.workAgiExpGained) +
|
numeralWrapper.formatExp(this.workAgiExpGained) +
|
||||||
" agi exp, " +
|
" agi exp, " +
|
||||||
numeralWrapper.formatExp(this.workChaExpGained) +
|
numeralWrapper.formatExp(this.workChaExpGained) +
|
||||||
" cha exp.",
|
" cha exp.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -2640,7 +2700,7 @@ export function gotoLocation(this: IPlayer, to: LocationName): boolean {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canAccessResleeving(this: IPlayer): boolean {
|
export function canAccessGrafting(this: IPlayer): boolean {
|
||||||
return this.bitNodeN === 10 || SourceFileFlags[10] > 0;
|
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
|
// Messages
|
||||||
initMessages();
|
initMessages();
|
||||||
|
|
||||||
|
// Apply entropy from grafting
|
||||||
|
Player.applyEntropy(Player.entropyStacks);
|
||||||
|
|
||||||
// Gang
|
// Gang
|
||||||
const gang = Player.gang;
|
const gang = Player.gang;
|
||||||
if (Player.inGang() && gang !== null) {
|
if (Player.inGang() && gang !== null) {
|
||||||
|
@ -225,6 +225,9 @@ async function parseOnlyRamCalculate(
|
|||||||
} else if (ref in workerScript.env.vars.ui) {
|
} else if (ref in workerScript.env.vars.ui) {
|
||||||
func = workerScript.env.vars.ui[ref];
|
func = workerScript.env.vars.ui[ref];
|
||||||
refDetail = `ui.${ref}`;
|
refDetail = `ui.${ref}`;
|
||||||
|
} else if (ref in workerScript.env.vars.grafting) {
|
||||||
|
func = workerScript.env.vars.grafting[ref];
|
||||||
|
refDetail = `grafting.${ref}`;
|
||||||
} else {
|
} else {
|
||||||
func = workerScript.env.vars[ref];
|
func = workerScript.env.vars[ref];
|
||||||
refDetail = `${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;
|
tor: boolean;
|
||||||
hasCorporation: boolean;
|
hasCorporation: boolean;
|
||||||
inBladeburner: boolean;
|
inBladeburner: boolean;
|
||||||
|
entropyStacks: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -3719,6 +3720,43 @@ export interface Sleeve {
|
|||||||
purchaseSleeveAug(sleeveNumber: number, augName: string): boolean;
|
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
|
* Skills formulas
|
||||||
* @public
|
* @public
|
||||||
@ -4280,6 +4318,13 @@ export interface NS extends Singularity {
|
|||||||
*/
|
*/
|
||||||
readonly ui: UserInterface;
|
readonly ui: UserInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Namespace for grafting functions.
|
||||||
|
* @remarks
|
||||||
|
* RAM cost: 0 GB
|
||||||
|
*/
|
||||||
|
readonly grafting: Grafting;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Arguments passed into the script.
|
* Arguments passed into the script.
|
||||||
*
|
*
|
||||||
|
@ -617,7 +617,7 @@ export function SidebarRoot(props: IProps): React.ReactElement {
|
|||||||
key={"City"}
|
key={"City"}
|
||||||
className={clsx({
|
className={clsx({
|
||||||
[classes.active]:
|
[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}
|
onClick={clickCity}
|
||||||
>
|
>
|
||||||
|
@ -167,8 +167,8 @@ SourceFiles["SourceFile10"] = new SourceFile(
|
|||||||
10,
|
10,
|
||||||
(
|
(
|
||||||
<>
|
<>
|
||||||
This Source-File unlocks Sleeve technology in other BitNodes. Each level of this Source-File also grants you a
|
This Source-File unlocks Sleeve technology, and the Grafting API in other BitNodes.
|
||||||
Duplicate Sleeve
|
Each level of this Source-File also grants you a Duplicate Sleeve
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -263,6 +263,9 @@ const Engine: {
|
|||||||
initSymbolToStockMap();
|
initSymbolToStockMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply penalty for entropy accumulation
|
||||||
|
Player.applyEntropy(Player.entropyStacks);
|
||||||
|
|
||||||
// Calculate the number of cycles have elapsed while offline
|
// Calculate the number of cycles have elapsed while offline
|
||||||
Engine._lastUpdate = new Date().getTime();
|
Engine._lastUpdate = new Date().getTime();
|
||||||
const lastUpdate = Player.lastUpdate;
|
const lastUpdate = Player.lastUpdate;
|
||||||
@ -302,6 +305,8 @@ const Engine: {
|
|||||||
Player.commitCrime(numCyclesOffline);
|
Player.commitCrime(numCyclesOffline);
|
||||||
} else if (Player.workType == CONSTANTS.WorkTypeCompanyPartTime) {
|
} else if (Player.workType == CONSTANTS.WorkTypeCompanyPartTime) {
|
||||||
Player.workPartTime(numCyclesOffline);
|
Player.workPartTime(numCyclesOffline);
|
||||||
|
} else if (Player.workType === CONSTANTS.WorkTypeCraftAugmentation) {
|
||||||
|
Player.craftAugmentationWork(numCyclesOffline);
|
||||||
} else {
|
} else {
|
||||||
Player.work(numCyclesOffline);
|
Player.work(numCyclesOffline);
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ import { BladeburnerRoot } from "../Bladeburner/ui/BladeburnerRoot";
|
|||||||
import { GangRoot } from "../Gang/ui/GangRoot";
|
import { GangRoot } from "../Gang/ui/GangRoot";
|
||||||
import { CorporationRoot } from "../Corporation/ui/CorporationRoot";
|
import { CorporationRoot } from "../Corporation/ui/CorporationRoot";
|
||||||
import { InfiltrationRoot } from "../Infiltration/ui/InfiltrationRoot";
|
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 { WorkInProgressRoot } from "./WorkInProgressRoot";
|
||||||
import { GameOptionsRoot } from "./React/GameOptionsRoot";
|
import { GameOptionsRoot } from "./React/GameOptionsRoot";
|
||||||
import { SleeveRoot } from "../PersonObjects/Sleeve/ui/SleeveRoot";
|
import { SleeveRoot } from "../PersonObjects/Sleeve/ui/SleeveRoot";
|
||||||
@ -135,7 +135,7 @@ export let Router: IRouter = {
|
|||||||
toInfiltration: uninitialized,
|
toInfiltration: uninitialized,
|
||||||
toJob: uninitialized,
|
toJob: uninitialized,
|
||||||
toMilestones: uninitialized,
|
toMilestones: uninitialized,
|
||||||
toResleeves: uninitialized,
|
toGrafting: uninitialized,
|
||||||
toScriptEditor: uninitialized,
|
toScriptEditor: uninitialized,
|
||||||
toSleeves: uninitialized,
|
toSleeves: uninitialized,
|
||||||
toStockMarket: uninitialized,
|
toStockMarket: uninitialized,
|
||||||
@ -226,7 +226,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
|||||||
toGang: () => setPage(Page.Gang),
|
toGang: () => setPage(Page.Gang),
|
||||||
toHacknetNodes: () => setPage(Page.Hacknet),
|
toHacknetNodes: () => setPage(Page.Hacknet),
|
||||||
toMilestones: () => setPage(Page.Milestones),
|
toMilestones: () => setPage(Page.Milestones),
|
||||||
toResleeves: () => setPage(Page.Resleeves),
|
toGrafting: () => setPage(Page.Grafting),
|
||||||
toScriptEditor: (files: Record<string, string>, options?: ScriptEditorRouteOptions) => {
|
toScriptEditor: (files: Record<string, string>, options?: ScriptEditorRouteOptions) => {
|
||||||
setEditorOptions({
|
setEditorOptions({
|
||||||
files,
|
files,
|
||||||
@ -429,8 +429,8 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
|||||||
mainPage = <BladeburnerRoot />;
|
mainPage = <BladeburnerRoot />;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Page.Resleeves: {
|
case Page.Grafting: {
|
||||||
mainPage = <ResleeveRoot />;
|
mainPage = <GraftingRoot />;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Page.Travel: {
|
case Page.Travel: {
|
||||||
|
@ -143,51 +143,66 @@ function Work(): React.ReactElement {
|
|||||||
let details = <></>;
|
let details = <></>;
|
||||||
let header = <></>;
|
let header = <></>;
|
||||||
let innerText = <></>;
|
let innerText = <></>;
|
||||||
if (player.workType === CONSTANTS.WorkTypeCompanyPartTime || player.workType === CONSTANTS.WorkTypeCompany) {
|
switch (player.workType) {
|
||||||
details = (
|
case CONSTANTS.WorkTypeCompanyPartTime:
|
||||||
<>
|
case CONSTANTS.WorkTypeCompany:
|
||||||
{player.jobs[player.companyName]} at <strong>{player.companyName}</strong>
|
details = (
|
||||||
</>
|
<>
|
||||||
);
|
{player.jobs[player.companyName]} at <strong>{player.companyName}</strong>
|
||||||
header = (
|
</>
|
||||||
<>
|
);
|
||||||
Working at <strong>{player.companyName}</strong>
|
header = (
|
||||||
</>
|
<>
|
||||||
);
|
Working at <strong>{player.companyName}</strong>
|
||||||
innerText = (
|
</>
|
||||||
<>
|
);
|
||||||
+<Reputation reputation={player.workRepGained} /> rep
|
innerText = (
|
||||||
</>
|
<>
|
||||||
);
|
+<Reputation reputation={player.workRepGained} /> rep
|
||||||
} else if (player.workType === CONSTANTS.WorkTypeFaction) {
|
</>
|
||||||
details = (
|
);
|
||||||
<>
|
break;
|
||||||
{player.factionWorkType} for <strong>{player.currentWorkFactionName}</strong>
|
case CONSTANTS.WorkTypeFaction:
|
||||||
</>
|
details = (
|
||||||
);
|
<>
|
||||||
header = (
|
{player.factionWorkType} for <strong>{player.currentWorkFactionName}</strong>
|
||||||
<>
|
</>
|
||||||
Working for <strong>{player.currentWorkFactionName}</strong>
|
);
|
||||||
</>
|
header = (
|
||||||
);
|
<>
|
||||||
innerText = (
|
Working for <strong>{player.currentWorkFactionName}</strong>
|
||||||
<>
|
</>
|
||||||
+<Reputation reputation={player.workRepGained} /> rep
|
);
|
||||||
</>
|
innerText = (
|
||||||
);
|
<>
|
||||||
} else if (player.workType === CONSTANTS.WorkTypeStudyClass) {
|
+<Reputation reputation={player.workRepGained} /> rep
|
||||||
details = <>{player.workType}</>;
|
</>
|
||||||
header = <>You are {player.className}</>;
|
);
|
||||||
innerText = <>{convertTimeMsToTimeElapsedString(player.timeWorked)}</>;
|
break;
|
||||||
} else if (player.workType === CONSTANTS.WorkTypeCreateProgram) {
|
case CONSTANTS.WorkTypeStudyClass:
|
||||||
details = <>Coding {player.createProgramName}</>;
|
details = <>{player.workType}</>;
|
||||||
header = <>Creating a program</>;
|
header = <>You are {player.className}</>;
|
||||||
innerText = (
|
innerText = <>{convertTimeMsToTimeElapsedString(player.timeWorked)}</>;
|
||||||
<>
|
break;
|
||||||
{player.createProgramName}{" "}
|
case CONSTANTS.WorkTypeCreateProgram:
|
||||||
{((player.timeWorkedCreateProgram / player.timeNeededToCompleteWork) * 100).toFixed(2)}%
|
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 (
|
return (
|
||||||
|
@ -23,7 +23,7 @@ export enum Page {
|
|||||||
Job,
|
Job,
|
||||||
Milestones,
|
Milestones,
|
||||||
Options,
|
Options,
|
||||||
Resleeves,
|
Grafting,
|
||||||
Sleeves,
|
Sleeves,
|
||||||
Stats,
|
Stats,
|
||||||
StockMarket,
|
StockMarket,
|
||||||
@ -74,7 +74,7 @@ export interface IRouter {
|
|||||||
toInfiltration(location: Location): void;
|
toInfiltration(location: Location): void;
|
||||||
toJob(): void;
|
toJob(): void;
|
||||||
toMilestones(): void;
|
toMilestones(): void;
|
||||||
toResleeves(): void;
|
toGrafting(): void;
|
||||||
toScriptEditor(files?: Record<string, string>, options?: ScriptEditorRouteOptions): void;
|
toScriptEditor(files?: Record<string, string>, options?: ScriptEditorRouteOptions): void;
|
||||||
toSleeves(): void;
|
toSleeves(): void;
|
||||||
toStockMarket(): void;
|
toStockMarket(): void;
|
||||||
|
@ -41,8 +41,8 @@ export function WorkInProgressRoot(): React.ReactElement {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography variant="h4" color="primary">
|
<Typography variant="h4" color="primary">
|
||||||
You have not joined {player.currentWorkFactionName || "(Faction not found)"} yet or cannot work at this time,
|
You have not joined {player.currentWorkFactionName || "(Faction not found)"} yet or cannot work at this
|
||||||
please try again if you think this should have worked
|
time, please try again if you think this should have worked
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button onClick={() => router.toFactions()}>Back to Factions</Button>
|
<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();
|
if (!player.workType) router.toTerminal();
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
|
Loading…
Reference in New Issue
Block a user