diff --git a/markdown/bitburner.bitnodebooleanoptions.disable4sdata.md b/markdown/bitburner.bitnodebooleanoptions.disable4sdata.md new file mode 100644 index 000000000..2a9602a0b --- /dev/null +++ b/markdown/bitburner.bitnodebooleanoptions.disable4sdata.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [BitNodeBooleanOptions](./bitburner.bitnodebooleanoptions.md) > [disable4SData](./bitburner.bitnodebooleanoptions.disable4sdata.md) + +## BitNodeBooleanOptions.disable4SData property + +**Signature:** + +```typescript +disable4SData: boolean; +``` diff --git a/markdown/bitburner.bitnodebooleanoptions.disablebladeburner.md b/markdown/bitburner.bitnodebooleanoptions.disablebladeburner.md new file mode 100644 index 000000000..cc7102bc1 --- /dev/null +++ b/markdown/bitburner.bitnodebooleanoptions.disablebladeburner.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [BitNodeBooleanOptions](./bitburner.bitnodebooleanoptions.md) > [disableBladeburner](./bitburner.bitnodebooleanoptions.disablebladeburner.md) + +## BitNodeBooleanOptions.disableBladeburner property + +**Signature:** + +```typescript +disableBladeburner: boolean; +``` diff --git a/markdown/bitburner.bitnodebooleanoptions.disablecorporation.md b/markdown/bitburner.bitnodebooleanoptions.disablecorporation.md new file mode 100644 index 000000000..705f44ca2 --- /dev/null +++ b/markdown/bitburner.bitnodebooleanoptions.disablecorporation.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [BitNodeBooleanOptions](./bitburner.bitnodebooleanoptions.md) > [disableCorporation](./bitburner.bitnodebooleanoptions.disablecorporation.md) + +## BitNodeBooleanOptions.disableCorporation property + +**Signature:** + +```typescript +disableCorporation: boolean; +``` diff --git a/markdown/bitburner.bitnodebooleanoptions.disablegang.md b/markdown/bitburner.bitnodebooleanoptions.disablegang.md new file mode 100644 index 000000000..73461164f --- /dev/null +++ b/markdown/bitburner.bitnodebooleanoptions.disablegang.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [BitNodeBooleanOptions](./bitburner.bitnodebooleanoptions.md) > [disableGang](./bitburner.bitnodebooleanoptions.disablegang.md) + +## BitNodeBooleanOptions.disableGang property + +**Signature:** + +```typescript +disableGang: boolean; +``` diff --git a/markdown/bitburner.bitnodebooleanoptions.disablehacknetserver.md b/markdown/bitburner.bitnodebooleanoptions.disablehacknetserver.md new file mode 100644 index 000000000..58780661b --- /dev/null +++ b/markdown/bitburner.bitnodebooleanoptions.disablehacknetserver.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [BitNodeBooleanOptions](./bitburner.bitnodebooleanoptions.md) > [disableHacknetServer](./bitburner.bitnodebooleanoptions.disablehacknetserver.md) + +## BitNodeBooleanOptions.disableHacknetServer property + +**Signature:** + +```typescript +disableHacknetServer: boolean; +``` diff --git a/markdown/bitburner.bitnodebooleanoptions.disablesleeveexpandaugmentation.md b/markdown/bitburner.bitnodebooleanoptions.disablesleeveexpandaugmentation.md new file mode 100644 index 000000000..de8abfc31 --- /dev/null +++ b/markdown/bitburner.bitnodebooleanoptions.disablesleeveexpandaugmentation.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [BitNodeBooleanOptions](./bitburner.bitnodebooleanoptions.md) > [disableSleeveExpAndAugmentation](./bitburner.bitnodebooleanoptions.disablesleeveexpandaugmentation.md) + +## BitNodeBooleanOptions.disableSleeveExpAndAugmentation property + +**Signature:** + +```typescript +disableSleeveExpAndAugmentation: boolean; +``` diff --git a/markdown/bitburner.bitnodebooleanoptions.md b/markdown/bitburner.bitnodebooleanoptions.md new file mode 100644 index 000000000..e013a315d --- /dev/null +++ b/markdown/bitburner.bitnodebooleanoptions.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [BitNodeBooleanOptions](./bitburner.bitnodebooleanoptions.md) + +## BitNodeBooleanOptions interface + +restrictHomePCUpgrade: The home computer's maximum RAM and number of cores are lower than normal. Max RAM: 128GB. Max core: 1. + +disableSleeveExpAndAugmentation: Your Sleeves do not gain experience when they perform action. You also cannot buy augmentations for them. + +**Signature:** + +```typescript +export interface BitNodeBooleanOptions +``` + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [disable4SData](./bitburner.bitnodebooleanoptions.disable4sdata.md) | | boolean | | +| [disableBladeburner](./bitburner.bitnodebooleanoptions.disablebladeburner.md) | | boolean | | +| [disableCorporation](./bitburner.bitnodebooleanoptions.disablecorporation.md) | | boolean | | +| [disableGang](./bitburner.bitnodebooleanoptions.disablegang.md) | | boolean | | +| [disableHacknetServer](./bitburner.bitnodebooleanoptions.disablehacknetserver.md) | | boolean | | +| [disableSleeveExpAndAugmentation](./bitburner.bitnodebooleanoptions.disablesleeveexpandaugmentation.md) | | boolean | | +| [restrictHomePCUpgrade](./bitburner.bitnodebooleanoptions.restricthomepcupgrade.md) | | boolean | | + diff --git a/markdown/bitburner.bitnodebooleanoptions.restricthomepcupgrade.md b/markdown/bitburner.bitnodebooleanoptions.restricthomepcupgrade.md new file mode 100644 index 000000000..3caeb794a --- /dev/null +++ b/markdown/bitburner.bitnodebooleanoptions.restricthomepcupgrade.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [BitNodeBooleanOptions](./bitburner.bitnodebooleanoptions.md) > [restrictHomePCUpgrade](./bitburner.bitnodebooleanoptions.restricthomepcupgrade.md) + +## BitNodeBooleanOptions.restrictHomePCUpgrade property + +**Signature:** + +```typescript +restrictHomePCUpgrade: boolean; +``` diff --git a/markdown/bitburner.bitnodeoptions.intelligenceoverride.md b/markdown/bitburner.bitnodeoptions.intelligenceoverride.md new file mode 100644 index 000000000..7f6ba044a --- /dev/null +++ b/markdown/bitburner.bitnodeoptions.intelligenceoverride.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [BitNodeOptions](./bitburner.bitnodeoptions.md) > [intelligenceOverride](./bitburner.bitnodeoptions.intelligenceoverride.md) + +## BitNodeOptions.intelligenceOverride property + +**Signature:** + +```typescript +intelligenceOverride: number | undefined; +``` diff --git a/markdown/bitburner.bitnodeoptions.md b/markdown/bitburner.bitnodeoptions.md new file mode 100644 index 000000000..95edd24ac --- /dev/null +++ b/markdown/bitburner.bitnodeoptions.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [BitNodeOptions](./bitburner.bitnodeoptions.md) + +## BitNodeOptions interface + +Default value: - sourceFileOverrides: an empty Map - intelligenceOverride: undefined - All boolean options: false + +If you specify intelligenceOverride, it must be a non-negative integer. + +**Signature:** + +```typescript +export interface BitNodeOptions extends BitNodeBooleanOptions +``` +**Extends:** [BitNodeBooleanOptions](./bitburner.bitnodebooleanoptions.md) + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [intelligenceOverride](./bitburner.bitnodeoptions.intelligenceoverride.md) | | number \| undefined | | +| [sourceFileOverrides](./bitburner.bitnodeoptions.sourcefileoverrides.md) | | Map<number, number> | | + diff --git a/markdown/bitburner.bitnodeoptions.sourcefileoverrides.md b/markdown/bitburner.bitnodeoptions.sourcefileoverrides.md new file mode 100644 index 000000000..6b7e5998d --- /dev/null +++ b/markdown/bitburner.bitnodeoptions.sourcefileoverrides.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [BitNodeOptions](./bitburner.bitnodeoptions.md) > [sourceFileOverrides](./bitburner.bitnodeoptions.sourcefileoverrides.md) + +## BitNodeOptions.sourceFileOverrides property + +**Signature:** + +```typescript +sourceFileOverrides: Map; +``` diff --git a/markdown/bitburner.md b/markdown/bitburner.md index 52b905955..b6919a62b 100644 --- a/markdown/bitburner.md +++ b/markdown/bitburner.md @@ -30,7 +30,9 @@ | [AutocompleteData](./bitburner.autocompletedata.md) | Used for autocompletion | | [BackdoorRequirement](./bitburner.backdoorrequirement.md) | Player must have installed a backdoor on this server. | | [BasicHGWOptions](./bitburner.basichgwoptions.md) | Options to affect the behavior of [hack](./bitburner.ns.hack.md), [grow](./bitburner.ns.grow.md), and [weaken](./bitburner.ns.weaken.md). | +| [BitNodeBooleanOptions](./bitburner.bitnodebooleanoptions.md) |

