mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-25 18:50:56 +02:00
@@ -17,6 +17,10 @@ import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums";
|
||||
import Button from "@mui/material/Button";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import List from "@mui/material/List";
|
||||
import { ExpandLess, ExpandMore } from "@mui/icons-material";
|
||||
import { Box, Paper, ListItemButton, ListItemText, Typography, Collapse } from "@mui/material";
|
||||
import { CONSTANTS } from "../../Constants";
|
||||
import { formatNumber } from "../../utils/StringHelperFunctions";
|
||||
|
||||
export function InstalledAugmentations(): React.ReactElement {
|
||||
const setRerender = useState(true)[1];
|
||||
@@ -55,6 +59,38 @@ export function InstalledAugmentations(): React.ReactElement {
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<List dense>
|
||||
{player.entropy > 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 Virus - Level {player.entropy}
|
||||
</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.entropy) * 100, 3)}% (multiplicative)
|
||||
</Typography>
|
||||
</Box>
|
||||
</Collapse>
|
||||
</Box>
|
||||
);
|
||||
})()}
|
||||
|
||||
{sourceAugs.map((e) => {
|
||||
const aug = Augmentations[e.name];
|
||||
|
||||
|
||||
@@ -425,7 +425,7 @@ BitNodes["BitNode10"] = new BitNode(
|
||||
This BitNode unlocks Sleeve technology. Sleeve technology allows you to:
|
||||
<br />
|
||||
<br />
|
||||
1. Re-sleeve: Purchase and transfer your consciousness into a new body
|
||||
1. Grafting: Visit VitaLife in New Tokyo to be able to obtain Augmentations without needing to install
|
||||
<br />
|
||||
2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks
|
||||
synchronously
|
||||
|
||||
@@ -1466,20 +1466,19 @@ export class Bladeburner implements IBladeburner {
|
||||
if (isNaN(eff) || eff < 0) {
|
||||
throw new Error("Field Analysis Effectiveness calculated to be NaN or negative");
|
||||
}
|
||||
const hackingExpGain = 20 * player.hacking_exp_mult,
|
||||
charismaExpGain = 20 * player.charisma_exp_mult;
|
||||
const hackingExpGain = 20 * player.hacking_exp_mult;
|
||||
const charismaExpGain = 20 * player.charisma_exp_mult;
|
||||
const rankGain = 0.1 * BitNodeMultipliers.BladeburnerRank;
|
||||
player.gainHackingExp(hackingExpGain);
|
||||
player.gainIntelligenceExp(BladeburnerConstants.BaseIntGain);
|
||||
player.gainCharismaExp(charismaExpGain);
|
||||
this.changeRank(player, 0.1 * BitNodeMultipliers.BladeburnerRank);
|
||||
this.changeRank(player, rankGain);
|
||||
this.getCurrentCity().improvePopulationEstimateByPercentage(eff * this.skillMultipliers.successChanceEstimate);
|
||||
if (this.logging.general) {
|
||||
this.log(
|
||||
"Field analysis completed. Gained 0.1 rank, " +
|
||||
formatNumber(hackingExpGain, 1) +
|
||||
" hacking exp, and " +
|
||||
formatNumber(charismaExpGain, 1) +
|
||||
" charisma exp",
|
||||
`Field analysis completed. Gained ${formatNumber(rankGain, 2)} rank, ` +
|
||||
`${formatNumber(hackingExpGain, 1)} hacking exp, and ` +
|
||||
`${formatNumber(charismaExpGain, 1)} charisma exp`,
|
||||
);
|
||||
}
|
||||
this.startAction(player, this.action); // Repeat action
|
||||
|
||||
@@ -4,7 +4,6 @@ import { BladeburnerConstants } from "../data/Constants";
|
||||
import { formatNumber } from "../../utils/StringHelperFunctions";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
}
|
||||
@@ -23,8 +22,7 @@ export function SkillPage(props: IProps): React.ReactElement {
|
||||
<strong>Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}</strong>
|
||||
</Typography>
|
||||
<Typography>
|
||||
You will gain one skill point every{" "}
|
||||
{BladeburnerConstants.RanksPerSkillPoint * BitNodeMultipliers.BladeburnerSkillCost} ranks.
|
||||
You will gain one skill point every {BladeburnerConstants.RanksPerSkillPoint} ranks.
|
||||
<br />
|
||||
Note that when upgrading a skill, the benefit for that skill is additive. However, the effects of different
|
||||
skills with each other is multiplicative.
|
||||
|
||||
+103
-48
@@ -41,6 +41,7 @@ export const CONSTANTS: {
|
||||
IntelligenceInfiltrationWeight: number;
|
||||
IntelligenceCrimeBaseExpGain: number;
|
||||
IntelligenceProgramBaseExpGain: number;
|
||||
IntelligenceGraftBaseExpGain: number;
|
||||
IntelligenceTerminalHackBaseExpGain: number;
|
||||
IntelligenceSingFnBaseExpGain: number;
|
||||
IntelligenceClassBaseExpGain: number;
|
||||
@@ -71,6 +72,7 @@ export const CONSTANTS: {
|
||||
WorkTypeCreateProgram: string;
|
||||
WorkTypeStudyClass: string;
|
||||
WorkTypeCrime: string;
|
||||
WorkTypeGraftAugmentation: string;
|
||||
ClassStudyComputerScience: string;
|
||||
ClassDataStructures: string;
|
||||
ClassNetworks: string;
|
||||
@@ -108,11 +110,14 @@ export const CONSTANTS: {
|
||||
CodingContractBaseFactionRepGain: number;
|
||||
CodingContractBaseCompanyRepGain: number;
|
||||
CodingContractBaseMoneyGain: number;
|
||||
AugmentationGraftingCostMult: number;
|
||||
AugmentationGraftingTimeBase: number;
|
||||
EntropyEffect: number;
|
||||
TotalNumBitNodes: number;
|
||||
LatestUpdate: string;
|
||||
} = {
|
||||
VersionString: "1.5.0",
|
||||
VersionNumber: 11,
|
||||
VersionString: "1.6.0",
|
||||
VersionNumber: 12,
|
||||
|
||||
// Speed (in ms) at which the main loop is updated
|
||||
_idleSpeed: 200,
|
||||
@@ -180,6 +185,7 @@ export const CONSTANTS: {
|
||||
IntelligenceInfiltrationWeight: 0.1, // Weight for how much int affects infiltration success rates
|
||||
IntelligenceCrimeBaseExpGain: 0.05,
|
||||
IntelligenceProgramBaseExpGain: 0.1, // Program required hack level divided by this to determine int exp gain
|
||||
IntelligenceGraftBaseExpGain: 0.05,
|
||||
IntelligenceTerminalHackBaseExpGain: 200, // Hacking exp divided by this to determine int exp gain
|
||||
IntelligenceSingFnBaseExpGain: 1.5,
|
||||
IntelligenceClassBaseExpGain: 0.01,
|
||||
@@ -224,6 +230,7 @@ export const CONSTANTS: {
|
||||
WorkTypeCreateProgram: "Working on Create a Program",
|
||||
WorkTypeStudyClass: "Studying or Taking a class at university",
|
||||
WorkTypeCrime: "Committing a crime",
|
||||
WorkTypeGraftAugmentation: "Grafting an Augmentation",
|
||||
|
||||
ClassStudyComputerScience: "studying Computer Science",
|
||||
ClassDataStructures: "taking a Data Structures course",
|
||||
@@ -269,68 +276,116 @@ export const CONSTANTS: {
|
||||
CodingContractBaseCompanyRepGain: 4000,
|
||||
CodingContractBaseMoneyGain: 75e6,
|
||||
|
||||
// Augmentation crafting multipliers
|
||||
AugmentationGraftingCostMult: 3,
|
||||
AugmentationGraftingTimeBase: 3600000,
|
||||
|
||||
// Value raised to the number of entropy stacks, then multiplied to player multipliers
|
||||
EntropyEffect: 0.98,
|
||||
|
||||
// BitNode/Source-File related stuff
|
||||
TotalNumBitNodes: 24,
|
||||
|
||||
LatestUpdate: `
|
||||
v1.5.0 - Steam Cloud integration
|
||||
--------------------------------
|
||||
v1.6.0 - 2022-03-29 Grafting
|
||||
----------------------------
|
||||
|
||||
** Steam Cloud Saving **
|
||||
** Vitalife secret lab **
|
||||
|
||||
* Added (@MartinFournier)
|
||||
* A new mechanic called Augmentation Grafting has been added. Resleeving has been removed.
|
||||
* Credit to @nikfolas for his incredible work.
|
||||
|
||||
** Stanek **
|
||||
|
||||
* BREAKING: Many functions in the stanek API were renamed in order to avoid name collision with things like Map.prototype.get
|
||||
|
||||
** UI **
|
||||
|
||||
* background now matches game primary color (@nickofolas)
|
||||
* page title contains version (@MartinFourier)
|
||||
* Major text editor improvements (@nickofolas)
|
||||
* Display bonus time on sleeve page (@MartinFourier)
|
||||
* Several UI improvements (@nickofolas, @smolgumball, @DrCuriosity, @phyzical)
|
||||
* Fix aug display in alpha (@Dominik Winter)
|
||||
* Fix display of corporation product equation (@SagePtr)
|
||||
* Make Bitverse more accessible (@ChrissiQ)
|
||||
* Make corporation warehouse more accessible (@ChrissiQ)
|
||||
* Make tab style more consistent (@nikfolas)
|
||||
* Major update to Sleeve, Gang UI, and Create Program (@nikfolas)
|
||||
* re-add pre tags to support slash n in prompt (@jacktose)
|
||||
* Tabelize linked output of 'ls' (@Master-Guy)
|
||||
* Add the ability to filter open scripts (@phyzical)
|
||||
* Add minHeight to editor tabs (@nickofolas)
|
||||
* Properly expand gang equipment cards to fill entire screen (@nickofolas)
|
||||
* Add shortcut to Faction augmentations page from FactionsRoot (@nickofolas)
|
||||
* Fix extra space on editor tabs (@nickofolas)
|
||||
* Present offline message as list (@DSteve595)
|
||||
* add box showing remaining augments per faction (@jjayeon)
|
||||
* Add tab switching support to vim mode (@JParisFerrer)
|
||||
* Show current task on gang management screen (@zeddrak)
|
||||
* Fix for ui of gang members current task when set via api (@phyzical)
|
||||
* Don't hide irrelevant materials if their stock is not empty and hide irrelevant divisions from Export (@SagePtr)
|
||||
* Fix regex to enable alpha transparency hex codes (8 digits) (@surdaft)
|
||||
|
||||
** Netscript **
|
||||
** API **
|
||||
|
||||
* Fix bug with async.
|
||||
* Add 'printf' ns function (@Ninetailed)
|
||||
* Remove blob caching.
|
||||
* Fix formulas access check (@Ornedan)
|
||||
* Fix bug in exp calculation (@qcorradi)
|
||||
* Fix NaN comparison (@qcorradi)
|
||||
* Fix travelToCity with bad argument (@SlyCedix)
|
||||
* Fix bug where augs could not be purchased via sing (@reacocard)
|
||||
* Fix rounding error in donateToFaction (@Risenafis)
|
||||
* Fix bug with weakenAnalyze (@rhobes)
|
||||
* Prevent exploit with atExit (@Ornedan)
|
||||
* Double 'share' power
|
||||
* Added dark web functions to ns api
|
||||
* BREAKING: purchaseTor() should returns true if player already has Tor. (@DavidGrinberg, @waffleattack)
|
||||
* Implement getBonusTime in Corporation API (@t-wolfeadam)
|
||||
* Added functions to purchase TIX and WSI (@incubusnb)
|
||||
* purchaseSleeveAug checks shock value (@incubusnb)
|
||||
* Fix bug with hacknet api
|
||||
* Fix spendHashes bug
|
||||
* Added 0 cost of asleep() (@Master-Guy)
|
||||
* Fix some misleading corporation errors (@TheRealMaxion)
|
||||
* expose the inBladeburner on the player object (@phyzical)
|
||||
* added ram charge for stanek width and height (@phyzical)
|
||||
* Fix sufficient player money check to buy back shares. (@ChrissiQ)
|
||||
* Fix Static Ram Circumventing for some NS functions (@CrafterKolyan)
|
||||
* added CorporationSoftCap to NetscriptDefinitions (@phyzical)
|
||||
* Added definition of autocomplete() 'data' argument. (@tigercat2000)
|
||||
* Adding support for text/select options in Prompt command (@PhilipArmstead)
|
||||
* Added the ability to exportGame via api (@phyzical)
|
||||
|
||||
** Corporations **
|
||||
** Arcade **
|
||||
|
||||
* Fix bugs with corp API (@pigalot)
|
||||
* Add smart supply func to corp API (@pd)
|
||||
* Added an arcade to New Tokyo where you can play a 4 year old version of bitburner.
|
||||
|
||||
** Misc. **
|
||||
|
||||
* The file API now allows GET and DELETE (@lordducky)
|
||||
* Force achievement calculation on BN completion (@SagePtr)
|
||||
* Cleanup in repository (@MartinFourier)
|
||||
* Several improvements to the electron version (@MartinFourier)
|
||||
* Fix bug with casino roulette (@jamie-mac)
|
||||
* Terminal history persists in savefile (@MartinFourier)
|
||||
* Fix tests (@jamie-mac)
|
||||
* Fix crash with electron windows tracker (@smolgumball)
|
||||
* Fix BN6/7 passive reputation gain (@BrianLDev)
|
||||
* Fix Sleeve not resetting on install (@waffleattack)
|
||||
* Sort joined factions (@jjayeon)
|
||||
* Update documentation / typo (@lethern, @Meowdoleon, @JohnnyUrosevic, @JosephDavidTalbot,
|
||||
@pd, @lethern, @lordducky, @zeddrak, @fearnlj01, @reasonablytall, @MatthewTh0,
|
||||
@SagePtr, @manniL, @Jedimaster4559, @loganville, @Arrow2thekn33, @wdpk, @fwolfst,
|
||||
@fschoenfeldt, @Waladil, @AdamTReineke, @citrusmunch, @factubsio, @ashtongreen,
|
||||
@ChrissiQ, @DJ-Laser, @waffleattack, @ApamNapat, @CrafterKolyan, @DSteve595)
|
||||
* Add a warning triggered while auto-saves are off. (@MartinFournier)
|
||||
* Log info for field analysis now displays actual rank gained. (@ApamNapat)
|
||||
* Removed BladeburnerSkillCost from skill point cost description. (@ApamNapat)
|
||||
* Fix handling for UpArrow in bladeburner console. (@dowinter)
|
||||
* Add GitHub action to check PRs for generated files. (@MartinFournier)
|
||||
* Cap Staneks gift at 25x25 to prevent crashes. (@waffleattack)
|
||||
* Remove old & unused files from repository. (@MartinFournier)
|
||||
* Factions on the factions screens are sorted by story progress / type. (@phyzical)
|
||||
* Fix log manager not picking up new runs of scripts. (@phyzical)
|
||||
* Added prettier to cicd.
|
||||
* UI improvements (@phyzical)
|
||||
* Documentation / Typos (@nanogyth, @Master-Guy, @incubusnb, @ApamNapat, @phyzical, @SagePtr)
|
||||
* Give player code a copy of Division.upgrades instead of the live object (@Ornedan)
|
||||
* Fix bug with small town achievement.
|
||||
* Fix bug with purchaseSleeveAug (@phyzical)
|
||||
* Check before unlocking corp upgrade (@gianfun)
|
||||
* General codebase improvements. (@phyzical, @Master-Guy, @ApamNapat)
|
||||
* Waiting on promises in NS1 no longer freezes the script. (@Master-Guy)
|
||||
* Fix bug with missing ramcost for tFormat (@TheMas3212)
|
||||
* Fix crash with new prompt
|
||||
* Quick fix to prevent division by 0 in terminal (@Master-Guy)
|
||||
* removed ip references (@phyzical, @Master-Guy)
|
||||
* Terminal now supports 'ls -l'
|
||||
* Fix negative number formatting (@Master-Guy)
|
||||
* Fix unique ip generation (@InDieTasten)
|
||||
* remove terminal command theme from docs (@phyzical)
|
||||
* Fix 'Augmentations Left' with gang factions (@nickofolas)
|
||||
* Attempt to fix 'bladeburner.process()' early routing issue (@MartinFournier)
|
||||
* work in progress augment fix (@phyzical)
|
||||
* Fixes missing space in Smart Supply (@TheRealMaxion)
|
||||
* Change license to Apache 2 with Commons Clause
|
||||
* updated regex sanitization (@mbrannen)
|
||||
* Sleeve fix for when faction isnt found (@phyzical)
|
||||
* Fix editor "close" naming (@phyzical)
|
||||
* Fix bug with sleeves where some factions would be listed as workable. (@phyzical)
|
||||
* Fix research tree of product industries post-prestige (@pd)
|
||||
* Added a check for exisiting industry type before expanding (@phyzical)
|
||||
* fix hackAnalyzeThreads returning infinity (@chrisrabe)
|
||||
* Make growthAnalyze more accurate (@dwRchyngqxs)
|
||||
* Add 'Zoom -> Reset Zoom' command to Steam (@smolgumball)
|
||||
* Add hasOwnProperty check to GetServer (@SagePtr)
|
||||
* Speed up employee productivity calculation (@pd)
|
||||
* Field Work and Security Work benefit from 'share' (@SagePtr)
|
||||
* Nerf noodle bar.
|
||||
`,
|
||||
};
|
||||
|
||||
@@ -19,14 +19,14 @@ export class StaneksGift implements IStaneksGift {
|
||||
fragments: ActiveFragment[] = [];
|
||||
|
||||
baseSize(): number {
|
||||
return StanekConstants.BaseSize + BitNodeMultipliers.StaneksGiftExtraSize + Player.sourceFileLvl(13);
|
||||
return StanekConstants.BaseSize + BitNodeMultipliers.StaneksGiftExtraSize + Player.sourceFileLvl(13)
|
||||
}
|
||||
|
||||
width(): number {
|
||||
return Math.floor(this.baseSize() / 2 + 1);
|
||||
return Math.min(Math.floor(this.baseSize() / 2 + 1),StanekConstants.MaxSize);
|
||||
}
|
||||
height(): number {
|
||||
return Math.floor(this.baseSize() / 2 + 0.6);
|
||||
return Math.min(Math.floor(this.baseSize() / 2 + 0.6),StanekConstants.MaxSize);
|
||||
}
|
||||
|
||||
charge(player: IPlayer, af: ActiveFragment, threads: number): void {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
export const StanekConstants: {
|
||||
RAMBonus: number;
|
||||
BaseSize: number;
|
||||
MaxSize: number;
|
||||
} = {
|
||||
RAMBonus: 0.1,
|
||||
BaseSize: 9,
|
||||
MaxSize: 25
|
||||
};
|
||||
|
||||
@@ -23,6 +23,7 @@ import { Sleeves } from "./DevMenu/ui/Sleeves";
|
||||
import { Stanek } from "./DevMenu/ui/Stanek";
|
||||
import { TimeSkip } from "./DevMenu/ui/TimeSkip";
|
||||
import { Achievements } from "./DevMenu/ui/Achievements";
|
||||
import { Entropy } from "./DevMenu/ui/Entropy";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { Exploit } from "./Exploits/Exploit";
|
||||
|
||||
@@ -63,6 +64,7 @@ export function DevMenuRoot(props: IProps): React.ReactElement {
|
||||
|
||||
<TimeSkip player={props.player} engine={props.engine} />
|
||||
<Achievements player={props.player} engine={props.engine} />
|
||||
<Entropy player={props.player} engine={props.engine} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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.entropy += num;
|
||||
props.player.applyEntropy(props.player.entropy);
|
||||
}}
|
||||
subtract={(num) => {
|
||||
props.player.entropy -= num;
|
||||
props.player.applyEntropy(props.player.entropy);
|
||||
}}
|
||||
tons={() => {
|
||||
props.player.entropy += 1e12;
|
||||
props.player.applyEntropy(props.player.entropy);
|
||||
}}
|
||||
reset={() => {
|
||||
props.player.entropy = 0;
|
||||
props.player.applyEntropy(props.player.entropy);
|
||||
}}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
@@ -43,17 +43,21 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
|
||||
|
||||
function getAugs(): string[] {
|
||||
if (isPlayersGang) {
|
||||
const augs: string[] = [];
|
||||
for (const augName of Object.keys(Augmentations)) {
|
||||
if (augName === AugmentationNames.NeuroFluxGovernor) continue;
|
||||
if (augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) continue;
|
||||
const aug = Augmentations[augName];
|
||||
if (!aug.isSpecial) {
|
||||
augs.push(augName);
|
||||
}
|
||||
let augs = Object.values(Augmentations);
|
||||
|
||||
// Remove blacklisted augs.
|
||||
const blacklist = [AugmentationNames.NeuroFluxGovernor, AugmentationNames.TheRedPill];
|
||||
augs = augs.filter((a) => !blacklist.includes(a.name));
|
||||
|
||||
// Remove special augs.
|
||||
augs = augs.filter((a) => !a.isSpecial);
|
||||
|
||||
// Remove faction-unique augs outside BN2. (But keep the one for this faction.)
|
||||
if (player.bitNodeN !== 2) {
|
||||
augs = augs.filter((a) => a.factions.length > 1 || props.faction.augmentations.includes(a.name));
|
||||
}
|
||||
|
||||
return augs;
|
||||
return augs.map((a) => a.name);
|
||||
} else {
|
||||
return props.faction.augmentations.slice();
|
||||
}
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Container,
|
||||
Paper,
|
||||
TableBody,
|
||||
TableRow,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
import { Box, Button, Container, Paper, TableBody, TableRow, Typography } from "@mui/material";
|
||||
|
||||
import { Augmentations } from "../../Augmentation/Augmentations";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
@@ -19,6 +11,7 @@ import { IRouter } from "../../ui/Router";
|
||||
import { Faction } from "../Faction";
|
||||
import { joinFaction } from "../FactionHelpers";
|
||||
import { Factions } from "../Factions";
|
||||
import { FactionNames } from "../data/FactionNames";
|
||||
|
||||
export const InvitationsSeen: string[] = [];
|
||||
|
||||
@@ -64,21 +57,28 @@ export function FactionsRoot(props: IProps): React.ReactElement {
|
||||
|
||||
if (isPlayersGang) {
|
||||
for (const augName of Object.keys(Augmentations)) {
|
||||
const aug = Augmentations[augName];
|
||||
if (
|
||||
augName === AugmentationNames.NeuroFluxGovernor ||
|
||||
augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2 ||
|
||||
Augmentations[augName].isSpecial
|
||||
) continue;
|
||||
augs.push(augName)
|
||||
(augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) ||
|
||||
// Special augs (i.e. Bladeburner augs)
|
||||
aug.isSpecial ||
|
||||
// Exclusive augs (i.e. QLink)
|
||||
(aug.factions.length <= 1 && !faction.augmentations.includes(augName) && player.bitNodeN !== 2)
|
||||
)
|
||||
continue;
|
||||
augs.push(augName);
|
||||
}
|
||||
} else {
|
||||
augs = faction.augmentations.slice();
|
||||
}
|
||||
|
||||
return augs.filter(
|
||||
(augmentation: string) => !player.hasAugmentation(augmentation)
|
||||
).length;
|
||||
}
|
||||
return augs.filter((augmentation: string) => !player.hasAugmentation(augmentation)).length;
|
||||
};
|
||||
|
||||
const allFactions = Object.values(FactionNames).map((faction) => faction as string);
|
||||
const allJoinedFactions = props.player.factions.slice(0);
|
||||
allJoinedFactions.sort((a, b) => allFactions.indexOf(a) - allFactions.indexOf(b));
|
||||
|
||||
return (
|
||||
<Container disableGutters maxWidth="md" sx={{ mx: 0, mb: 10 }}>
|
||||
@@ -92,11 +92,11 @@ export function FactionsRoot(props: IProps): React.ReactElement {
|
||||
<Typography variant="h5" color="primary" mt={2} mb={1}>
|
||||
Factions you have joined:
|
||||
</Typography>
|
||||
{(props.player.factions.length > 0 && (
|
||||
{(allJoinedFactions.length > 0 && (
|
||||
<Paper sx={{ my: 1, p: 1, pb: 0, display: "inline-block" }}>
|
||||
<Table padding="none" style={{ width: "fit-content" }}>
|
||||
<TableBody>
|
||||
{props.player.factions.map((faction: string) => (
|
||||
{allJoinedFactions.map((faction: string) => (
|
||||
<TableRow key={faction}>
|
||||
<TableCell>
|
||||
<Typography noWrap mb={1}>
|
||||
@@ -110,7 +110,7 @@ export function FactionsRoot(props: IProps): React.ReactElement {
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Box ml={1} mb={1}>
|
||||
<Button sx={{ width: '100%' }} onClick={() => openFactionAugPage(Factions[faction])}>
|
||||
<Button sx={{ width: "100%" }} onClick={() => openFactionAugPage(Factions[faction])}>
|
||||
Augmentations Left: {getAugsLeft(Factions[faction], props.player)}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
+1
-1
@@ -227,7 +227,7 @@ export class Gang implements IGang {
|
||||
AllGangs[thisGang].territory += territoryGain;
|
||||
if (AllGangs[thisGang].territory > 0.999) AllGangs[thisGang].territory = 1;
|
||||
AllGangs[otherGang].territory -= territoryGain;
|
||||
if (AllGangs[thisGang].territory < 0.001) AllGangs[thisGang].territory = 0;
|
||||
if (AllGangs[thisGang].territory) AllGangs[thisGang].territory = 0;
|
||||
if (thisGang === gangName) {
|
||||
this.clash(true); // Player won
|
||||
AllGangs[otherGang].power *= 1 / 1.01;
|
||||
|
||||
@@ -72,8 +72,8 @@ export function SpecialLocation(props: IProps): React.ReactElement {
|
||||
/**
|
||||
* Click handler for Resleeving button at New Tokyo VitaLife
|
||||
*/
|
||||
function handleResleeving(): void {
|
||||
router.toResleeves();
|
||||
function handleGrafting(): void {
|
||||
router.toGrafting();
|
||||
}
|
||||
|
||||
function renderBladeburner(): React.ReactElement {
|
||||
@@ -151,11 +151,11 @@ export function SpecialLocation(props: IProps): React.ReactElement {
|
||||
);
|
||||
}
|
||||
|
||||
function renderResleeving(): React.ReactElement {
|
||||
if (!player.canAccessResleeving()) {
|
||||
function renderGrafting(): React.ReactElement {
|
||||
if (!player.canAccessGrafting()) {
|
||||
return <></>;
|
||||
}
|
||||
return <Button onClick={handleResleeving}>Re-Sleeve</Button>;
|
||||
return <Button onClick={handleGrafting} sx={{ my: 5 }}>Enter the secret lab</Button>;
|
||||
}
|
||||
|
||||
function handleCotMG(): void {
|
||||
@@ -299,7 +299,7 @@ export function SpecialLocation(props: IProps): React.ReactElement {
|
||||
|
||||
switch (props.loc.name) {
|
||||
case LocationName.NewTokyoVitaLife: {
|
||||
return renderResleeving();
|
||||
return renderGrafting();
|
||||
}
|
||||
case LocationName.Sector12CityHall: {
|
||||
return <CreateCorporation />;
|
||||
|
||||
@@ -386,6 +386,12 @@ export const RamCosts: IMap<any> = {
|
||||
getGameInfo: 0,
|
||||
},
|
||||
|
||||
grafting: {
|
||||
getAugmentationCraftPrice: 3.75,
|
||||
getAugmentationCraftTime: 3.75,
|
||||
graftAugmentation: 7.5,
|
||||
},
|
||||
|
||||
heart: {
|
||||
// Easter egg function
|
||||
break: 0,
|
||||
|
||||
@@ -70,6 +70,7 @@ import { NetscriptCodingContract } from "./NetscriptFunctions/CodingContract";
|
||||
import { NetscriptCorporation } from "./NetscriptFunctions/Corporation";
|
||||
import { NetscriptFormulas } from "./NetscriptFunctions/Formulas";
|
||||
import { NetscriptStockMarket } from "./NetscriptFunctions/StockMarket";
|
||||
import { NetscriptGrafting } from "./NetscriptFunctions/Grafting";
|
||||
import { IPort } from "./NetscriptPort";
|
||||
|
||||
import {
|
||||
@@ -480,6 +481,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
||||
const singularity = NetscriptSingularity(Player, workerScript, helper);
|
||||
const stockmarket = NetscriptStockMarket(Player, workerScript, helper);
|
||||
const ui = NetscriptUserInterface(Player, workerScript, helper);
|
||||
const grafting = NetscriptGrafting(Player, workerScript, helper);
|
||||
|
||||
const base: INS = {
|
||||
...singularity,
|
||||
@@ -493,6 +495,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
||||
ui: ui,
|
||||
formulas: formulas,
|
||||
stock: stockmarket,
|
||||
grafting: grafting,
|
||||
args: workerScript.args,
|
||||
hacknet: hacknet,
|
||||
sprintf: sprintf,
|
||||
@@ -2315,6 +2318,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
||||
tor: Player.hasTorRouter(),
|
||||
inBladeburner: Player.inBladeburner(),
|
||||
hasCorporation: Player.hasCorporation(),
|
||||
entropy: Player.entropy,
|
||||
};
|
||||
Object.assign(data.jobs, Player.jobs);
|
||||
return data;
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { CityName } from "../Locations/data/CityNames";
|
||||
import { Augmentations } from "../Augmentation/Augmentations";
|
||||
import { getRamCost } from "../Netscript/RamCostGenerator";
|
||||
import { WorkerScript } from "../Netscript/WorkerScript";
|
||||
import { GraftableAugmentation } from "../PersonObjects/Grafting/GraftableAugmentation";
|
||||
import { getAvailableAugs } from "../PersonObjects/Grafting/ui/GraftingRoot";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import { Grafting as IGrafting } from "../ScriptEditor/NetscriptDefinitions";
|
||||
import { Router } from "../ui/GameRoot";
|
||||
import { INetscriptHelper } from "./INetscriptHelper";
|
||||
|
||||
export function NetscriptGrafting(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): IGrafting {
|
||||
const checkGraftingAPIAccess = (func: string): 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 {
|
||||
getAugmentationGraftPrice: (_augName: unknown): number => {
|
||||
const augName = helper.string("getAugmentationGraftPrice", "augName", _augName);
|
||||
helper.updateDynamicRam("getAugmentationGraftPrice", getRamCost(player, "grafting", "getAugmentationGraftPrice"));
|
||||
checkGraftingAPIAccess("getAugmentationGraftPrice");
|
||||
if (!Augmentations.hasOwnProperty(augName)) {
|
||||
throw helper.makeRuntimeErrorMsg("grafting.getAugmentationGraftPrice", `Invalid aug: ${augName}`);
|
||||
}
|
||||
const craftableAug = new GraftableAugmentation(Augmentations[augName]);
|
||||
return craftableAug.cost;
|
||||
},
|
||||
|
||||
getAugmentationGraftTime: (_augName: string): number => {
|
||||
const augName = helper.string("getAugmentationGraftTime", "augName", _augName);
|
||||
helper.updateDynamicRam("getAugmentationGraftTime", getRamCost(player, "grafting", "getAugmentationGraftTime"));
|
||||
checkGraftingAPIAccess("getAugmentationGraftTime");
|
||||
if (!Augmentations.hasOwnProperty(augName)) {
|
||||
throw helper.makeRuntimeErrorMsg("grafting.getAugmentationGraftTime", `Invalid aug: ${augName}`);
|
||||
}
|
||||
const craftableAug = new GraftableAugmentation(Augmentations[augName]);
|
||||
return craftableAug.time;
|
||||
},
|
||||
|
||||
graftAugmentation: (_augName: string, _focus: unknown = true): boolean => {
|
||||
const augName = helper.string("graftAugmentation", "augName", _augName);
|
||||
const focus = helper.boolean(_focus);
|
||||
helper.updateDynamicRam("graftAugmentation", getRamCost(player, "grafting", "graftAugmentation"));
|
||||
checkGraftingAPIAccess("graftAugmentation");
|
||||
if (player.city !== CityName.NewTokyo) {
|
||||
throw helper.makeRuntimeErrorMsg(
|
||||
"grafting.graftAugmentation",
|
||||
"You must be in New Tokyo to begin crafting an Augmentation.",
|
||||
);
|
||||
}
|
||||
if (!getAvailableAugs(player).includes(augName)) {
|
||||
workerScript.log("grafting.graftAugmentation", () => `Invalid aug: ${augName}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const wasFocusing = player.focus;
|
||||
if (player.isWorking) {
|
||||
const txt = player.singularityStopWork();
|
||||
workerScript.log("graftAugmentation", () => txt);
|
||||
}
|
||||
|
||||
const craftableAug = new GraftableAugmentation(Augmentations[augName]);
|
||||
if (player.money < craftableAug.cost) {
|
||||
workerScript.log("grafting.graftAugmentation", () => `You don't have enough money to craft ${augName}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
player.loseMoney(craftableAug.cost, "augmentations");
|
||||
player.startGraftAugmentationWork(augName, craftableAug.time);
|
||||
|
||||
if (focus) {
|
||||
player.startFocusing();
|
||||
Router.toWork();
|
||||
} else if (wasFocusing) {
|
||||
player.stopFocusing();
|
||||
Router.toTerminal();
|
||||
}
|
||||
|
||||
workerScript.log("grafting.graftAugmentation", () => `Began crafting Augmentation ${augName}.`);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -42,7 +42,6 @@ import { Terminal } from "../Terminal";
|
||||
import { calculateHackingTime } from "../Hacking";
|
||||
import { Server } from "../Server/Server";
|
||||
import { netscriptCanHack } from "../Hacking/netscriptCanHack";
|
||||
import { FactionNames } from "../Faction/data/FactionNames";
|
||||
import { FactionInfos } from "../Faction/FactionInfo";
|
||||
|
||||
export function NetscriptSingularity(
|
||||
@@ -114,17 +113,21 @@ export function NetscriptSingularity(
|
||||
|
||||
// If player has a gang with this faction, return all augmentations.
|
||||
if (player.hasGangWith(facname)) {
|
||||
const res = [];
|
||||
for (const augName of Object.keys(Augmentations)) {
|
||||
if (augName === AugmentationNames.NeuroFluxGovernor) continue;
|
||||
if (augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) continue;
|
||||
const aug = Augmentations[augName];
|
||||
if (!aug.isSpecial) {
|
||||
res.push(augName);
|
||||
}
|
||||
let augs = Object.values(Augmentations);
|
||||
|
||||
// Remove blacklisted augs.
|
||||
const blacklist = [AugmentationNames.NeuroFluxGovernor, AugmentationNames.TheRedPill];
|
||||
augs = augs.filter((a) => !blacklist.includes(a.name));
|
||||
|
||||
// Remove special augs.
|
||||
augs = augs.filter((a) => !a.isSpecial);
|
||||
|
||||
// Remove faction-unique augs outside BN2. (But keep the one for this faction.)
|
||||
if (player.bitNodeN !== 2) {
|
||||
augs = augs.filter((a) => a.factions.length > 1 || Factions[facname].augmentations.includes(a.name));
|
||||
}
|
||||
|
||||
return res;
|
||||
return augs.map((a) => a.name);
|
||||
}
|
||||
|
||||
return faction.augmentations.slice();
|
||||
@@ -168,12 +171,17 @@ export function NetscriptSingularity(
|
||||
let augs = [];
|
||||
if (player.hasGangWith(faction)) {
|
||||
for (const augName of Object.keys(Augmentations)) {
|
||||
if (augName === AugmentationNames.NeuroFluxGovernor) continue;
|
||||
if (augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) continue;
|
||||
const tempAug = Augmentations[augName];
|
||||
if (!tempAug.isSpecial) {
|
||||
augs.push(augName);
|
||||
}
|
||||
const aug = Augmentations[augName];
|
||||
if (
|
||||
augName === AugmentationNames.NeuroFluxGovernor ||
|
||||
(augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) ||
|
||||
// Special augs (i.e. Bladeburner augs)
|
||||
aug.isSpecial ||
|
||||
// Exclusive augs (i.e. QLink)
|
||||
(aug.factions.length <= 1 && !fac.augmentations.includes(augName) && player.bitNodeN !== 2)
|
||||
)
|
||||
continue;
|
||||
augs.push(augName);
|
||||
}
|
||||
} else {
|
||||
augs = fac.augmentations;
|
||||
@@ -500,7 +508,7 @@ export function NetscriptSingularity(
|
||||
|
||||
if (player.hasTorRouter()) {
|
||||
workerScript.log("purchaseTor", () => "You already have a TOR router!");
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (player.money < CONSTANTS.TorRouterCost) {
|
||||
|
||||
@@ -22,28 +22,29 @@ export function NetscriptStanek(player: IPlayer, workerScript: WorkerScript, hel
|
||||
}
|
||||
|
||||
return {
|
||||
width: function (): number {
|
||||
helper.updateDynamicRam("width", getRamCost(player, "stanek", "width"));
|
||||
checkStanekAPIAccess("width");
|
||||
giftWidth: function (): number {
|
||||
helper.updateDynamicRam("giftWidth", getRamCost(player, "stanek", "giftWidth"));
|
||||
checkStanekAPIAccess("giftWidth");
|
||||
return staneksGift.width();
|
||||
},
|
||||
height: function (): number {
|
||||
helper.updateDynamicRam("height", getRamCost(player, "stanek", "height"));
|
||||
checkStanekAPIAccess("height");
|
||||
giftHeight: function (): number {
|
||||
helper.updateDynamicRam("giftHeight", getRamCost(player, "stanek", "giftHeight"));
|
||||
checkStanekAPIAccess("giftHeight");
|
||||
return staneksGift.height();
|
||||
},
|
||||
charge: function (_rootX: unknown, _rootY: unknown): Promise<void> {
|
||||
const rootX = helper.number("stanek.charge", "rootX", _rootX);
|
||||
const rootY = helper.number("stanek.charge", "rootY", _rootY);
|
||||
chargeFragment: function (_rootX: unknown, _rootY: unknown): Promise<void> {
|
||||
const rootX = helper.number("stanek.chargeFragment", "rootX", _rootX);
|
||||
const rootY = helper.number("stanek.chargeFragment", "rootY", _rootY);
|
||||
|
||||
helper.updateDynamicRam("charge", getRamCost(player, "stanek", "charge"));
|
||||
checkStanekAPIAccess("charge");
|
||||
helper.updateDynamicRam("chargeFragment", getRamCost(player, "stanek", "chargeFragment"));
|
||||
checkStanekAPIAccess("chargeFragment");
|
||||
const fragment = staneksGift.findFragment(rootX, rootY);
|
||||
if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.charge", `No fragment with root (${rootX}, ${rootY}).`);
|
||||
if (!fragment)
|
||||
throw helper.makeRuntimeErrorMsg("stanek.chargeFragment", `No fragment with root (${rootX}, ${rootY}).`);
|
||||
const time = staneksGift.inBonus() ? 200 : 1000;
|
||||
return netscriptDelay(time, workerScript).then(function () {
|
||||
const charge = staneksGift.charge(player, fragment, workerScript.scriptRef.threads);
|
||||
workerScript.log("stanek.charge", () => `Charged fragment for ${charge} charge.`);
|
||||
workerScript.log("stanek.chargeFragment", () => `Charged fragment for ${charge} charge.`);
|
||||
return Promise.resolve();
|
||||
});
|
||||
},
|
||||
@@ -61,49 +62,49 @@ export function NetscriptStanek(player: IPlayer, workerScript: WorkerScript, hel
|
||||
return { ...af.copy(), ...af.fragment().copy() };
|
||||
});
|
||||
},
|
||||
clear: function (): void {
|
||||
helper.updateDynamicRam("clear", getRamCost(player, "stanek", "clear"));
|
||||
checkStanekAPIAccess("clear");
|
||||
workerScript.log("stanek.clear", () => `Cleared Stanek's Gift.`);
|
||||
clearGift: function (): void {
|
||||
helper.updateDynamicRam("clearGift", getRamCost(player, "stanek", "clearGift"));
|
||||
checkStanekAPIAccess("clearGift");
|
||||
workerScript.log("stanek.clearGift", () => `Cleared Stanek's Gift.`);
|
||||
staneksGift.clear();
|
||||
},
|
||||
canPlace: function (_rootX: unknown, _rootY: unknown, _rotation: unknown, _fragmentId: unknown): boolean {
|
||||
const rootX = helper.number("stanek.canPlace", "rootX", _rootX);
|
||||
const rootY = helper.number("stanek.canPlace", "rootY", _rootY);
|
||||
const rotation = helper.number("stanek.canPlace", "rotation", _rotation);
|
||||
const fragmentId = helper.number("stanek.canPlace", "fragmentId", _fragmentId);
|
||||
helper.updateDynamicRam("canPlace", getRamCost(player, "stanek", "canPlace"));
|
||||
checkStanekAPIAccess("canPlace");
|
||||
canPlaceFragment: function (_rootX: unknown, _rootY: unknown, _rotation: unknown, _fragmentId: unknown): boolean {
|
||||
const rootX = helper.number("stanek.canPlaceFragment", "rootX", _rootX);
|
||||
const rootY = helper.number("stanek.canPlaceFragment", "rootY", _rootY);
|
||||
const rotation = helper.number("stanek.canPlaceFragment", "rotation", _rotation);
|
||||
const fragmentId = helper.number("stanek.canPlaceFragment", "fragmentId", _fragmentId);
|
||||
helper.updateDynamicRam("canPlaceFragment", getRamCost(player, "stanek", "canPlaceFragment"));
|
||||
checkStanekAPIAccess("canPlaceFragment");
|
||||
const fragment = FragmentById(fragmentId);
|
||||
if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.canPlace", `Invalid fragment id: ${fragmentId}`);
|
||||
if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.canPlaceFragment", `Invalid fragment id: ${fragmentId}`);
|
||||
const can = staneksGift.canPlace(rootX, rootY, rotation, fragment);
|
||||
return can;
|
||||
},
|
||||
place: function (_rootX: unknown, _rootY: unknown, _rotation: unknown, _fragmentId: unknown): boolean {
|
||||
const rootX = helper.number("stanek.place", "rootX", _rootX);
|
||||
const rootY = helper.number("stanek.place", "rootY", _rootY);
|
||||
const rotation = helper.number("stanek.place", "rotation", _rotation);
|
||||
const fragmentId = helper.number("stanek.place", "fragmentId", _fragmentId);
|
||||
helper.updateDynamicRam("place", getRamCost(player, "stanek", "place"));
|
||||
checkStanekAPIAccess("place");
|
||||
placeFragment: function (_rootX: unknown, _rootY: unknown, _rotation: unknown, _fragmentId: unknown): boolean {
|
||||
const rootX = helper.number("stanek.placeFragment", "rootX", _rootX);
|
||||
const rootY = helper.number("stanek.placeFragment", "rootY", _rootY);
|
||||
const rotation = helper.number("stanek.placeFragment", "rotation", _rotation);
|
||||
const fragmentId = helper.number("stanek.placeFragment", "fragmentId", _fragmentId);
|
||||
helper.updateDynamicRam("placeFragment", getRamCost(player, "stanek", "placeFragment"));
|
||||
checkStanekAPIAccess("placeFragment");
|
||||
const fragment = FragmentById(fragmentId);
|
||||
if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.place", `Invalid fragment id: ${fragmentId}`);
|
||||
if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.placeFragment", `Invalid fragment id: ${fragmentId}`);
|
||||
return staneksGift.place(rootX, rootY, rotation, fragment);
|
||||
},
|
||||
get: function (_rootX: unknown, _rootY: unknown): IActiveFragment | undefined {
|
||||
const rootX = helper.number("stanek.get", "rootX", _rootX);
|
||||
const rootY = helper.number("stanek.get", "rootY", _rootY);
|
||||
helper.updateDynamicRam("get", getRamCost(player, "stanek", "get"));
|
||||
checkStanekAPIAccess("get");
|
||||
getFragment: function (_rootX: unknown, _rootY: unknown): IActiveFragment | undefined {
|
||||
const rootX = helper.number("stanek.getFragment", "rootX", _rootX);
|
||||
const rootY = helper.number("stanek.getFragment", "rootY", _rootY);
|
||||
helper.updateDynamicRam("getFragment", getRamCost(player, "stanek", "getFragment"));
|
||||
checkStanekAPIAccess("getFragment");
|
||||
const fragment = staneksGift.findFragment(rootX, rootY);
|
||||
if (fragment !== undefined) return fragment.copy();
|
||||
return undefined;
|
||||
},
|
||||
remove: function (_rootX: unknown, _rootY: unknown): boolean {
|
||||
const rootX = helper.number("stanek.remove", "rootX", _rootX);
|
||||
const rootY = helper.number("stanek.remove", "rootY", _rootY);
|
||||
helper.updateDynamicRam("remove", getRamCost(player, "stanek", "remove"));
|
||||
checkStanekAPIAccess("remove");
|
||||
removeFragment: function (_rootX: unknown, _rootY: unknown): boolean {
|
||||
const rootX = helper.number("stanek.removeFragment", "rootX", _rootX);
|
||||
const rootY = helper.number("stanek.removeFragment", "rootY", _rootY);
|
||||
helper.updateDynamicRam("removeFragment", getRamCost(player, "stanek", "removeFragment"));
|
||||
checkStanekAPIAccess("removeFragment");
|
||||
return staneksGift.delete(rootX, rootY);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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 GraftableAugmentation {
|
||||
// The augmentation that this craftable corresponds to
|
||||
augmentation: Augmentation;
|
||||
|
||||
constructor(augmentation: Augmentation) {
|
||||
this.augmentation = augmentation;
|
||||
}
|
||||
|
||||
get cost(): number {
|
||||
return this.augmentation.startingCost * CONSTANTS.AugmentationGraftingCostMult;
|
||||
}
|
||||
|
||||
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.AugmentationGraftingTimeBase * mult + CONSTANTS.MillisecondsPerHalfHour;
|
||||
}
|
||||
}
|
||||
@@ -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 { GraftableAugmentation } from "../GraftableAugmentation";
|
||||
|
||||
const GraftableAugmentations: IMap<GraftableAugmentation> = {};
|
||||
|
||||
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 graftableAug = new GraftableAugmentation(aug);
|
||||
GraftableAugmentations[name] = graftableAug;
|
||||
}
|
||||
|
||||
const [selectedAug, setSelectedAug] = useState(getAvailableAugs(player)[0]);
|
||||
const [graftOpen, setGraftOpen] = 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">Graft 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={() => setGraftOpen(true)}
|
||||
sx={{ width: "100%" }}
|
||||
disabled={player.money < GraftableAugmentations[selectedAug].cost}
|
||||
>
|
||||
Graft Augmentation (
|
||||
<Typography>
|
||||
<Money money={GraftableAugmentations[selectedAug].cost} player={player} />
|
||||
</Typography>
|
||||
)
|
||||
</Button>
|
||||
<ConfirmationModal
|
||||
open={graftOpen}
|
||||
onClose={() => setGraftOpen(false)}
|
||||
onConfirm={() => {
|
||||
const graftableAug = GraftableAugmentations[selectedAug];
|
||||
player.loseMoney(graftableAug.cost, "augmentations");
|
||||
player.startGraftAugmentationWork(selectedAug, graftableAug.time);
|
||||
player.startFocusing();
|
||||
router.toWork();
|
||||
}}
|
||||
confirmationText={
|
||||
<>
|
||||
Cancelling grafting will <b>not</b> save grafting 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 Graft:</b>{" "}
|
||||
{convertTimeMsToTimeElapsedString(
|
||||
GraftableAugmentations[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 Virus</Typography>
|
||||
|
||||
<Paper sx={{ my: 1, p: 1, width: "fit-content" }}>
|
||||
<Typography>
|
||||
<b>Entropy strength:</b> {player.entropy}
|
||||
<br />
|
||||
<b>All multipliers decreased by:</b>{" "}
|
||||
{formatNumber((1 - CONSTANTS.EntropyEffect ** player.entropy) * 100, 3)}% (multiplicative)
|
||||
</Typography>
|
||||
</Paper>
|
||||
|
||||
<Typography>
|
||||
When installed on an unconscious individual, Augmentations are scanned by the body on awakening, eliminating
|
||||
hidden malware. However, grafted Augmentations do not provide this security measure.
|
||||
<br />
|
||||
<br />
|
||||
Individuals who tested Augmentation grafting have reported symptoms of an unknown virus, which they've dubbed
|
||||
"Entropy". This virus seems to grow more potent with each grafted Augmentation...
|
||||
</Typography>
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
@@ -3,7 +3,6 @@
|
||||
* Used because at the time of implementation, the PlayerObject
|
||||
* cant be converted to TypeScript.
|
||||
*/
|
||||
import { Resleeve } from "./Resleeving/Resleeve";
|
||||
import { Sleeve } from "./Sleeve/Sleeve";
|
||||
|
||||
import { IMap } from "../types";
|
||||
@@ -65,7 +64,6 @@ export interface IPlayer {
|
||||
playtimeSinceLastBitnode: number;
|
||||
purchasedServers: any[];
|
||||
queuedAugmentations: IPlayerOwnedAugmentation[];
|
||||
resleeves: Resleeve[];
|
||||
scriptProdSinceLastAug: number;
|
||||
sleeves: Sleeve[];
|
||||
sleevesFromCovenant: number;
|
||||
@@ -130,6 +128,8 @@ export interface IPlayer {
|
||||
factionWorkType: string;
|
||||
createProgramName: string;
|
||||
timeWorkedCreateProgram: number;
|
||||
graftAugmentationName: string;
|
||||
timeWorkedGraftAugmentation: number;
|
||||
crimeType: string;
|
||||
committingCrimeThruSingFn: boolean;
|
||||
singFnCrimeWorkerScript: WorkerScript | null;
|
||||
@@ -160,6 +160,8 @@ export interface IPlayer {
|
||||
workChaExpGainRate: number;
|
||||
workMoneyLossRate: number;
|
||||
|
||||
entropy: number;
|
||||
|
||||
// Methods
|
||||
work(numCycles: number): boolean;
|
||||
workPartTime(numCycles: number): boolean;
|
||||
@@ -181,7 +183,7 @@ export interface IPlayer {
|
||||
canAccessBladeburner(): boolean;
|
||||
canAccessCorporation(): boolean;
|
||||
canAccessGang(): boolean;
|
||||
canAccessResleeving(): boolean;
|
||||
canAccessGrafting(): boolean;
|
||||
canAfford(cost: number): boolean;
|
||||
gainHackingExp(exp: number): void;
|
||||
gainStrengthExp(exp: number): void;
|
||||
@@ -286,4 +288,8 @@ export interface IPlayer {
|
||||
setMult(name: string, mult: number): void;
|
||||
canAccessCotMG(): boolean;
|
||||
sourceFileLvl(n: number): number;
|
||||
startGraftAugmentationWork(augmentationName: string, time: number): void;
|
||||
graftAugmentationWork(numCycles: number): boolean;
|
||||
finishGraftAugmentationWork(cancelled: boolean): string;
|
||||
applyEntropy(stacks?: number): void;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import * as generalMethods from "./PlayerObjectGeneralMethods";
|
||||
import * as serverMethods from "./PlayerObjectServerMethods";
|
||||
|
||||
import { IMap } from "../../types";
|
||||
import { Resleeve } from "../Resleeving/Resleeve";
|
||||
import { Sleeve } from "../Sleeve/Sleeve";
|
||||
import { IPlayerOwnedSourceFile } from "../../SourceFile/PlayerOwnedSourceFile";
|
||||
import { Exploit } from "../../Exploits/Exploit";
|
||||
@@ -72,7 +71,6 @@ export class PlayerObject implements IPlayer {
|
||||
playtimeSinceLastBitnode: number;
|
||||
purchasedServers: any[];
|
||||
queuedAugmentations: IPlayerOwnedAugmentation[];
|
||||
resleeves: Resleeve[];
|
||||
scriptProdSinceLastAug: number;
|
||||
sleeves: Sleeve[];
|
||||
sleevesFromCovenant: number;
|
||||
@@ -139,6 +137,8 @@ export class PlayerObject implements IPlayer {
|
||||
factionWorkType: string;
|
||||
createProgramName: string;
|
||||
timeWorkedCreateProgram: number;
|
||||
graftAugmentationName: string;
|
||||
timeWorkedGraftAugmentation: number;
|
||||
crimeType: string;
|
||||
committingCrimeThruSingFn: boolean;
|
||||
singFnCrimeWorkerScript: WorkerScript | null;
|
||||
@@ -169,6 +169,8 @@ export class PlayerObject implements IPlayer {
|
||||
workChaExpGainRate: number;
|
||||
workMoneyLossRate: number;
|
||||
|
||||
entropy: number;
|
||||
|
||||
// Methods
|
||||
work: (numCycles: number) => boolean;
|
||||
workPartTime: (numCycles: number) => boolean;
|
||||
@@ -190,7 +192,7 @@ export class PlayerObject implements IPlayer {
|
||||
canAccessBladeburner: () => boolean;
|
||||
canAccessCorporation: () => boolean;
|
||||
canAccessGang: () => boolean;
|
||||
canAccessResleeving: () => boolean;
|
||||
canAccessGrafting: () => boolean;
|
||||
canAfford: (cost: number) => boolean;
|
||||
gainHackingExp: (exp: number) => void;
|
||||
gainStrengthExp: (exp: number) => void;
|
||||
@@ -296,6 +298,10 @@ export class PlayerObject implements IPlayer {
|
||||
setMult: (name: string, mult: number) => void;
|
||||
canAccessCotMG: () => boolean;
|
||||
sourceFileLvl: (n: number) => number;
|
||||
startGraftAugmentationWork: (augmentationName: string, time: number) => void;
|
||||
graftAugmentationWork: (numCycles: number) => boolean;
|
||||
finishGraftAugmentationWork: (cancelled: boolean) => string;
|
||||
applyEntropy: (stacks?: number) => void;
|
||||
|
||||
constructor() {
|
||||
//Skills and stats
|
||||
@@ -419,6 +425,9 @@ export class PlayerObject implements IPlayer {
|
||||
this.createProgramName = "";
|
||||
this.createProgramReqLvl = 0;
|
||||
|
||||
this.graftAugmentationName = "";
|
||||
this.timeWorkedGraftAugmentation = 0;
|
||||
|
||||
this.className = "";
|
||||
|
||||
this.crimeType = "";
|
||||
@@ -457,11 +466,12 @@ export class PlayerObject implements IPlayer {
|
||||
|
||||
// Sleeves & Re-sleeving
|
||||
this.sleeves = [];
|
||||
this.resleeves = [];
|
||||
this.sleevesFromCovenant = 0; // # of Duplicate sleeves purchased from the covenan;
|
||||
//bitnode
|
||||
this.bitNodeN = 1;
|
||||
|
||||
this.entropy = 0;
|
||||
|
||||
//Used to store the last update time.
|
||||
this.lastUpdate = 0;
|
||||
this.lastSave = 0;
|
||||
@@ -483,11 +493,11 @@ export class PlayerObject implements IPlayer {
|
||||
// Let's get a hash of some semi-random stuff so we have something unique.
|
||||
this.identifier = cyrb53(
|
||||
"I-" +
|
||||
new Date().getTime() +
|
||||
navigator.userAgent +
|
||||
window.innerWidth +
|
||||
window.innerHeight +
|
||||
getRandomInt(100, 999),
|
||||
new Date().getTime() +
|
||||
navigator.userAgent +
|
||||
window.innerWidth +
|
||||
window.innerHeight +
|
||||
getRandomInt(100, 999),
|
||||
);
|
||||
|
||||
this.init = generalMethods.init;
|
||||
@@ -541,6 +551,9 @@ export class PlayerObject implements IPlayer {
|
||||
this.startCreateProgramWork = generalMethods.startCreateProgramWork;
|
||||
this.createProgramWork = generalMethods.createProgramWork;
|
||||
this.finishCreateProgramWork = generalMethods.finishCreateProgramWork;
|
||||
this.startGraftAugmentationWork = generalMethods.startGraftAugmentationWork;
|
||||
this.graftAugmentationWork = generalMethods.craftAugmentationWork;
|
||||
this.finishGraftAugmentationWork = generalMethods.finishGraftAugmentationWork;
|
||||
this.startClass = generalMethods.startClass;
|
||||
this.takeClass = generalMethods.takeClass;
|
||||
this.finishClass = generalMethods.finishClass;
|
||||
@@ -577,7 +590,7 @@ export class PlayerObject implements IPlayer {
|
||||
this.gainCodingContractReward = generalMethods.gainCodingContractReward;
|
||||
this.travel = generalMethods.travel;
|
||||
this.gotoLocation = generalMethods.gotoLocation;
|
||||
this.canAccessResleeving = generalMethods.canAccessResleeving;
|
||||
this.canAccessGrafting = generalMethods.canAccessGrafting;
|
||||
this.giveExploit = generalMethods.giveExploit;
|
||||
this.giveAchievement = generalMethods.giveAchievement;
|
||||
this.getIntelligenceBonus = generalMethods.getIntelligenceBonus;
|
||||
@@ -611,6 +624,8 @@ export class PlayerObject implements IPlayer {
|
||||
|
||||
this.canAccessCotMG = generalMethods.canAccessCotMG;
|
||||
this.sourceFileLvl = generalMethods.sourceFileLvl;
|
||||
|
||||
this.applyEntropy = augmentationMethods.applyEntropy;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,6 +5,8 @@ import { IPlayer } from "../IPlayer";
|
||||
|
||||
import { Augmentation } from "../../Augmentation/Augmentation";
|
||||
|
||||
import { calculateEntropy } from "../Grafting/EntropyAccumulation";
|
||||
|
||||
export function hasAugmentation(this: IPlayer, aug: string | Augmentation, installed = false): boolean {
|
||||
const augName: string = aug instanceof Augmentation ? aug.name : aug;
|
||||
|
||||
@@ -24,3 +26,14 @@ export function hasAugmentation(this: IPlayer, aug: string | Augmentation, insta
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function applyEntropy(this: IPlayer, stacks = 1): void {
|
||||
// Re-apply all multipliers
|
||||
this.reapplyAllAugmentations();
|
||||
this.reapplyAllSourceFiles();
|
||||
|
||||
const newMultipliers = calculateEntropy(this, stacks);
|
||||
for (const [mult, val] of Object.entries(newMultipliers)) {
|
||||
this.setMult(mult, val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,8 +121,6 @@ export function prestigeAugmentation(this: PlayerObject): void {
|
||||
|
||||
this.queuedAugmentations = [];
|
||||
|
||||
this.resleeves = [];
|
||||
|
||||
const numSleeves = Math.min(3, SourceFileFlags[10] + (this.bitNodeN === 10 ? 1 : 0)) + this.sleevesFromCovenant;
|
||||
if (this.sleeves.length > numSleeves) this.sleeves.length = numSleeves;
|
||||
for (let i = this.sleeves.length; i < numSleeves; i++) {
|
||||
@@ -182,6 +180,7 @@ export function prestigeAugmentation(this: PlayerObject): void {
|
||||
}
|
||||
|
||||
export function prestigeSourceFile(this: IPlayer): void {
|
||||
this.entropy = 0;
|
||||
this.prestigeAugmentation();
|
||||
this.karma = 0;
|
||||
// Duplicate sleeves are reset to level 1 every Bit Node (but the number of sleeves you have persists)
|
||||
@@ -529,10 +528,12 @@ export function resetWorkStatus(this: IPlayer, generalType?: string, group?: str
|
||||
|
||||
this.timeWorked = 0;
|
||||
this.timeWorkedCreateProgram = 0;
|
||||
this.timeWorkedGraftAugmentation = 0;
|
||||
|
||||
this.currentWorkFactionName = "";
|
||||
this.currentWorkFactionDescription = "";
|
||||
this.createProgramName = "";
|
||||
this.graftAugmentationName = "";
|
||||
this.className = "";
|
||||
this.workType = "";
|
||||
}
|
||||
@@ -609,6 +610,10 @@ export function process(this: IPlayer, router: IRouter, numCycles = 1): void {
|
||||
if (this.workPartTime(numCycles)) {
|
||||
router.toCity();
|
||||
}
|
||||
} else if (this.workType === CONSTANTS.WorkTypeGraftAugmentation) {
|
||||
if (this.graftAugmentationWork(numCycles)) {
|
||||
router.toGrafting();
|
||||
}
|
||||
} else if (this.work(numCycles)) {
|
||||
router.toCity();
|
||||
}
|
||||
@@ -1329,6 +1334,59 @@ export function finishCreateProgramWork(this: IPlayer, cancelled: boolean): stri
|
||||
this.resetWorkStatus();
|
||||
return "You've finished creating " + programName + "! The new program can be found on your home computer.";
|
||||
}
|
||||
|
||||
export function startGraftAugmentationWork(this: IPlayer, augmentationName: string, time: number): void {
|
||||
this.resetWorkStatus();
|
||||
this.isWorking = true;
|
||||
this.workType = CONSTANTS.WorkTypeGraftAugmentation;
|
||||
|
||||
this.timeNeededToCompleteWork = time;
|
||||
this.graftAugmentationName = 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.timeWorkedGraftAugmentation += CONSTANTS._idleSpeed * numCycles * skillMult;
|
||||
|
||||
if (this.timeWorkedGraftAugmentation >= this.timeNeededToCompleteWork) {
|
||||
this.finishGraftAugmentationWork(false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function finishGraftAugmentationWork(this: IPlayer, cancelled: boolean): string {
|
||||
const augName = this.graftAugmentationName;
|
||||
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.entropy += 1;
|
||||
this.applyEntropy(this.entropy);
|
||||
} else {
|
||||
dialogBoxCreate(`You cancelled the crafting of ${augName}.<br>Your money was not returned to you.`);
|
||||
}
|
||||
|
||||
// Intelligence gain
|
||||
if (!cancelled) {
|
||||
this.gainIntelligenceExp((CONSTANTS.IntelligenceGraftBaseExpGain * this.timeWorked) / 10000);
|
||||
}
|
||||
|
||||
this.isWorking = false;
|
||||
this.resetWorkStatus();
|
||||
return `Grafting of ${augName} has ended.`;
|
||||
}
|
||||
|
||||
/* Studying/Taking Classes */
|
||||
export function startClass(this: IPlayer, costMult: number, expMult: number, className: string): void {
|
||||
this.resetWorkStatus();
|
||||
@@ -2640,7 +2698,7 @@ export function gotoLocation(this: IPlayer, to: LocationName): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
export function canAccessResleeving(this: IPlayer): boolean {
|
||||
export function canAccessGrafting(this: IPlayer): boolean {
|
||||
return this.bitNodeN === 10 || SourceFileFlags[10] > 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
Implements the Re-sleeving feature, which allows players to purchase a new body
|
||||
that comes with pre-existing Augmentations and experience. Note that purchasing
|
||||
a new body causes you to lose all of your old Augmentations and experience
|
||||
|
||||
This feature is introduced in BitNode-10, and destroying BitNode-10 allows
|
||||
the user to use it in other BitNodes (provided that they purchase the required
|
||||
cortical stack Augmentation)
|
||||
|
||||
While they are based on the same concept, this feature is different than the
|
||||
"Duplicate Sleeve" mechanic (which is referred to as just "Sleeve" in the source code).
|
||||
@@ -1,63 +0,0 @@
|
||||
/**
|
||||
* Implements the Resleeve class, which defines a new body
|
||||
* that the player can "re-sleeve" into.
|
||||
*/
|
||||
import { Person } from "../Person";
|
||||
|
||||
import { Augmentation } from "../../Augmentation/Augmentation";
|
||||
import { Augmentations } from "../../Augmentation/Augmentations";
|
||||
|
||||
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
|
||||
|
||||
export class Resleeve extends Person {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
getCost(): number {
|
||||
// Each experience point adds this to the cost
|
||||
const CostPerExp = 25e3;
|
||||
|
||||
// Final cost is multiplied by this constant ^ # Augs
|
||||
const NumAugsExponent = 1.2;
|
||||
|
||||
// Get total exp in this re-sleeve
|
||||
const totalExp: number =
|
||||
this.hacking_exp +
|
||||
this.strength_exp +
|
||||
this.defense_exp +
|
||||
this.dexterity_exp +
|
||||
this.agility_exp +
|
||||
this.charisma_exp;
|
||||
|
||||
// Get total base Augmentation cost for this re-sleeve
|
||||
let totalAugmentationCost = 0;
|
||||
for (let i = 0; i < this.augmentations.length; ++i) {
|
||||
const aug: Augmentation | null = Augmentations[this.augmentations[i].name];
|
||||
if (aug == null) {
|
||||
console.error(`Could not find Augmentation ${this.augmentations[i].name}`);
|
||||
continue;
|
||||
}
|
||||
totalAugmentationCost += aug.startingCost;
|
||||
}
|
||||
|
||||
return totalExp * CostPerExp + totalAugmentationCost * Math.pow(NumAugsExponent, this.augmentations.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the current object to a JSON save state.
|
||||
*/
|
||||
toJSON(): any {
|
||||
return Generic_toJSON("Resleeve", this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiatizes a Resleeve object from a JSON save state.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
static fromJSON(value: any): Resleeve {
|
||||
return Generic_fromJSON(Resleeve, value.data);
|
||||
}
|
||||
}
|
||||
|
||||
Reviver.constructors.Resleeve = Resleeve;
|
||||
@@ -1,133 +0,0 @@
|
||||
/**
|
||||
* Implements the Re-sleeving mechanic for BitNode-10.
|
||||
* This allows the player to purchase and "use" new sleeves at VitaLife.
|
||||
* These new sleeves come with different starting experience and Augmentations
|
||||
* The cost of these new sleeves scales based on the exp and Augs.
|
||||
*
|
||||
* Note that this is different from the "Sleeve mechanic". The "Sleeve" mechanic
|
||||
* provides new sleeves, essentially clones. This Re-sleeving mechanic lets
|
||||
* the player purchase a new body with pre-existing Augmentations and experience
|
||||
*
|
||||
* As of right now, this feature is only available in BitNode 10
|
||||
*/
|
||||
import { Resleeve } from "./Resleeve";
|
||||
import { IPlayer } from "../IPlayer";
|
||||
|
||||
import { Augmentation } from "../../Augmentation/Augmentation";
|
||||
import { Augmentations } from "../../Augmentation/Augmentations";
|
||||
import { IPlayerOwnedAugmentation, PlayerOwnedAugmentation } from "../../Augmentation/PlayerOwnedAugmentation";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
|
||||
import { getRandomInt } from "../../utils/helpers/getRandomInt";
|
||||
|
||||
// Executes the actual re-sleeve when one is purchased
|
||||
export function purchaseResleeve(r: Resleeve, p: IPlayer): boolean {
|
||||
const cost: number = r.getCost();
|
||||
if (!p.canAfford(cost)) {
|
||||
return false;
|
||||
}
|
||||
p.loseMoney(cost, "other");
|
||||
|
||||
// Set the player's exp
|
||||
p.hacking_exp = r.hacking_exp;
|
||||
p.strength_exp = r.strength_exp;
|
||||
p.defense_exp = r.defense_exp;
|
||||
p.dexterity_exp = r.dexterity_exp;
|
||||
p.agility_exp = r.agility_exp;
|
||||
p.charisma_exp = r.charisma_exp;
|
||||
|
||||
// Reset Augmentation "owned" data
|
||||
for (const augKey of Object.keys(Augmentations)) {
|
||||
Augmentations[augKey].owned = false;
|
||||
}
|
||||
|
||||
// Clear all of the player's augmentations, except the NeuroFlux Governor
|
||||
// which is kept
|
||||
for (let i = p.augmentations.length - 1; i >= 0; --i) {
|
||||
if (p.augmentations[i].name !== AugmentationNames.NeuroFluxGovernor) {
|
||||
p.augmentations.splice(i, 1);
|
||||
} else {
|
||||
// NeuroFlux Governor
|
||||
Augmentations[AugmentationNames.NeuroFluxGovernor].owned = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < r.augmentations.length; ++i) {
|
||||
p.augmentations.push(new PlayerOwnedAugmentation(r.augmentations[i].name));
|
||||
Augmentations[r.augmentations[i].name].owned = true;
|
||||
}
|
||||
|
||||
// The player's purchased Augmentations should remain the same, but any purchased
|
||||
// Augmentations that are given by the resleeve should be removed so there are no duplicates
|
||||
for (let i = p.queuedAugmentations.length - 1; i >= 0; --i) {
|
||||
const name: string = p.queuedAugmentations[i].name;
|
||||
|
||||
if (
|
||||
p.augmentations.filter((e: IPlayerOwnedAugmentation) => {
|
||||
return e.name !== AugmentationNames.NeuroFluxGovernor && e.name === name;
|
||||
}).length >= 1
|
||||
) {
|
||||
p.queuedAugmentations.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
p.reapplyAllAugmentations(true);
|
||||
p.reapplyAllSourceFiles(); //Multipliers get reset, so have to re-process source files too
|
||||
return true;
|
||||
}
|
||||
|
||||
// Creates all of the Re-sleeves that will be available for purchase at VitaLife
|
||||
export function generateResleeves(): Resleeve[] {
|
||||
const NumResleeves = 40; // Total number of Resleeves to generate
|
||||
|
||||
const ret: Resleeve[] = [];
|
||||
for (let i = 0; i < NumResleeves; ++i) {
|
||||
// i will be a number indicating how "powerful" the Re-sleeve should be
|
||||
const r: Resleeve = new Resleeve();
|
||||
|
||||
// Generate experience
|
||||
const expMult: number = 5 * i + 1;
|
||||
r.hacking_exp = expMult * getRandomInt(1000, 5000);
|
||||
r.strength_exp = expMult * getRandomInt(1000, 5000);
|
||||
r.defense_exp = expMult * getRandomInt(1000, 5000);
|
||||
r.dexterity_exp = expMult * getRandomInt(1000, 5000);
|
||||
r.agility_exp = expMult * getRandomInt(1000, 5000);
|
||||
r.charisma_exp = expMult * getRandomInt(1000, 5000);
|
||||
|
||||
// Generate Augs
|
||||
// Augmentation prequisites will be ignored for this
|
||||
const baseNumAugs: number = Math.max(2, Math.ceil((i + 3) / 2));
|
||||
const numAugs: number = getRandomInt(baseNumAugs, baseNumAugs + 2);
|
||||
const augKeys: string[] = Object.keys(Augmentations);
|
||||
for (let a = 0; a < numAugs; ++a) {
|
||||
// Get a random aug
|
||||
const randIndex: number = getRandomInt(0, augKeys.length - 1);
|
||||
const randKey: string = augKeys[randIndex];
|
||||
|
||||
// Forbidden augmentations
|
||||
const forbidden = [
|
||||
AugmentationNames.TheRedPill,
|
||||
AugmentationNames.NeuroFluxGovernor,
|
||||
AugmentationNames.StaneksGift1,
|
||||
AugmentationNames.StaneksGift2,
|
||||
AugmentationNames.StaneksGift3,
|
||||
];
|
||||
if (forbidden.includes(randKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const randAug: Augmentation | null = Augmentations[randKey];
|
||||
if (randAug === null) throw new Error(`null augmentation: ${randKey}`);
|
||||
r.augmentations.push({ name: randAug.name, level: 1 });
|
||||
r.applyAugmentation(Augmentations[randKey]);
|
||||
r.updateStatLevels();
|
||||
|
||||
// Remove Augmentation so that there are no duplicates
|
||||
augKeys.splice(randIndex, 1);
|
||||
}
|
||||
|
||||
ret.push(r);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import { IPlayer } from "../../IPlayer";
|
||||
import { Resleeve } from "../Resleeve";
|
||||
import { Augmentations } from "../../../Augmentation/Augmentations";
|
||||
import { purchaseResleeve } from "../Resleeving";
|
||||
import { Money } from "../../../ui/React/Money";
|
||||
|
||||
import { numeralWrapper } from "../../../ui/numeralFormat";
|
||||
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
|
||||
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import Button from "@mui/material/Button";
|
||||
import Select, { SelectChangeEvent } from "@mui/material/Select";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Grid from "@mui/material/Grid";
|
||||
|
||||
interface IProps {
|
||||
resleeve: Resleeve;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function ResleeveElem(props: IProps): React.ReactElement {
|
||||
const [aug, setAug] = useState(props.resleeve.augmentations[0].name);
|
||||
|
||||
function openStats(): void {
|
||||
dialogBoxCreate(
|
||||
<>
|
||||
<Typography variant="h5" color="primary">
|
||||
Total Multipliers:
|
||||
</Typography>
|
||||
<Typography>
|
||||
Hacking Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_mult)}
|
||||
<br />
|
||||
Hacking Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_exp_mult)}
|
||||
<br />
|
||||
Strength Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.strength_mult)}
|
||||
<br />
|
||||
Strength Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.strength_exp_mult)}
|
||||
<br />
|
||||
Defense Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.defense_mult)}
|
||||
<br />
|
||||
Defense Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.defense_exp_mult)}
|
||||
<br />
|
||||
Dexterity Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.dexterity_mult)}
|
||||
<br />
|
||||
Dexterity Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.dexterity_exp_mult)}
|
||||
<br />
|
||||
Agility Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.agility_mult)}
|
||||
<br />
|
||||
Agility Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.agility_exp_mult)}
|
||||
<br />
|
||||
Charisma Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.charisma_mult)}
|
||||
<br />
|
||||
Charisma Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.charisma_exp_mult)}
|
||||
<br />
|
||||
Hacking Chance multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_chance_mult)}
|
||||
<br />
|
||||
Hacking Speed multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_speed_mult)}
|
||||
<br />
|
||||
Hacking Money multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_money_mult)}
|
||||
<br />
|
||||
Hacking Growth multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_grow_mult)}
|
||||
<br />
|
||||
Salary multiplier: {numeralWrapper.formatPercentage(props.resleeve.work_money_mult)}
|
||||
<br />
|
||||
Company Reputation Gain multiplier: {numeralWrapper.formatPercentage(props.resleeve.company_rep_mult)}
|
||||
<br />
|
||||
Faction Reputation Gain multiplier: {numeralWrapper.formatPercentage(props.resleeve.faction_rep_mult)}
|
||||
<br />
|
||||
Crime Money multiplier: {numeralWrapper.formatPercentage(props.resleeve.crime_money_mult)}
|
||||
<br />
|
||||
Crime Success multiplier: {numeralWrapper.formatPercentage(props.resleeve.crime_success_mult)}
|
||||
<br />
|
||||
Hacknet Income multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacknet_node_money_mult)}
|
||||
<br />
|
||||
Hacknet Purchase Cost multiplier:
|
||||
{numeralWrapper.formatPercentage(props.resleeve.hacknet_node_purchase_cost_mult)}
|
||||
<br />
|
||||
Hacknet Level Upgrade Cost multiplier:
|
||||
{numeralWrapper.formatPercentage(props.resleeve.hacknet_node_level_cost_mult)}
|
||||
<br />
|
||||
Hacknet Ram Upgrade Cost multiplier:
|
||||
{numeralWrapper.formatPercentage(props.resleeve.hacknet_node_ram_cost_mult)}
|
||||
<br />
|
||||
Hacknet Core Upgrade Cost multiplier:
|
||||
{numeralWrapper.formatPercentage(props.resleeve.hacknet_node_core_cost_mult)}
|
||||
<br />
|
||||
Bladeburner Max Stamina multiplier:
|
||||
{numeralWrapper.formatPercentage(props.resleeve.bladeburner_max_stamina_mult)}
|
||||
<br />
|
||||
Bladeburner Stamina Gain multiplier:
|
||||
{numeralWrapper.formatPercentage(props.resleeve.bladeburner_stamina_gain_mult)}
|
||||
<br />
|
||||
Bladeburner Field Analysis multiplier:
|
||||
{numeralWrapper.formatPercentage(props.resleeve.bladeburner_analysis_mult)}
|
||||
<br />
|
||||
Bladeburner Success Chance multiplier:
|
||||
{numeralWrapper.formatPercentage(props.resleeve.bladeburner_success_chance_mult)}
|
||||
</Typography>
|
||||
</>,
|
||||
);
|
||||
}
|
||||
|
||||
function onAugChange(event: SelectChangeEvent<string>): void {
|
||||
setAug(event.target.value);
|
||||
}
|
||||
|
||||
const currentAug = Augmentations[aug];
|
||||
const cost = props.resleeve.getCost();
|
||||
|
||||
function purchase(): void {
|
||||
if (!purchaseResleeve(props.resleeve, props.player)) return;
|
||||
dialogBoxCreate(
|
||||
<>
|
||||
You re-sleeved for <Money money={cost} />!
|
||||
</>,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper sx={{ my: 1 }}>
|
||||
<Grid container>
|
||||
<Grid item xs={3}>
|
||||
<Typography>
|
||||
Hacking: {numeralWrapper.formatSkill(props.resleeve.hacking)} (
|
||||
{numeralWrapper.formatExp(props.resleeve.hacking_exp)} exp)
|
||||
<br />
|
||||
Strength: {numeralWrapper.formatSkill(props.resleeve.strength)} (
|
||||
{numeralWrapper.formatExp(props.resleeve.strength_exp)} exp)
|
||||
<br />
|
||||
Defense: {numeralWrapper.formatSkill(props.resleeve.defense)} (
|
||||
{numeralWrapper.formatExp(props.resleeve.defense_exp)} exp)
|
||||
<br />
|
||||
Dexterity: {numeralWrapper.formatSkill(props.resleeve.dexterity)} (
|
||||
{numeralWrapper.formatExp(props.resleeve.dexterity_exp)} exp)
|
||||
<br />
|
||||
Agility: {numeralWrapper.formatSkill(props.resleeve.agility)} (
|
||||
{numeralWrapper.formatExp(props.resleeve.agility_exp)} exp)
|
||||
<br />
|
||||
Charisma: {numeralWrapper.formatSkill(props.resleeve.charisma)} (
|
||||
{numeralWrapper.formatExp(props.resleeve.charisma_exp)} exp)
|
||||
<br /># Augmentations: {props.resleeve.augmentations.length}
|
||||
</Typography>
|
||||
<Button onClick={openStats}>Multipliers</Button>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Select value={aug} onChange={onAugChange}>
|
||||
{props.resleeve.augmentations.map((aug) => (
|
||||
<MenuItem key={aug.name} value={aug.name}>
|
||||
{aug.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<Typography>{currentAug !== undefined && currentAug.info}</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Typography>
|
||||
It costs <Money money={cost} player={props.player} /> to purchase this Sleeve.
|
||||
</Typography>
|
||||
<Button onClick={purchase}>Purchase</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { generateResleeves } from "../Resleeving";
|
||||
import { Resleeve } from "../Resleeve";
|
||||
import { ResleeveElem } from "./ResleeveElem";
|
||||
import { use } from "../../../ui/Context";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Select, { SelectChangeEvent } from "@mui/material/Select";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Box from "@mui/material/Box";
|
||||
|
||||
const SortOption: {
|
||||
[key: string]: string | undefined;
|
||||
Cost: string;
|
||||
Hacking: string;
|
||||
Strength: string;
|
||||
Defense: string;
|
||||
Dexterity: string;
|
||||
Agility: string;
|
||||
Charisma: string;
|
||||
AverageCombatStats: string;
|
||||
AverageAllStats: string;
|
||||
TotalNumAugmentations: string;
|
||||
} = {
|
||||
Cost: "Cost",
|
||||
Hacking: "Hacking Level",
|
||||
Strength: "Strength Level",
|
||||
Defense: "Defense Level",
|
||||
Dexterity: "Dexterity Level",
|
||||
Agility: "Agility Level",
|
||||
Charisma: "Charisma Level",
|
||||
AverageCombatStats: "Average Combat Stats",
|
||||
AverageAllStats: "Average Stats",
|
||||
TotalNumAugmentations: "Number of Augmentations",
|
||||
};
|
||||
|
||||
// Helper function for averaging
|
||||
function getAverage(...values: number[]): number {
|
||||
let sum = 0;
|
||||
for (let i = 0; i < values.length; ++i) {
|
||||
sum += values[i];
|
||||
}
|
||||
|
||||
return sum / values.length;
|
||||
}
|
||||
|
||||
const SortFunctions: {
|
||||
[key: string]: ((a: Resleeve, b: Resleeve) => number) | undefined;
|
||||
Cost: (a: Resleeve, b: Resleeve) => number;
|
||||
Hacking: (a: Resleeve, b: Resleeve) => number;
|
||||
Strength: (a: Resleeve, b: Resleeve) => number;
|
||||
Defense: (a: Resleeve, b: Resleeve) => number;
|
||||
Dexterity: (a: Resleeve, b: Resleeve) => number;
|
||||
Agility: (a: Resleeve, b: Resleeve) => number;
|
||||
Charisma: (a: Resleeve, b: Resleeve) => number;
|
||||
AverageCombatStats: (a: Resleeve, b: Resleeve) => number;
|
||||
AverageAllStats: (a: Resleeve, b: Resleeve) => number;
|
||||
TotalNumAugmentations: (a: Resleeve, b: Resleeve) => number;
|
||||
} = {
|
||||
Cost: (a: Resleeve, b: Resleeve): number => a.getCost() - b.getCost(),
|
||||
Hacking: (a: Resleeve, b: Resleeve): number => a.hacking - b.hacking,
|
||||
Strength: (a: Resleeve, b: Resleeve): number => a.strength - b.strength,
|
||||
Defense: (a: Resleeve, b: Resleeve): number => a.defense - b.defense,
|
||||
Dexterity: (a: Resleeve, b: Resleeve): number => a.dexterity - b.dexterity,
|
||||
Agility: (a: Resleeve, b: Resleeve): number => a.agility - b.agility,
|
||||
Charisma: (a: Resleeve, b: Resleeve): number => a.charisma - b.charisma,
|
||||
AverageCombatStats: (a: Resleeve, b: Resleeve): number =>
|
||||
getAverage(a.strength, a.defense, a.dexterity, a.agility) -
|
||||
getAverage(b.strength, b.defense, b.dexterity, b.agility),
|
||||
AverageAllStats: (a: Resleeve, b: Resleeve): number =>
|
||||
getAverage(a.hacking, a.strength, a.defense, a.dexterity, a.agility, a.charisma) -
|
||||
getAverage(b.hacking, b.strength, b.defense, b.dexterity, b.agility, b.charisma),
|
||||
TotalNumAugmentations: (a: Resleeve, b: Resleeve): number => a.augmentations.length - b.augmentations.length,
|
||||
};
|
||||
|
||||
export function ResleeveRoot(): React.ReactElement {
|
||||
const player = use.Player();
|
||||
const [sort, setSort] = useState(SortOption.Cost);
|
||||
// Randomly create all Resleeves if they dont already exist
|
||||
if (player.resleeves.length === 0) {
|
||||
player.resleeves = generateResleeves();
|
||||
}
|
||||
|
||||
function onSortChange(event: SelectChangeEvent<string>): void {
|
||||
setSort(event.target.value);
|
||||
}
|
||||
|
||||
const sortFunction = SortFunctions[sort];
|
||||
if (sortFunction === undefined) throw new Error(`sort function '${sort}' is undefined`);
|
||||
player.resleeves.sort(sortFunction);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography>
|
||||
Re-sleeving is the process of digitizing and transferring your consciousness into a new human body, or 'sleeve'.
|
||||
Here at VitaLife, you can purchase new specially-engineered bodies for the re-sleeve process. Many of these
|
||||
bodies even come with genetic and cybernetic Augmentations!
|
||||
<br />
|
||||
<br />
|
||||
Re-sleeving will change your experience for every stat. It will also REMOVE all of your currently-installed
|
||||
Augmentations, and replace them with the ones provided by the purchased sleeve. However, Augmentations that you
|
||||
have purchased but not installed will NOT be removed. If you have purchased an Augmentation and then re-sleeve
|
||||
into a body which already has that Augmentation, it will be removed (since you cannot have duplicate
|
||||
Augmentations).
|
||||
<br />
|
||||
<br />
|
||||
NOTE: The stats and multipliers displayed on this page do NOT include your bonuses from Source-File.
|
||||
</Typography>
|
||||
<Box display="flex" alignItems="center">
|
||||
<Typography>Sort By: </Typography>
|
||||
<Select value={sort} onChange={onSortChange}>
|
||||
{Object.keys(SortOption).map((opt) => (
|
||||
<MenuItem key={opt} value={opt}>
|
||||
{SortOption[opt]}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</Box>
|
||||
{player.resleeves.map((resleeve, i) => (
|
||||
<ResleeveElem key={i} player={player} resleeve={resleeve} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -108,6 +108,9 @@ export function prestigeAugmentation(): void {
|
||||
// Messages
|
||||
initMessages();
|
||||
|
||||
// Apply entropy from grafting
|
||||
Player.applyEntropy(Player.entropy);
|
||||
|
||||
// Gang
|
||||
const gang = Player.gang;
|
||||
if (Player.inGang() && gang !== null) {
|
||||
|
||||
@@ -393,6 +393,11 @@ function evaluateVersionCompatibility(ver: string | number): void {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ver < 12) {
|
||||
if (anyPlayer.resleeves !== undefined) {
|
||||
delete anyPlayer.resleeves;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -225,6 +225,9 @@ async function parseOnlyRamCalculate(
|
||||
} else if (ref in workerScript.env.vars.ui) {
|
||||
func = workerScript.env.vars.ui[ref];
|
||||
refDetail = `ui.${ref}`;
|
||||
} else if (ref in workerScript.env.vars.grafting) {
|
||||
func = workerScript.env.vars.grafting[ref];
|
||||
refDetail = `grafting.${ref}`;
|
||||
} else {
|
||||
func = workerScript.env.vars[ref];
|
||||
refDetail = `${ref}`;
|
||||
|
||||
+54
-9
@@ -95,6 +95,7 @@ interface Player {
|
||||
tor: boolean;
|
||||
hasCorporation: boolean;
|
||||
inBladeburner: boolean;
|
||||
entropy: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1597,7 +1598,7 @@ export interface Singularity {
|
||||
* purchasing a TOR router using this function is the same as if you were to
|
||||
* manually purchase one.
|
||||
*
|
||||
* @returns True if actions is successful, false otherwise.
|
||||
* @returns True if actions is successful or you already own TOR router, false otherwise.
|
||||
*/
|
||||
purchaseTor(): boolean;
|
||||
|
||||
@@ -3719,6 +3720,43 @@ export interface Sleeve {
|
||||
purchaseSleeveAug(sleeveNumber: number, augName: string): boolean;
|
||||
}
|
||||
|
||||
export interface Grafting {
|
||||
/**
|
||||
* Retrieve the grafting 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 graft the named augmentation.
|
||||
* @throws Will error if an invalid Augmentation name is provided.
|
||||
*/
|
||||
getAugmentationGraftPrice(augName: string): number;
|
||||
|
||||
/**
|
||||
* Retrieves the time required to graft an aug.
|
||||
* @remarks
|
||||
* RAM cost: 3.75 GB
|
||||
*
|
||||
* @param augName - Name of the aug to check the grafting time of. Must be an exact match.
|
||||
* @returns The time required, in millis, to graft the named augmentation.
|
||||
* @throws Will error if an invalid Augmentation name is provided.
|
||||
*/
|
||||
getAugmentationGraftTime(augName: string): number;
|
||||
|
||||
/**
|
||||
* Begins grafting 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 grafting. Must be an exact match.
|
||||
* @param focus - Acquire player focus on this Augmentation grafting. Optional. Defaults to true.
|
||||
* @returns True if the aug successfully began grafting, false otherwise (e.g. not enough money, or
|
||||
* invalid Augmentation name provided).
|
||||
* @throws Will error if called while you are not in New Tokyo.
|
||||
*/
|
||||
graftAugmentation(augName: string, focus?: boolean): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skills formulas
|
||||
* @public
|
||||
@@ -4028,14 +4066,14 @@ interface Stanek {
|
||||
* RAM cost: 0.4 GB
|
||||
* @returns The width of the gift.
|
||||
*/
|
||||
width(): number;
|
||||
giftWidth(): number;
|
||||
/**
|
||||
* Stanek's Gift height.
|
||||
* @remarks
|
||||
* RAM cost: 0.4 GB
|
||||
* @returns The height of the gift.
|
||||
*/
|
||||
height(): number;
|
||||
giftHeight(): number;
|
||||
|
||||
/**
|
||||
* Charge a fragment, increasing its power.
|
||||
@@ -4045,7 +4083,7 @@ interface Stanek {
|
||||
* @param rootY - rootY Root Y against which to align the top left of the fragment.
|
||||
* @returns Promise that lasts until the charge action is over.
|
||||
*/
|
||||
charge(rootX: number, rootY: number): Promise<void>;
|
||||
chargeFragment(rootX: number, rootY: number): Promise<void>;
|
||||
|
||||
/**
|
||||
* List possible fragments.
|
||||
@@ -4070,7 +4108,7 @@ interface Stanek {
|
||||
* @remarks
|
||||
* RAM cost: 0 GB
|
||||
*/
|
||||
clear(): void;
|
||||
clearGift(): void;
|
||||
|
||||
/**
|
||||
* Check if fragment can be placed at specified location.
|
||||
@@ -4083,7 +4121,7 @@ interface Stanek {
|
||||
* @param fragmentId - fragmentId ID of the fragment to place.
|
||||
* @returns true if the fragment can be placed at that position. false otherwise.
|
||||
*/
|
||||
canPlace(rootX: number, rootY: number, rotation: number, fragmentId: number): boolean;
|
||||
canPlaceFragment(rootX: number, rootY: number, rotation: number, fragmentId: number): boolean;
|
||||
/**
|
||||
* Place fragment on Stanek's Gift.
|
||||
* @remarks
|
||||
@@ -4095,7 +4133,7 @@ interface Stanek {
|
||||
* @param fragmentId - ID of the fragment to place.
|
||||
* @returns true if the fragment can be placed at that position. false otherwise.
|
||||
*/
|
||||
place(rootX: number, rootY: number, rotation: number, fragmentId: number): boolean;
|
||||
placeFragment(rootX: number, rootY: number, rotation: number, fragmentId: number): boolean;
|
||||
/**
|
||||
* Get placed fragment at location.
|
||||
* @remarks
|
||||
@@ -4105,7 +4143,7 @@ interface Stanek {
|
||||
* @param rootY - Y against which to align the top left of the fragment.
|
||||
* @returns The fragment at [rootX, rootY], if any.
|
||||
*/
|
||||
get(rootX: number, rootY: number): ActiveFragment | undefined;
|
||||
getFragment(rootX: number, rootY: number): ActiveFragment | undefined;
|
||||
|
||||
/**
|
||||
* Remove fragment at location.
|
||||
@@ -4116,7 +4154,7 @@ interface Stanek {
|
||||
* @param rootY - Y against which to align the top left of the fragment.
|
||||
* @returns The fragment at [rootX, rootY], if any.
|
||||
*/
|
||||
remove(rootX: number, rootY: number): boolean;
|
||||
removeFragment(rootX: number, rootY: number): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4280,6 +4318,13 @@ export interface NS extends Singularity {
|
||||
*/
|
||||
readonly ui: UserInterface;
|
||||
|
||||
/**
|
||||
* Namespace for grafting functions.
|
||||
* @remarks
|
||||
* RAM cost: 0 GB
|
||||
*/
|
||||
readonly grafting: Grafting;
|
||||
|
||||
/**
|
||||
* Arguments passed into the script.
|
||||
*
|
||||
|
||||
@@ -118,6 +118,11 @@ interface IDefaultSettings {
|
||||
*/
|
||||
SuppressSavedGameToast: boolean;
|
||||
|
||||
/**
|
||||
* Whether the user should be displayed a toast warning when the autosave is disabled.
|
||||
*/
|
||||
SuppressAutosaveDisabledWarnings: boolean;
|
||||
|
||||
/*
|
||||
* Whether the game should skip saving the running scripts for late game
|
||||
*/
|
||||
@@ -197,6 +202,7 @@ export const defaultSettings: IDefaultSettings = {
|
||||
SuppressBladeburnerPopup: false,
|
||||
SuppressTIXPopup: false,
|
||||
SuppressSavedGameToast: false,
|
||||
SuppressAutosaveDisabledWarnings: false,
|
||||
UseIEC60027_2: false,
|
||||
ExcludeRunningScriptsFromSave: false,
|
||||
IsSidebarOpened: true,
|
||||
@@ -235,6 +241,7 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
|
||||
SuppressBladeburnerPopup: defaultSettings.SuppressBladeburnerPopup,
|
||||
SuppressTIXPopup: defaultSettings.SuppressTIXPopup,
|
||||
SuppressSavedGameToast: defaultSettings.SuppressSavedGameToast,
|
||||
SuppressAutosaveDisabledWarnings: defaultSettings.SuppressAutosaveDisabledWarnings,
|
||||
UseIEC60027_2: defaultSettings.UseIEC60027_2,
|
||||
ExcludeRunningScriptsFromSave: defaultSettings.ExcludeRunningScriptsFromSave,
|
||||
IsSidebarOpened: defaultSettings.IsSidebarOpened,
|
||||
|
||||
@@ -617,7 +617,7 @@ export function SidebarRoot(props: IProps): React.ReactElement {
|
||||
key={"City"}
|
||||
className={clsx({
|
||||
[classes.active]:
|
||||
props.page === Page.City || props.page === Page.Resleeves || props.page === Page.Location,
|
||||
props.page === Page.City || props.page === Page.Grafting || props.page === Page.Location,
|
||||
})}
|
||||
onClick={clickCity}
|
||||
>
|
||||
|
||||
@@ -167,8 +167,8 @@ SourceFiles["SourceFile10"] = new SourceFile(
|
||||
10,
|
||||
(
|
||||
<>
|
||||
This Source-File unlocks Sleeve technology in other BitNodes. Each level of this Source-File also grants you a
|
||||
Duplicate Sleeve
|
||||
This Source-File unlocks Sleeve technology, and the Grafting API in other BitNodes.
|
||||
Each level of this Source-File also grants you a Duplicate Sleeve
|
||||
</>
|
||||
),
|
||||
);
|
||||
|
||||
@@ -36,7 +36,7 @@ export function connect(
|
||||
terminal.connectToServer(player, hostname);
|
||||
return;
|
||||
}
|
||||
terminal.error(`Cannot directly connect to ${hostname}`);
|
||||
terminal.error(`Cannot directly connect to ${hostname}. Make sure the server is backdoored or adjacent to your current Server`);
|
||||
} else {
|
||||
terminal.error("Host not found");
|
||||
}
|
||||
|
||||
+41
-2
@@ -15,6 +15,7 @@ import { Factions, initFactions } from "./Faction/Factions";
|
||||
import { staneksGift } from "./CotMG/Helper";
|
||||
import { processPassiveFactionRepGain, inviteToFaction } from "./Faction/FactionHelpers";
|
||||
import { Router } from "./ui/GameRoot";
|
||||
import { Page } from "./ui/Router";
|
||||
import { SetupTextEditor } from "./ScriptEditor/ui/ScriptEditorRoot";
|
||||
|
||||
import {
|
||||
@@ -48,7 +49,8 @@ import { calculateAchievements } from "./Achievements/Achievements";
|
||||
|
||||
import React from "react";
|
||||
import { setupUncaughtPromiseHandler } from "./UncaughtPromiseHandler";
|
||||
import { Typography } from "@mui/material";
|
||||
import { Button, Typography } from "@mui/material";
|
||||
import { SnackbarEvents } from "./ui/React/Snackbar";
|
||||
|
||||
const Engine: {
|
||||
_lastUpdate: number;
|
||||
@@ -187,7 +189,8 @@ const Engine: {
|
||||
Settings.AutosaveInterval = 60;
|
||||
}
|
||||
if (Settings.AutosaveInterval === 0) {
|
||||
Engine.Counters.autoSaveCounter = Infinity;
|
||||
warnAutosaveDisabled();
|
||||
Engine.Counters.autoSaveCounter = 60 * 5; // Let's check back in a bit
|
||||
} else {
|
||||
Engine.Counters.autoSaveCounter = Settings.AutosaveInterval * 5;
|
||||
saveObject.saveGame(!Settings.SuppressSavedGameToast);
|
||||
@@ -260,6 +263,9 @@ const Engine: {
|
||||
initSymbolToStockMap();
|
||||
}
|
||||
|
||||
// Apply penalty for entropy accumulation
|
||||
Player.applyEntropy(Player.entropy);
|
||||
|
||||
// Calculate the number of cycles have elapsed while offline
|
||||
Engine._lastUpdate = new Date().getTime();
|
||||
const lastUpdate = Player.lastUpdate;
|
||||
@@ -299,6 +305,8 @@ const Engine: {
|
||||
Player.commitCrime(numCyclesOffline);
|
||||
} else if (Player.workType == CONSTANTS.WorkTypeCompanyPartTime) {
|
||||
Player.workPartTime(numCyclesOffline);
|
||||
} else if (Player.workType === CONSTANTS.WorkTypeGraftAugmentation) {
|
||||
Player.graftAugmentationWork(numCyclesOffline);
|
||||
} else {
|
||||
Player.work(numCyclesOffline);
|
||||
}
|
||||
@@ -459,4 +467,35 @@ const Engine: {
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows a toast warning that lets the player know that auto-saves are disabled, with an button to re-enable them.
|
||||
*/
|
||||
function warnAutosaveDisabled(): void {
|
||||
// If the player has suppressed those warnings let's just exit right away.
|
||||
if (Settings.SuppressAutosaveDisabledWarnings) return;
|
||||
|
||||
// We don't want this warning to show up on certain pages.
|
||||
// When in recovery or importing we want to keep autosave disabled.
|
||||
const ignoredPages = [Page.Recovery, Page.ImportSave];
|
||||
if (ignoredPages.includes(Router.page())) return;
|
||||
|
||||
const warningToast = (
|
||||
<>
|
||||
Auto-saves are <strong>disabled</strong>!
|
||||
<Button
|
||||
sx={{ ml: 1 }}
|
||||
color="warning"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
// We reset the value to a default
|
||||
Settings.AutosaveInterval = 60;
|
||||
}}
|
||||
>
|
||||
Enable
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
SnackbarEvents.emit(warningToast, "warning", 5000);
|
||||
}
|
||||
|
||||
export { Engine };
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
// These should really be imported with the module that is presenting that UI, but because they very much depend on the
|
||||
// cascade order, we'll pull them all in here.
|
||||
import "normalize.css";
|
||||
import "../css/styles.scss";
|
||||
import "../css/tooltips.scss";
|
||||
import "../css/buttons.scss";
|
||||
import "../css/mainmenu.scss";
|
||||
import "../css/characteroverview.scss";
|
||||
|
||||
import "../css/scripteditor.scss";
|
||||
import "../css/hacknetnodes.scss";
|
||||
import "../css/menupages.scss";
|
||||
import "../css/augmentations.scss";
|
||||
import "../css/redpill.scss";
|
||||
import "../css/stockmarket.scss";
|
||||
import "../css/workinprogress.scss";
|
||||
import "../css/popupboxes.scss";
|
||||
import "../css/gameoptions.scss";
|
||||
import "../css/interactivetutorial.scss";
|
||||
import "../css/loader.scss";
|
||||
import "../css/missions.scss";
|
||||
import "../css/companymanagement.scss";
|
||||
import "../css/bladeburner.scss";
|
||||
import "../css/gang.scss";
|
||||
import "../css/sleeves.scss";
|
||||
import "../css/resleeving.scss";
|
||||
import "../css/treant.css";
|
||||
import "../css/grid.min.css";
|
||||
import "../css/dev-menu.css";
|
||||
import "../css/casino.scss";
|
||||
import "../css/milestones.scss";
|
||||
import "../css/infiltration.scss";
|
||||
import "../css/staneksgift.scss";
|
||||
+5
-5
@@ -42,7 +42,7 @@ import { BladeburnerRoot } from "../Bladeburner/ui/BladeburnerRoot";
|
||||
import { GangRoot } from "../Gang/ui/GangRoot";
|
||||
import { CorporationRoot } from "../Corporation/ui/CorporationRoot";
|
||||
import { InfiltrationRoot } from "../Infiltration/ui/InfiltrationRoot";
|
||||
import { ResleeveRoot } from "../PersonObjects/Resleeving/ui/ResleeveRoot";
|
||||
import { GraftingRoot } from "../PersonObjects/Grafting/ui/GraftingRoot";
|
||||
import { WorkInProgressRoot } from "./WorkInProgressRoot";
|
||||
import { GameOptionsRoot } from "./React/GameOptionsRoot";
|
||||
import { SleeveRoot } from "../PersonObjects/Sleeve/ui/SleeveRoot";
|
||||
@@ -135,7 +135,7 @@ export let Router: IRouter = {
|
||||
toInfiltration: uninitialized,
|
||||
toJob: uninitialized,
|
||||
toMilestones: uninitialized,
|
||||
toResleeves: uninitialized,
|
||||
toGrafting: uninitialized,
|
||||
toScriptEditor: uninitialized,
|
||||
toSleeves: uninitialized,
|
||||
toStockMarket: uninitialized,
|
||||
@@ -226,7 +226,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
||||
toGang: () => setPage(Page.Gang),
|
||||
toHacknetNodes: () => setPage(Page.Hacknet),
|
||||
toMilestones: () => setPage(Page.Milestones),
|
||||
toResleeves: () => setPage(Page.Resleeves),
|
||||
toGrafting: () => setPage(Page.Grafting),
|
||||
toScriptEditor: (files: Record<string, string>, options?: ScriptEditorRouteOptions) => {
|
||||
setEditorOptions({
|
||||
files,
|
||||
@@ -429,8 +429,8 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
||||
mainPage = <BladeburnerRoot />;
|
||||
break;
|
||||
}
|
||||
case Page.Resleeves: {
|
||||
mainPage = <ResleeveRoot />;
|
||||
case Page.Grafting: {
|
||||
mainPage = <GraftingRoot />;
|
||||
break;
|
||||
}
|
||||
case Page.Travel: {
|
||||
|
||||
@@ -66,7 +66,7 @@ export function AlertManager(): React.ReactElement {
|
||||
{alerts.length > 0 && (
|
||||
<Modal open={true} onClose={close}>
|
||||
<Box overflow="scroll" sx={{ overflowWrap: "break-word", whiteSpace: "pre-line" }}>
|
||||
<Typography>{alerts[0].text}</Typography>
|
||||
<Typography component={'span'}>{alerts[0].text}</Typography>
|
||||
</Box>
|
||||
</Modal>
|
||||
)}
|
||||
|
||||
@@ -143,51 +143,66 @@ function Work(): React.ReactElement {
|
||||
let details = <></>;
|
||||
let header = <></>;
|
||||
let innerText = <></>;
|
||||
if (player.workType === CONSTANTS.WorkTypeCompanyPartTime || player.workType === CONSTANTS.WorkTypeCompany) {
|
||||
details = (
|
||||
<>
|
||||
{player.jobs[player.companyName]} at <strong>{player.companyName}</strong>
|
||||
</>
|
||||
);
|
||||
header = (
|
||||
<>
|
||||
Working at <strong>{player.companyName}</strong>
|
||||
</>
|
||||
);
|
||||
innerText = (
|
||||
<>
|
||||
+<Reputation reputation={player.workRepGained} /> rep
|
||||
</>
|
||||
);
|
||||
} else if (player.workType === CONSTANTS.WorkTypeFaction) {
|
||||
details = (
|
||||
<>
|
||||
{player.factionWorkType} for <strong>{player.currentWorkFactionName}</strong>
|
||||
</>
|
||||
);
|
||||
header = (
|
||||
<>
|
||||
Working for <strong>{player.currentWorkFactionName}</strong>
|
||||
</>
|
||||
);
|
||||
innerText = (
|
||||
<>
|
||||
+<Reputation reputation={player.workRepGained} /> rep
|
||||
</>
|
||||
);
|
||||
} else if (player.workType === CONSTANTS.WorkTypeStudyClass) {
|
||||
details = <>{player.workType}</>;
|
||||
header = <>You are {player.className}</>;
|
||||
innerText = <>{convertTimeMsToTimeElapsedString(player.timeWorked)}</>;
|
||||
} else if (player.workType === CONSTANTS.WorkTypeCreateProgram) {
|
||||
details = <>Coding {player.createProgramName}</>;
|
||||
header = <>Creating a program</>;
|
||||
innerText = (
|
||||
<>
|
||||
{player.createProgramName}{" "}
|
||||
{((player.timeWorkedCreateProgram / player.timeNeededToCompleteWork) * 100).toFixed(2)}%
|
||||
</>
|
||||
);
|
||||
switch (player.workType) {
|
||||
case CONSTANTS.WorkTypeCompanyPartTime:
|
||||
case CONSTANTS.WorkTypeCompany:
|
||||
details = (
|
||||
<>
|
||||
{player.jobs[player.companyName]} at <strong>{player.companyName}</strong>
|
||||
</>
|
||||
);
|
||||
header = (
|
||||
<>
|
||||
Working at <strong>{player.companyName}</strong>
|
||||
</>
|
||||
);
|
||||
innerText = (
|
||||
<>
|
||||
+<Reputation reputation={player.workRepGained} /> rep
|
||||
</>
|
||||
);
|
||||
break;
|
||||
case CONSTANTS.WorkTypeFaction:
|
||||
details = (
|
||||
<>
|
||||
{player.factionWorkType} for <strong>{player.currentWorkFactionName}</strong>
|
||||
</>
|
||||
);
|
||||
header = (
|
||||
<>
|
||||
Working for <strong>{player.currentWorkFactionName}</strong>
|
||||
</>
|
||||
);
|
||||
innerText = (
|
||||
<>
|
||||
+<Reputation reputation={player.workRepGained} /> rep
|
||||
</>
|
||||
);
|
||||
break;
|
||||
case CONSTANTS.WorkTypeStudyClass:
|
||||
details = <>{player.workType}</>;
|
||||
header = <>You are {player.className}</>;
|
||||
innerText = <>{convertTimeMsToTimeElapsedString(player.timeWorked)}</>;
|
||||
break;
|
||||
case CONSTANTS.WorkTypeCreateProgram:
|
||||
details = <>Coding {player.createProgramName}</>;
|
||||
header = <>Creating a program</>;
|
||||
innerText = (
|
||||
<>
|
||||
{player.createProgramName}{" "}
|
||||
{((player.timeWorkedCreateProgram / player.timeNeededToCompleteWork) * 100).toFixed(2)}%
|
||||
</>
|
||||
);
|
||||
break;
|
||||
case CONSTANTS.WorkTypeGraftAugmentation:
|
||||
details = <>Grafting {player.graftAugmentationName}</>;
|
||||
header = <>Grafting an Augmentation</>;
|
||||
innerText = (
|
||||
<>
|
||||
<strong>{((player.timeWorkedGraftAugmentation / player.timeNeededToCompleteWork) * 100).toFixed(2)}%</strong>{" "}
|
||||
done
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -465,7 +480,7 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
|
||||
<Box sx={{ display: "flex", borderTop: `1px solid ${Settings.theme.welllight}` }}>
|
||||
<Box sx={{ display: "flex", flex: 1, justifyContent: "flex-start", alignItems: "center" }}>
|
||||
<IconButton aria-label="save game" onClick={save}>
|
||||
<Tooltip title="Save game">
|
||||
<Tooltip title={Settings.AutosaveInterval !== 0 ? "Save game" : "Save game (auto-saves are disabled!)"}>
|
||||
<SaveIcon color={Settings.AutosaveInterval !== 0 ? "primary" : "error"} />
|
||||
</Tooltip>
|
||||
</IconButton>
|
||||
|
||||
@@ -328,6 +328,14 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
tooltip={<>If this is set, there will be no "Game Saved!" toast appearing after an auto-save.</>}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<OptionSwitch
|
||||
checked={Settings.SuppressAutosaveDisabledWarnings}
|
||||
onChange={(newValue) => (Settings.SuppressAutosaveDisabledWarnings = newValue)}
|
||||
text="Suppress Auto-Save Disabled Warning"
|
||||
tooltip={<>If this is set, there will be no warning triggered when auto-save is disabled (at 0).</>}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<OptionSwitch
|
||||
checked={Settings.DisableHotkeys}
|
||||
|
||||
@@ -18,6 +18,7 @@ import { Theme } from "@mui/material";
|
||||
import { findRunningScript } from "../../Script/ScriptHelpers";
|
||||
import { Player } from "../../Player";
|
||||
import { debounce } from "lodash";
|
||||
import { WorkerScriptStartStopEventEmitter } from "../../Netscript/WorkerScriptStartStopEventEmitter";
|
||||
|
||||
let layerCounter = 0;
|
||||
|
||||
@@ -128,6 +129,23 @@ function LogWindow(props: IProps): React.ReactElement {
|
||||
setRerender((old) => !old);
|
||||
}
|
||||
|
||||
useEffect(
|
||||
() =>
|
||||
WorkerScriptStartStopEventEmitter.subscribe(() => {
|
||||
setTimeout(() => {
|
||||
const server = GetServer(script.server);
|
||||
if (server === null) return;
|
||||
const exisitingScript = findRunningScript(script.filename, script.args, server);
|
||||
if (exisitingScript) {
|
||||
exisitingScript.logs = script.logs.concat(exisitingScript.logs)
|
||||
setScript(exisitingScript)
|
||||
}
|
||||
rerender();
|
||||
}, 100)
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
updateLayer();
|
||||
const id = setInterval(rerender, 1000);
|
||||
@@ -198,7 +216,7 @@ function LogWindow(props: IProps): React.ReactElement {
|
||||
const node = draggableRef?.current;
|
||||
if (!node) return;
|
||||
|
||||
if(!isOnScreen(node)) {
|
||||
if (!isOnScreen(node)) {
|
||||
resetPosition();
|
||||
}
|
||||
}, 100);
|
||||
@@ -207,25 +225,25 @@ function LogWindow(props: IProps): React.ReactElement {
|
||||
const bounds = node.getBoundingClientRect();
|
||||
|
||||
return !(bounds.right < 0 ||
|
||||
bounds.bottom < 0 ||
|
||||
bounds.left > innerWidth ||
|
||||
bounds.top > outerWidth);
|
||||
bounds.bottom < 0 ||
|
||||
bounds.left > innerWidth ||
|
||||
bounds.top > outerWidth);
|
||||
}
|
||||
|
||||
const resetPosition = (): void => {
|
||||
const node = rootRef?.current;
|
||||
if (!node) return;
|
||||
const state = node.state as {x: number; y: number};
|
||||
const state = node.state as { x: number; y: number };
|
||||
state.x = 0;
|
||||
state.y = 0;
|
||||
node.setState(state);
|
||||
}
|
||||
|
||||
const boundToBody = (e: any): void | false => {
|
||||
if(e.clientX < 0 ||
|
||||
e.clientY < 0 ||
|
||||
e.clientX > innerWidth ||
|
||||
e.clientY > innerHeight) return false;
|
||||
if (e.clientX < 0 ||
|
||||
e.clientY < 0 ||
|
||||
e.clientX > innerWidth ||
|
||||
e.clientY > innerHeight) return false;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
+2
-2
@@ -23,7 +23,7 @@ export enum Page {
|
||||
Job,
|
||||
Milestones,
|
||||
Options,
|
||||
Resleeves,
|
||||
Grafting,
|
||||
Sleeves,
|
||||
Stats,
|
||||
StockMarket,
|
||||
@@ -74,7 +74,7 @@ export interface IRouter {
|
||||
toInfiltration(location: Location): void;
|
||||
toJob(): void;
|
||||
toMilestones(): void;
|
||||
toResleeves(): void;
|
||||
toGrafting(): void;
|
||||
toScriptEditor(files?: Record<string, string>, options?: ScriptEditorRouteOptions): void;
|
||||
toSleeves(): void;
|
||||
toStockMarket(): void;
|
||||
|
||||
@@ -41,8 +41,8 @@ export function WorkInProgressRoot(): React.ReactElement {
|
||||
return (
|
||||
<>
|
||||
<Typography variant="h4" color="primary">
|
||||
You have not joined {player.currentWorkFactionName || "(Faction not found)"} yet or cannot work at this time,
|
||||
please try again if you think this should have worked
|
||||
You have not joined {player.currentWorkFactionName || "(Faction not found)"} yet or cannot work at this
|
||||
time, please try again if you think this should have worked
|
||||
</Typography>
|
||||
<Button onClick={() => router.toFactions()}>Back to Factions</Button>
|
||||
</>
|
||||
@@ -483,6 +483,42 @@ export function WorkInProgressRoot(): React.ReactElement {
|
||||
);
|
||||
}
|
||||
|
||||
if (player.graftAugmentationName !== "") {
|
||||
function cancel(): void {
|
||||
player.finishGraftAugmentationWork(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.graftAugmentationName}.
|
||||
<br />
|
||||
<br />
|
||||
You have been working for {convertTimeMsToTimeElapsedString(player.timeWorked)}
|
||||
<br />
|
||||
<br />
|
||||
The augmentation is{" "}
|
||||
{((player.timeWorkedGraftAugmentation / player.timeNeededToCompleteWork) * 100).toFixed(2)}% done being
|
||||
crafted.
|
||||
<br />
|
||||
If you cancel, your work will <b>not</b> be saved, and the money you spent will <b>not</b> be returned.
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Button sx={{ mx: 2 }} onClick={cancel}>
|
||||
Cancel work on crafting Augmentation
|
||||
</Button>
|
||||
<Button onClick={unfocus}>Do something else simultaneously</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
if (!player.workType) router.toTerminal();
|
||||
|
||||
return <></>;
|
||||
|
||||
+32
-23
@@ -16,7 +16,6 @@ import "numeral/locales/ru";
|
||||
|
||||
import { Settings } from "../Settings/Settings";
|
||||
|
||||
|
||||
const extraFormats = [1e15, 1e18, 1e21, 1e24, 1e27, 1e30];
|
||||
const extraNotations = ["q", "Q", "s", "S", "o", "n"];
|
||||
const gigaMultiplier = { standard: 1e9, iec60027_2: 2 ** 30 };
|
||||
@@ -52,7 +51,7 @@ class NumeralFormatter {
|
||||
}
|
||||
|
||||
formatBigNumber(n: number): string {
|
||||
return this.format(n, "0.[000]a");
|
||||
return this.format(n, "0.000a");
|
||||
}
|
||||
|
||||
// TODO: leverage numeral.js to do it. This function also implies you can
|
||||
@@ -67,10 +66,10 @@ class NumeralFormatter {
|
||||
}
|
||||
}
|
||||
if (nAbs < 1000) {
|
||||
return this.format(n, "0.[" + "0".repeat(decimalPlaces) + "]");
|
||||
return this.format(n, "0." + "0".repeat(decimalPlaces));
|
||||
}
|
||||
const str = this.format(n, "0.[" + "0".repeat(decimalPlaces) + "]a");
|
||||
if (str === "NaNt") return this.format(n, "0.[" + " ".repeat(decimalPlaces) + "]e+0");
|
||||
const str = this.format(n, "0." + "0".repeat(decimalPlaces) + "a");
|
||||
if (str === "NaNt") return this.format(n, "0." + " ".repeat(decimalPlaces) + "e+0");
|
||||
return str;
|
||||
}
|
||||
|
||||
@@ -113,8 +112,7 @@ class NumeralFormatter {
|
||||
}
|
||||
|
||||
formatRAM(n: number): string {
|
||||
if(Settings.UseIEC60027_2)
|
||||
{
|
||||
if (Settings.UseIEC60027_2) {
|
||||
return this.format(n * gigaMultiplier.iec60027_2, "0.00ib");
|
||||
}
|
||||
return this.format(n * gigaMultiplier.standard, "0.00b");
|
||||
@@ -189,7 +187,7 @@ class NumeralFormatter {
|
||||
}
|
||||
|
||||
parseCustomLargeNumber(str: string): number {
|
||||
const numericRegExp = new RegExp('^(\-?\\d+\\.?\\d*)([' + extraNotations.join("") + ']?)$');
|
||||
const numericRegExp = new RegExp("^(-?\\d+\\.?\\d*)([" + extraNotations.join("") + "]?)$");
|
||||
const match = str.match(numericRegExp);
|
||||
if (match == null) {
|
||||
return NaN;
|
||||
@@ -203,14 +201,17 @@ class NumeralFormatter {
|
||||
}
|
||||
|
||||
largestAbsoluteNumber(n1: number, n2 = 0, n3 = 0): number {
|
||||
if(isNaN(n1)) n1=0;
|
||||
if(isNaN(n2)) n2=0;
|
||||
if(isNaN(n3)) n3=0;
|
||||
if (isNaN(n1)) n1 = 0;
|
||||
if (isNaN(n2)) n2 = 0;
|
||||
if (isNaN(n3)) n3 = 0;
|
||||
const largestAbsolute = Math.max(Math.abs(n1), Math.abs(n2), Math.abs(n3));
|
||||
switch(largestAbsolute) {
|
||||
case Math.abs(n1): return n1;
|
||||
case Math.abs(n2): return n2;
|
||||
case Math.abs(n3): return n3;
|
||||
switch (largestAbsolute) {
|
||||
case Math.abs(n1):
|
||||
return n1;
|
||||
case Math.abs(n2):
|
||||
return n2;
|
||||
case Math.abs(n3):
|
||||
return n3;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -222,21 +223,29 @@ class NumeralFormatter {
|
||||
const parsed = parseFloat(s);
|
||||
const selfParsed = this.parseCustomLargeNumber(s);
|
||||
// Check for one or more NaN values
|
||||
if (isNaN(parsed) && numeralValue === null && isNaN(selfParsed)) { // 3x NaN
|
||||
if (isNaN(parsed) && numeralValue === null && isNaN(selfParsed)) {
|
||||
// 3x NaN
|
||||
return NaN;
|
||||
} else if (isNaN(parsed) && isNaN(selfParsed)) { // 2x NaN
|
||||
} else if (isNaN(parsed) && isNaN(selfParsed)) {
|
||||
// 2x NaN
|
||||
return numeralValue;
|
||||
} else if (numeralValue === null && isNaN(selfParsed)) { // 2x NaN
|
||||
} else if (numeralValue === null && isNaN(selfParsed)) {
|
||||
// 2x NaN
|
||||
return parsed;
|
||||
} else if (isNaN(parsed) && numeralValue === null) { // 2x NaN
|
||||
} else if (isNaN(parsed) && numeralValue === null) {
|
||||
// 2x NaN
|
||||
return selfParsed;
|
||||
} else if (isNaN(parsed)) { // 1x NaN
|
||||
} else if (isNaN(parsed)) {
|
||||
// 1x NaN
|
||||
return this.largestAbsoluteNumber(numeralValue, selfParsed);
|
||||
} else if (numeralValue === null) { // 1x NaN
|
||||
} else if (numeralValue === null) {
|
||||
// 1x NaN
|
||||
return this.largestAbsoluteNumber(parsed, selfParsed);
|
||||
} else if (isNaN(selfParsed)) { // 1x NaN
|
||||
} else if (isNaN(selfParsed)) {
|
||||
// 1x NaN
|
||||
return this.largestAbsoluteNumber(numeralValue, parsed);
|
||||
} else { // no NaN
|
||||
} else {
|
||||
// no NaN
|
||||
return this.largestAbsoluteNumber(numeralValue, parsed, selfParsed);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user