restrictHomePCUpgrade: The home computer's maximum RAM and number of cores are lower than normal. Max RAM: 128GB. Max core: 1.

disableSleeveExpAndAugmentation: Your Sleeves do not gain experience when they perform action. You also cannot buy augmentations for them.

| | [BitNodeMultipliers](./bitburner.bitnodemultipliers.md) | All multipliers affecting the difficulty of the current challenge. | +| [BitNodeOptions](./bitburner.bitnodeoptions.md) |

Default value: - sourceFileOverrides: an empty Map - intelligenceOverride: undefined - All boolean options: false

If you specify intelligenceOverride, it must be a non-negative integer.

| | [BitNodeRequirement](./bitburner.bitnoderequirement.md) | Player must be located in this BitNode. | | [Bladeburner](./bitburner.bladeburner.md) | Bladeburner API | | [BladeburnerCurAction](./bitburner.bladeburnercuraction.md) | Bladeburner current action. | diff --git a/markdown/bitburner.resetinfo.bitnodeoptions.md b/markdown/bitburner.resetinfo.bitnodeoptions.md new file mode 100644 index 000000000..f4849bad7 --- /dev/null +++ b/markdown/bitburner.resetinfo.bitnodeoptions.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [ResetInfo](./bitburner.resetinfo.md) > [bitNodeOptions](./bitburner.resetinfo.bitnodeoptions.md) + +## ResetInfo.bitNodeOptions property + +Current BitNode options + +**Signature:** + +```typescript +bitNodeOptions: BitNodeOptions; +``` diff --git a/markdown/bitburner.resetinfo.md b/markdown/bitburner.resetinfo.md index eddc8e1c5..6f834bf2f 100644 --- a/markdown/bitburner.resetinfo.md +++ b/markdown/bitburner.resetinfo.md @@ -16,6 +16,7 @@ interface ResetInfo | Property | Modifiers | Type | Description | | --- | --- | --- | --- | +| [bitNodeOptions](./bitburner.resetinfo.bitnodeoptions.md) | | [BitNodeOptions](./bitburner.bitnodeoptions.md) | Current BitNode options | | [currentNode](./bitburner.resetinfo.currentnode.md) | | number | The current bitnode | | [lastAugReset](./bitburner.resetinfo.lastaugreset.md) | | number | Numeric timestamp (from Date.now()) of last augmentation reset | | [lastNodeReset](./bitburner.resetinfo.lastnodereset.md) | | number | Numeric timestamp (from Date.now()) of last bitnode reset | diff --git a/markdown/bitburner.singularity.b1tflum3.md b/markdown/bitburner.singularity.b1tflum3.md index 33c28a898..7977db281 100644 --- a/markdown/bitburner.singularity.b1tflum3.md +++ b/markdown/bitburner.singularity.b1tflum3.md @@ -9,7 +9,7 @@ b1t\_flum3 into a different BN. **Signature:** ```typescript -b1tflum3(nextBN: number, callbackScript?: string): void; +b1tflum3(nextBN: number, callbackScript?: string, bitNodeOptions?: BitNodeOptions): void; ``` ## Parameters @@ -18,6 +18,7 @@ b1tflum3(nextBN: number, callbackScript?: string): void; | --- | --- | --- | | nextBN | number | BN number to jump to | | callbackScript | string | _(Optional)_ Name of the script to launch in the next BN. | +| bitNodeOptions | [BitNodeOptions](./bitburner.bitnodeoptions.md) | _(Optional)_ BitNode options for the next BN. | **Returns:** diff --git a/markdown/bitburner.singularity.destroyw0r1dd43m0n.md b/markdown/bitburner.singularity.destroyw0r1dd43m0n.md index f4ccd8c5f..accdb02b1 100644 --- a/markdown/bitburner.singularity.destroyw0r1dd43m0n.md +++ b/markdown/bitburner.singularity.destroyw0r1dd43m0n.md @@ -9,7 +9,7 @@ Destroy the w0r1d\_d43m0n and move on to the next BN. **Signature:** ```typescript -destroyW0r1dD43m0n(nextBN: number, callbackScript?: string): void; +destroyW0r1dD43m0n(nextBN: number, callbackScript?: string, bitNodeOptions?: BitNodeOptions): void; ``` ## Parameters @@ -18,6 +18,7 @@ destroyW0r1dD43m0n(nextBN: number, callbackScript?: string): void; | --- | --- | --- | | nextBN | number | BN number to jump to | | callbackScript | string | _(Optional)_ Name of the script to launch in the next BN. | +| bitNodeOptions | [BitNodeOptions](./bitburner.bitnodeoptions.md) | _(Optional)_ BitNode options for the next BN. | **Returns:** diff --git a/markdown/bitburner.singularity.md b/markdown/bitburner.singularity.md index 29511d602..012af34d5 100644 --- a/markdown/bitburner.singularity.md +++ b/markdown/bitburner.singularity.md @@ -21,12 +21,12 @@ This API requires Source-File 4 to use. The RAM cost of all these functions is m | Method | Description | | --- | --- | | [applyToCompany(companyName, field)](./bitburner.singularity.applytocompany.md) | Apply for a job at a company. | -| [b1tflum3(nextBN, callbackScript)](./bitburner.singularity.b1tflum3.md) | b1t\_flum3 into a different BN. | +| [b1tflum3(nextBN, callbackScript, bitNodeOptions)](./bitburner.singularity.b1tflum3.md) | b1t\_flum3 into a different BN. | | [checkFactionInvitations()](./bitburner.singularity.checkfactioninvitations.md) | List all current faction invitations. | | [commitCrime(crime, focus)](./bitburner.singularity.commitcrime.md) | Commit a crime. | | [connect(hostname)](./bitburner.singularity.connect.md) | Connect to a server. | | [createProgram(program, focus)](./bitburner.singularity.createprogram.md) | Create a program. | -| [destroyW0r1dD43m0n(nextBN, callbackScript)](./bitburner.singularity.destroyw0r1dd43m0n.md) | Destroy the w0r1d\_d43m0n and move on to the next BN. | +| [destroyW0r1dD43m0n(nextBN, callbackScript, bitNodeOptions)](./bitburner.singularity.destroyw0r1dd43m0n.md) | Destroy the w0r1d\_d43m0n and move on to the next BN. | | [donateToFaction(faction, amount)](./bitburner.singularity.donatetofaction.md) | Donate to a faction. | | [exportGame()](./bitburner.singularity.exportgame.md) | Backup game save. | | [exportGameBonus()](./bitburner.singularity.exportgamebonus.md) | Returns Backup save bonus availability. | diff --git a/src/Achievements/Achievements.ts b/src/Achievements/Achievements.ts index eff510dbe..85da3e733 100644 --- a/src/Achievements/Achievements.ts +++ b/src/Achievements/Achievements.ts @@ -29,7 +29,7 @@ import { workerScripts } from "../Netscript/WorkerScripts"; import { getRecordValues } from "../Types/Record"; import { ServerConstants } from "../Server/data/Constants"; -import { isBitNodeFinished } from "../BitNode/BitNodeUtils"; +import { canAccessBitNodeFeature, isBitNodeFinished, knowAboutBitverse } from "../BitNode/BitNodeUtils"; // Unable to correctly cast the JSON data into AchievementDataJson type otherwise... const achievementData = ((data)).achievements; @@ -60,14 +60,6 @@ export interface AchievementData { Description: string; } -function canAccessBitNodeFeature(bitNode: number): boolean { - return Player.bitNodeN === bitNode || Player.sourceFileLvl(bitNode) > 0; -} - -function knowsAboutBitverse(): boolean { - return Player.sourceFiles.size > 0; -} - function sfAchievements(): Record { const achs: Record = {}; for (let i = 1; i <= 12; i++) { @@ -75,7 +67,7 @@ function sfAchievements(): Record { achs[ID] = { ...achievementData[ID], Icon: ID, - Visible: knowsAboutBitverse, + Visible: knowAboutBitverse, Condition: () => Player.sourceFileLvl(i) >= 1, }; } @@ -498,7 +490,7 @@ export const achievements: Record = { INDECISIVE: { ...achievementData.INDECISIVE, Icon: "1H", - Visible: knowsAboutBitverse, + Visible: knowAboutBitverse, Condition: (function () { let c = 0; setInterval(() => { @@ -514,13 +506,13 @@ export const achievements: Record = { FAST_BN: { ...achievementData.FAST_BN, Icon: "2DAYS", - Visible: knowsAboutBitverse, + Visible: knowAboutBitverse, Condition: () => isBitNodeFinished() && Player.playtimeSinceLastBitnode < 1000 * 60 * 60 * 24 * 2, }, CHALLENGE_BN1: { ...achievementData.CHALLENGE_BN1, Icon: "BN1+", - Visible: knowsAboutBitverse, + Visible: knowAboutBitverse, Condition: () => Player.bitNodeN === 1 && isBitNodeFinished() && diff --git a/src/Arcade/ui/ArcadeRoot.tsx b/src/Arcade/ui/ArcadeRoot.tsx index b8e33f678..81193a48e 100644 --- a/src/Arcade/ui/ArcadeRoot.tsx +++ b/src/Arcade/ui/ArcadeRoot.tsx @@ -14,7 +14,7 @@ export function ArcadeRoot(): React.ReactElement { const [page, setPage] = useState(Page.None); function mbBurner2000(): void { - if (Player.sourceFileLvl(1) === 0) { + if (Player.activeSourceFileLvl(1) === 0) { AlertEvents.emit("This machine is broken."); } else { setPage(Page.Megabyteburner2000); diff --git a/src/Augmentation/AugmentationHelpers.ts b/src/Augmentation/AugmentationHelpers.ts index 78b5ccd6f..25e5869cf 100644 --- a/src/Augmentation/AugmentationHelpers.ts +++ b/src/Augmentation/AugmentationHelpers.ts @@ -26,7 +26,7 @@ const soaAugmentationNames = [ ]; export function getBaseAugmentationPriceMultiplier(): number { - return CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][Player.sourceFileLvl(11)]; + return CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][Player.activeSourceFileLvl(11)]; } export function getGenericAugmentationPriceMultiplier(): number { const queuedNonSoAAugmentationList = Player.queuedAugmentations.filter((augmentation) => { diff --git a/src/Augmentation/ui/PlayerMultipliers.tsx b/src/Augmentation/ui/PlayerMultipliers.tsx index 205c648ba..93240ef85 100644 --- a/src/Augmentation/ui/PlayerMultipliers.tsx +++ b/src/Augmentation/ui/PlayerMultipliers.tsx @@ -7,6 +7,7 @@ import { Player } from "@player"; import { Settings } from "../../Settings/Settings"; import { formatPercent } from "../../ui/formatNumber"; import { Augmentations } from "../Augmentations"; +import { canAccessBitNodeFeature } from "../../BitNode/BitNodeUtils"; function calculateAugmentedStats(): Multipliers { let augP: Multipliers = defaultMultipliers(); @@ -29,7 +30,7 @@ function customFormatPercent(value: number): string { function BitNodeModifiedStats(props: IBitNodeModifiedStatsProps): React.ReactElement { // If the player doesn't have access to SF5 feature or if the property isn't affected by BitNode mults - if (props.mult === 1 || (Player.bitNodeN !== 5 && Player.sourceFileLvl(5) === 0)) { + if (props.mult === 1 || !canAccessBitNodeFeature(5)) { return {customFormatPercent(props.base)}; } diff --git a/src/Augmentation/ui/SourceFiles.tsx b/src/Augmentation/ui/SourceFiles.tsx index 890adb370..2fd777d9d 100644 --- a/src/Augmentation/ui/SourceFiles.tsx +++ b/src/Augmentation/ui/SourceFiles.tsx @@ -3,93 +3,101 @@ import Box from "@mui/material/Box"; import List from "@mui/material/List"; import Typography from "@mui/material/Typography"; import React, { useState } from "react"; -import { Exploit, ExploitName } from "../../Exploits/Exploit"; +import { Exploit, ExploitDescription } from "../../Exploits/Exploit"; import { Player } from "@player"; import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums"; import { Settings } from "../../Settings/Settings"; -import { SourceFile } from "../../SourceFile/SourceFile"; import { SourceFiles } from "../../SourceFile/SourceFiles"; -interface SfMinus1 { - info: React.ReactElement; +interface SourceFileData { n: number; + level: number; + maxLevel: number; + activeLevel: number; name: string; - lvl: number; + info: JSX.Element; } -const safeGetSf = (sfNum: number): SourceFile | SfMinus1 | null => { - if (sfNum === -1) { - const sfMinus1: SfMinus1 = { +const getSourceFileData = (sfNumber: number): SourceFileData | null => { + let maxLevel: number; + switch (sfNumber) { + case -1: + maxLevel = Object.keys(Exploit).length; + break; + case 12: + maxLevel = Infinity; + break; + default: + maxLevel = 3; + } + + const sourceFile = SourceFiles["SourceFile" + sfNumber]; + if (sourceFile === undefined) { + console.error(`Invalid source file number: ${sfNumber}`); + return null; + } + return { + n: sfNumber, + level: Player.sourceFileLvl(sfNumber), + maxLevel: maxLevel, + activeLevel: Player.activeSourceFileLvl(sfNumber), + name: sourceFile.name, + info: sourceFile.info, + }; +}; + +export function SourceFilesElement(): React.ReactElement { + const sourceFileList: SourceFileData[] = []; + + const exploits = Player.exploits; + // Create a fake SF for -1, if "owned" + if (exploits.length > 0) { + sourceFileList.push({ + n: -1, + level: Player.exploits.length, + maxLevel: Object.keys(Exploit).length, + activeLevel: Player.exploits.length, + name: "Source-File -1: Exploits in the BitNodes", info: ( <> - This Source-File can only be acquired with obscure knowledge of the game, javascript, and the web ecosystem. + This Source-File can only be acquired with obscure knowledge of the game, Javascript, and the web ecosystem.

It increases all of the player's multipliers by 0.1%

You have found the following exploits: -
-
- {Player.exploits.map((c) => ( - - * {ExploitName(c)} -
-
- ))} +
    + {Player.exploits.map((c) => ( +
  • + {c}: {ExploitDescription[c]} +
  • + ))} +
), - lvl: Player.exploits.length, - n: -1, - name: "Source-File -1: Exploits in the BitNodes", - }; - return sfMinus1; + }); + } + for (const sfNumber of Player.sourceFiles.keys()) { + const sourceFileData = getSourceFileData(sfNumber); + if (!sourceFileData) { + continue; + } + sourceFileList.push(sourceFileData); } - const srcFileKey = "SourceFile" + sfNum; - const sfObj = SourceFiles[srcFileKey]; - if (sfObj == null) { - console.error(`Invalid source file number: ${sfNum}`); - return null; - } - return sfObj; -}; - -const getMaxLevel = (sfObj: SourceFile | SfMinus1): string | number => { - let maxLevel; - switch (sfObj.n) { - case 12: - maxLevel = "∞"; - break; - case -1: - maxLevel = Object.keys(Exploit).length; - break; - default: - maxLevel = "3"; - } - return maxLevel; -}; - -export function SourceFilesElement(): React.ReactElement { - const sourceFilesCopy = new Map(Player.sourceFiles); - const exploits = Player.exploits; - // Create a fake SF for -1, if "owned" - if (exploits.length > 0) { - sourceFilesCopy.set(-1, exploits.length); - } - - const sfList = [...sourceFilesCopy]; if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) { - sfList.sort(([n1, __lvl1], [n2, __lvl2]) => n1 - n2); + sourceFileList.sort((a, b) => a.n - b.n); } - const [selectedSf, setSelectedSf] = useState(() => { - if (sfList.length === 0) return null; - const [n, lvl] = sfList[0]; - return { n, lvl }; + const [selectedSfData, setSelectedSfData] = useState(() => { + if (sourceFileList.length === 0) { + return null; + } + return sourceFileList[0]; }); - if (!selectedSf) { + if (!selectedSfData) { return <>; } @@ -104,26 +112,26 @@ export function SourceFilesElement(): React.ReactElement { sx={{ height: 400, overflowY: "scroll", borderRight: `1px solid ${Settings.theme.welllight}` }} disablePadding > - {sfList.map(([n, lvl], i) => { - const sfObj = safeGetSf(n); - if (!sfObj) return; - - const maxLevel = getMaxLevel(sfObj); - + {sourceFileList.map((sourceFileData, i) => { return ( setSelectedSf({ n, lvl })} - selected={selectedSf.n === n} + onClick={() => setSelectedSfData(sourceFileData)} + selected={selectedSfData.n === sourceFileData.n} sx={{ py: 0 }} > {sfObj.name}} + primary={{sourceFileData.name}} secondary={ - - Level {lvl} / {maxLevel} - + <> + + Level: {sourceFileData.level} / {sourceFileData.maxLevel} + + {sourceFileData.activeLevel < sourceFileData.level && ( + Active level: {sourceFileData.activeLevel} + )} + } /> @@ -131,28 +139,25 @@ export function SourceFilesElement(): React.ReactElement { })} - - - {safeGetSf(selectedSf.n)?.name} - - - {(() => { - const sfObj = safeGetSf(selectedSf.n); - if (!sfObj) return; - - const maxLevel = getMaxLevel(sfObj); - - return ( + {selectedSfData !== null && ( + + + {selectedSfData.name} + + + Level: {selectedSfData.level} / {selectedSfData.maxLevel} +
+ {selectedSfData.activeLevel < selectedSfData.level && ( <> - Level {selectedSf.lvl} / {maxLevel} + Active level: {selectedSfData.activeLevel}
-
- {sfObj.info} - ); - })()} -
-
+ )} +
+ {selectedSfData.info} +
+
+ )} ); diff --git a/src/BitNode/BitNode.tsx b/src/BitNode/BitNode.tsx index f1c10c3ae..a76562b0b 100644 --- a/src/BitNode/BitNode.tsx +++ b/src/BitNode/BitNode.tsx @@ -962,5 +962,5 @@ export function getBitNodeMultipliers(n: number, lvl: number): BitNodeMultiplier } export function initBitNodeMultipliers(): void { - replaceCurrentNodeMults(getBitNodeMultipliers(Player.bitNodeN, Player.sourceFileLvl(Player.bitNodeN) + 1)); + replaceCurrentNodeMults(getBitNodeMultipliers(Player.bitNodeN, Player.activeSourceFileLvl(Player.bitNodeN) + 1)); } diff --git a/src/BitNode/BitNodeUtils.ts b/src/BitNode/BitNodeUtils.ts index 65eb15fad..b2705c477 100644 --- a/src/BitNode/BitNodeUtils.ts +++ b/src/BitNode/BitNodeUtils.ts @@ -1,6 +1,11 @@ +import { Player } from "@player"; +import { type BitNodeOptions } from "@nsdefs"; import { GetServer } from "../Server/AllServers"; import { Server } from "../Server/Server"; import { SpecialServers } from "../Server/data/SpecialServers"; +import { JSONMap } from "../Types/Jsonable"; + +export const validBitNodes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]; export function isBitNodeFinished(): boolean { const wd = GetServer(SpecialServers.WorldDaemon); @@ -9,3 +14,70 @@ export function isBitNodeFinished(): boolean { } return wd.backdoorInstalled; } + +export function canAccessBitNodeFeature(bitNode: number): boolean { + return Player.bitNodeN === bitNode || Player.activeSourceFileLvl(bitNode) > 0; +} + +export function knowAboutBitverse(): boolean { + for (const sfActiveLevel of Player.activeSourceFiles.values()) { + if (sfActiveLevel > 0) { + return true; + } + } + return false; +} + +export function getDefaultBitNodeOptions(): BitNodeOptions { + return { + sourceFileOverrides: new Map(), + intelligenceOverride: undefined, + restrictHomePCUpgrade: false, + disableGang: false, + disableCorporation: false, + disableBladeburner: false, + disable4SData: false, + disableHacknetServer: false, + disableSleeveExpAndAugmentation: false, + }; +} + +export function validateSourceFileOverrides( + sourceFileOverrides: Map, + isDataFromPlayer: boolean, +): { + valid: boolean; + message?: string; +} { + if (!isDataFromPlayer && !(sourceFileOverrides instanceof JSONMap)) { + return { valid: false, message: `It must be a JSONMap.` }; + } + for (const [sfNumber, sfLevel] of sourceFileOverrides.entries()) { + if (!validBitNodes.includes(sfNumber)) { + return { valid: false, message: `Invalid BitNode: ${sfNumber}.` }; + } + if (!Number.isFinite(sfLevel)) { + return { valid: false, message: `Invalid SF level: ${sfLevel}.` }; + } + const maxSfLevel = Player.sourceFileLvl(sfNumber); + if (sfLevel > maxSfLevel) { + return { valid: false, message: `Invalid SF level: ${sfLevel}. Max level: ${maxSfLevel}.` }; + } + } + return { valid: true }; +} + +export function setBitNodeOptions(bitNodeOptions: BitNodeOptions): void { + const validationResultForSourceFileOverrides = validateSourceFileOverrides(bitNodeOptions.sourceFileOverrides, false); + if (!validationResultForSourceFileOverrides.valid) { + throw new Error(`sourceFileOverrides is invalid. Reason: ${validationResultForSourceFileOverrides.message}`); + } + if ( + bitNodeOptions.intelligenceOverride !== undefined && + (!Number.isInteger(bitNodeOptions.intelligenceOverride) || bitNodeOptions.intelligenceOverride < 0) + ) { + throw new Error(`intelligenceOverride is invalid. It must be a non-negative integer.`); + } + + Object.assign(Player.bitNodeOptions, bitNodeOptions); +} diff --git a/src/BitNode/ui/BitNodeAdvancedOptions.tsx b/src/BitNode/ui/BitNodeAdvancedOptions.tsx new file mode 100644 index 000000000..780728cba --- /dev/null +++ b/src/BitNode/ui/BitNodeAdvancedOptions.tsx @@ -0,0 +1,433 @@ +import { type BitNodeBooleanOptions } from "@nsdefs"; +import React from "react"; +import { + Box, + Button, + Collapse, + ListItemButton, + ListItemText, + MenuItem, + Paper, + Select, + TextField, + Tooltip, + Typography, +} from "@mui/material"; +import { OptionSwitch } from "../../ui/React/OptionSwitch"; +import { ButtonWithTooltip } from "../../ui/Components/ButtonWithTooltip"; +import { ExpandLess, ExpandMore } from "@mui/icons-material"; +import { JSONMap } from "../../Types/Jsonable"; +import { Settings } from "../../Settings/Settings"; +import { Player } from "@player"; + +interface SourceFileButtonRowProps { + sfNumber: number; + sfLevel: number; + sfActiveLevel: number; + callbacks: BitNodeAdvancedOptionsProps["callbacks"]; +} + +function SourceFileButtonRow({ + sfNumber, + sfLevel, + sfActiveLevel, + callbacks, +}: SourceFileButtonRowProps): React.ReactElement { + const title = `SF-${sfNumber}`; + const sourceFileLevelTool = + sfNumber !== 12 ? ( + [...Array(sfLevel + 1).keys()].map((level) => ( + + )) + ) : ( + // The usage of TextField instead of NumberInput is intentional. + { + // Empty string will be automatically changed to "0". + if (event.target.value === "") { + callbacks.setSfActiveLevel(sfNumber, 0); + return; + } + const level = Number.parseInt(event.target.value); + if (!Number.isFinite(level) || level < 0 || level > sfLevel) { + return; + } + callbacks.setSfActiveLevel(sfNumber, level); + }} + > + ); + const extraInfo = + sfNumber === 12 ? ( + + Max level: {sfLevel} + + ) : null; + + return ( + + + {title} + + {sourceFileLevelTool} + {extraInfo} + + ); +} + +function SourceFileOverrides({ + currentSourceFiles, + sourceFileOverrides, + callbacks, + getSfLevel, +}: { + currentSourceFiles: BitNodeAdvancedOptionsProps["currentSourceFiles"]; + sourceFileOverrides: BitNodeAdvancedOptionsProps["sourceFileOverrides"]; + callbacks: BitNodeAdvancedOptionsProps["callbacks"]; + getSfLevel: (sfNumber: number) => number; +}): React.ReactElement { + const firstSourceFile = React.useMemo( + () => (currentSourceFiles.size > 0 ? [...currentSourceFiles.keys()][0] : null), + [currentSourceFiles], + ); + const [selectElementValue, setSelectElementValue] = React.useState(firstSourceFile); + const getMenuItemList = (data: typeof sourceFileOverrides): number[] => { + return [...currentSourceFiles.keys()].filter((sfNumber) => ![...data.keys()].includes(sfNumber)); + }; + const menuItemList = getMenuItemList(sourceFileOverrides); + + React.useEffect(() => { + if (sourceFileOverrides.size === 0) { + setSelectElementValue(firstSourceFile); + } + }, [sourceFileOverrides, firstSourceFile]); + + const basicNote = `Changing the active level of a SF is temporary; you still permanently own that SF level. For example, if + you enter BN 1.3 while having SF 1.2 but with the active level set to 0, you will not get the bonuses from SF + 1.1 or SF 1.2, but you will still earn SF 1.3 when destroying the BN.`; + const note = currentSourceFiles.has(10) ? ( + <> + Note: +
    +
  • {basicNote}
  • +
  • + Changing the active level of SF 10 does not affect your current sleeves or the maximum number of sleeves. +
  • +
+ + ) : ( + <> + Note: {basicNote} +
+ + ); + + return ( + <> + Override active level of Source-File: +
+ {note} +
+ + + { + callbacks.setSfOverrides(new JSONMap()); + }} + buttonProps={{ sx: { marginLeft: "1rem" } }} + > + Remove all + +
+
+ {sourceFileOverrides.size > 0 && ( + <> + + + + + + + {[...sourceFileOverrides.keys()].map((sfNumber) => ( + + ))} + +
+ + Set all SF + + + {[0, 1, 2, 3].map((level) => ( + { + const newSfOverrides = new JSONMap(sourceFileOverrides); + for (const [sfNumber] of newSfOverrides) { + newSfOverrides.set(sfNumber, Math.min(level, getSfLevel(sfNumber))); + } + callbacks.setSfOverrides(newSfOverrides); + }} + buttonProps={{ sx: { marginRight: "0.5rem", minWidth: "40px" } }} + > + {level} + + ))} +
+
+ + )} + + ); +} + +function IntelligenceOverride({ + intelligenceOverride, + callbacks, +}: { + intelligenceOverride: BitNodeAdvancedOptionsProps["intelligenceOverride"]; + callbacks: BitNodeAdvancedOptionsProps["callbacks"]; +}): React.ReactElement { + const [enabled, setEnabled] = React.useState(false); + const [lastValueOfIntelligenceOverride, setLastValueOfIntelligenceOverride] = React.useState( + Player.skills.intelligence, + ); + return ( + { + setEnabled(value); + if (!value) { + // If this option is disabled, save last value and reset data. + setLastValueOfIntelligenceOverride(intelligenceOverride); + callbacks.setIntelligenceOverride(undefined); + return; + } else { + // If this option is enabled, load last value. + callbacks.setIntelligenceOverride(lastValueOfIntelligenceOverride); + } + }} + text={ + <> + + + Override Intelligence: + + { + // Empty string will be automatically changed to "0". + if (event.target.value === "") { + callbacks.setIntelligenceOverride(0); + return; + } + const value = Number.parseInt(event.target.value); + if (!Number.isInteger(value) || value < 0) { + return; + } + callbacks.setIntelligenceOverride(value); + }} + > + + + } + tooltip={ + <> + + The Intelligence bonuses for you and your Sleeves will be limited by this value. For example: +
    +
  • + If your Intelligence is 1000 and you set this value to 500, the "effective" Intelligence, which is used + for bonus calculation, is only 500. +
  • +
  • + If a Sleeve's Intelligence is 200 and you set this value to 500, the "effective" Intelligence, which is + used for bonus calculation, is still 200. +
  • +
+
+ + You will still gain Intelligence experience as normal. Intelligence Override only affects the Intelligence + bonus. + +
+ + The "effective" Intelligence will be shown in the character overview. If the effective value is different + from the original value, you can hover your mouse over it to see the original value. + + + } + /> + ); +} + +interface BitNodeAdvancedOptionsProps { + targetBitNode: number; + currentSourceFiles: Map; + sourceFileOverrides: JSONMap; + intelligenceOverride: number | undefined; + bitNodeBooleanOptions: BitNodeBooleanOptions; + callbacks: { + setSfOverrides: (value: JSONMap) => void; + setSfActiveLevel: (sfNumber: number, sfLevel: number) => void; + setIntelligenceOverride: (value: number | undefined) => void; + setBooleanOption: (key: keyof BitNodeBooleanOptions, value: boolean) => void; + }; +} + +export function BitNodeAdvancedOptions({ + targetBitNode, + currentSourceFiles, + sourceFileOverrides, + intelligenceOverride, + bitNodeBooleanOptions, + callbacks, +}: BitNodeAdvancedOptionsProps): React.ReactElement { + const [open, setOpen] = React.useState(false); + const getSfLevel = React.useCallback( + (sfNumber: number): number => { + return currentSourceFiles.get(sfNumber) ?? 0; + }, + [currentSourceFiles], + ); + + return ( + + setOpen((old) => !old)} sx={{ padding: "4px 8px" }}> + Advanced options} /> + {open ? : } + + + + { + callbacks.setBooleanOption("restrictHomePCUpgrade", value); + }} + text="Restrict max RAM and core of Home PC" + tooltip="The home computer's maximum RAM and number of cores are lower than normal. Max RAM: 128GB. Max core: 1." + /> + { + callbacks.setBooleanOption("disableGang", value); + }} + text="Disable Gang" + tooltip="Disable Gang" + /> + { + callbacks.setBooleanOption("disableCorporation", value); + }} + text="Disable Corporation" + tooltip="Disable Corporation" + /> + { + callbacks.setBooleanOption("disableBladeburner", value); + }} + text="Disable Bladeburner" + tooltip="Disable Bladeburner" + /> + { + callbacks.setBooleanOption("disable4SData", value); + }} + text="Disable 4S Market Data" + tooltip="Disable 4S Market Data" + /> + { + callbacks.setBooleanOption("disableHacknetServer", value); + }} + text="Disable Hacknet Server" + tooltip="Hacknet Node is re-enabled in place of Hacknet Server." + /> + { + callbacks.setBooleanOption("disableSleeveExpAndAugmentation", value); + }} + text="Disable Sleeves' experience and augmentation" + tooltip="Sleeves cannot gain experience or install augmentations" + /> + +
+ +
+
+
+ ); +} diff --git a/src/BitNode/ui/BitnodeMultipliersDescription.tsx b/src/BitNode/ui/BitnodeMultipliersDescription.tsx index d4b164f65..1cdf4e3dd 100644 --- a/src/BitNode/ui/BitnodeMultipliersDescription.tsx +++ b/src/BitNode/ui/BitnodeMultipliersDescription.tsx @@ -11,6 +11,7 @@ import { StatsRow } from "../../ui/React/StatsRow"; import { defaultMultipliers, getBitNodeMultipliers } from "../BitNode"; import { BitNodeMultipliers } from "../BitNodeMultipliers"; import { PartialRecord, getRecordEntries } from "../../Types/Record"; +import { canAccessBitNodeFeature } from "../BitNodeUtils"; interface IProps { n: number; @@ -23,7 +24,7 @@ export function BitnodeMultiplierDescription({ n, level }: IProps): React.ReactE return ( - setOpen((old) => !old)}> + setOpen((old) => !old)} sx={{ padding: "4px 8px" }}> Bitnode Multipliers} /> {open ? : } @@ -39,8 +40,8 @@ export const BitNodeMultipliersDisplay = ({ n, level }: IProps): React.ReactElem // If not, then we have to assume that we want the next level up from the // current node's source file, so we get the min of that, the SF's max level, // or if it's BN12, ∞ - const maxSfLevel = n === 12 ? Infinity : 3; - const mults = getBitNodeMultipliers(n, level ?? Math.min(Player.sourceFileLvl(n) + 1, maxSfLevel)); + const maxSfLevel = n === 12 ? Number.MAX_VALUE : 3; + const mults = getBitNodeMultipliers(n, level ?? Math.min(Player.activeSourceFileLvl(n) + 1, maxSfLevel)); return ( @@ -314,7 +315,7 @@ function StanekMults({ mults }: IMultsProps): React.ReactElement { } function GangMults({ mults }: IMultsProps): React.ReactElement { - if (Player.bitNodeN !== 2 && Player.sourceFileLvl(2) <= 0) return <>; + if (!canAccessBitNodeFeature(2)) return <>; const rows: IBNMultRows = { GangSoftcap: { diff --git a/src/BitNode/ui/BitverseRoot.tsx b/src/BitNode/ui/BitverseRoot.tsx index 92d2eae11..3b5d0dad6 100644 --- a/src/BitNode/ui/BitverseRoot.tsx +++ b/src/BitNode/ui/BitverseRoot.tsx @@ -172,7 +172,7 @@ export function BitverseRoot(props: IProps): React.ReactElement { if (n !== destroyed) { return lvl; } - const max = n === 12 ? Infinity : 3; + const max = n === 12 ? Number.MAX_VALUE : 3; // If accessed via flume, display the current BN level, else the next return Math.min(max, lvl + Number(!props.flume)); diff --git a/src/BitNode/ui/PortalModal.tsx b/src/BitNode/ui/PortalModal.tsx index 8ff8956d9..2f4b435f1 100644 --- a/src/BitNode/ui/PortalModal.tsx +++ b/src/BitNode/ui/PortalModal.tsx @@ -1,11 +1,15 @@ import React from "react"; +import { Player } from "@player"; +import { type BitNodeBooleanOptions } from "@nsdefs"; import { enterBitNode } from "../../RedPill"; import { BitNodes } from "../BitNode"; import { Modal } from "../../ui/React/Modal"; import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; import { BitnodeMultiplierDescription } from "./BitnodeMultipliersDescription"; +import { BitNodeAdvancedOptions } from "./BitNodeAdvancedOptions"; +import { JSONMap } from "../../Types/Jsonable"; interface IProps { open: boolean; @@ -17,14 +21,77 @@ interface IProps { } export function PortalModal(props: IProps): React.ReactElement { + const [sourceFileOverrides, setSourceFileOverrides] = React.useState>(new JSONMap()); + const [intelligenceOverride, setIntelligenceOverride] = React.useState(); + const [bitNodeBooleanOptions, setBitNodeBooleanOptions] = React.useState({ + restrictHomePCUpgrade: false, + disableGang: false, + disableCorporation: false, + disableBladeburner: false, + disable4SData: false, + disableHacknetServer: false, + disableSleeveExpAndAugmentation: false, + }); + const bitNodeKey = "BitNode" + props.n; const bitNode = BitNodes[bitNodeKey]; if (bitNode == null) throw new Error(`Could not find BitNode object for number: ${props.n}`); const maxSourceFileLevel = props.n === 12 ? "∞" : "3"; + const newLevel = Math.min(props.level + 1, props.n === 12 ? Number.MAX_VALUE : 3); + + let currentSourceFiles = new Map(Player.sourceFiles); + if (!props.flume) { + const currentSourceFileLevel = Player.sourceFileLvl(props.destroyedBitNode); + if (currentSourceFileLevel < 3 || props.destroyedBitNode === 12) { + currentSourceFiles.set(props.destroyedBitNode, currentSourceFileLevel + 1); + } + } + currentSourceFiles = new Map([...currentSourceFiles].sort((a, b) => a[0] - b[0])); + + const callbacks = { + setSfOverrides: (value: JSONMap) => { + setSourceFileOverrides(value); + }, + setSfActiveLevel: (sfNumber: number, sfLevel: number) => { + setSourceFileOverrides((old) => { + const newValue = new JSONMap(old); + newValue.set(sfNumber, sfLevel); + return newValue; + }); + }, + setIntelligenceOverride: (value: number | undefined) => { + setIntelligenceOverride(value); + }, + setBooleanOption: (key: keyof BitNodeBooleanOptions, value: boolean) => { + if (!(key in bitNodeBooleanOptions)) { + throw new Error(`Invalid key of booleanOptions: ${key}`); + } + setBitNodeBooleanOptions((old) => { + return { + ...old, + [key]: value, + }; + }); + }, + }; + + function onClose() { + setSourceFileOverrides(new JSONMap()); + setIntelligenceOverride(undefined); + setBitNodeBooleanOptions({ + restrictHomePCUpgrade: false, + disableGang: false, + disableCorporation: false, + disableBladeburner: false, + disable4SData: false, + disableHacknetServer: false, + disableSleeveExpAndAugmentation: false, + }); + props.onClose(); + } - const newLevel = Math.min(props.level + 1, props.n === 12 ? Infinity : 3); return ( - + BitNode-{props.n}: {bitNode.name} @@ -39,12 +106,25 @@ export function PortalModal(props: IProps): React.ReactElement {
{bitNode.info} +