mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-12-18 12:15:44 +01:00
FEATURE: BitNode options (#1411)
This commit is contained in:
parent
0e1e8a9862
commit
783120c886
11
markdown/bitburner.bitnodebooleanoptions.disable4sdata.md
Normal file
11
markdown/bitburner.bitnodebooleanoptions.disable4sdata.md
Normal file
@ -0,0 +1,11 @@
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [bitburner](./bitburner.md) > [BitNodeBooleanOptions](./bitburner.bitnodebooleanoptions.md) > [disable4SData](./bitburner.bitnodebooleanoptions.disable4sdata.md)
|
||||
|
||||
## BitNodeBooleanOptions.disable4SData property
|
||||
|
||||
**Signature:**
|
||||
|
||||
```typescript
|
||||
disable4SData: boolean;
|
||||
```
|
@ -0,0 +1,11 @@
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [bitburner](./bitburner.md) > [BitNodeBooleanOptions](./bitburner.bitnodebooleanoptions.md) > [disableBladeburner](./bitburner.bitnodebooleanoptions.disablebladeburner.md)
|
||||
|
||||
## BitNodeBooleanOptions.disableBladeburner property
|
||||
|
||||
**Signature:**
|
||||
|
||||
```typescript
|
||||
disableBladeburner: boolean;
|
||||
```
|
@ -0,0 +1,11 @@
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [bitburner](./bitburner.md) > [BitNodeBooleanOptions](./bitburner.bitnodebooleanoptions.md) > [disableCorporation](./bitburner.bitnodebooleanoptions.disablecorporation.md)
|
||||
|
||||
## BitNodeBooleanOptions.disableCorporation property
|
||||
|
||||
**Signature:**
|
||||
|
||||
```typescript
|
||||
disableCorporation: boolean;
|
||||
```
|
11
markdown/bitburner.bitnodebooleanoptions.disablegang.md
Normal file
11
markdown/bitburner.bitnodebooleanoptions.disablegang.md
Normal file
@ -0,0 +1,11 @@
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [bitburner](./bitburner.md) > [BitNodeBooleanOptions](./bitburner.bitnodebooleanoptions.md) > [disableGang](./bitburner.bitnodebooleanoptions.disablegang.md)
|
||||
|
||||
## BitNodeBooleanOptions.disableGang property
|
||||
|
||||
**Signature:**
|
||||
|
||||
```typescript
|
||||
disableGang: boolean;
|
||||
```
|
@ -0,0 +1,11 @@
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [bitburner](./bitburner.md) > [BitNodeBooleanOptions](./bitburner.bitnodebooleanoptions.md) > [disableHacknetServer](./bitburner.bitnodebooleanoptions.disablehacknetserver.md)
|
||||
|
||||
## BitNodeBooleanOptions.disableHacknetServer property
|
||||
|
||||
**Signature:**
|
||||
|
||||
```typescript
|
||||
disableHacknetServer: boolean;
|
||||
```
|
@ -0,0 +1,11 @@
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [bitburner](./bitburner.md) > [BitNodeBooleanOptions](./bitburner.bitnodebooleanoptions.md) > [disableSleeveExpAndAugmentation](./bitburner.bitnodebooleanoptions.disablesleeveexpandaugmentation.md)
|
||||
|
||||
## BitNodeBooleanOptions.disableSleeveExpAndAugmentation property
|
||||
|
||||
**Signature:**
|
||||
|
||||
```typescript
|
||||
disableSleeveExpAndAugmentation: boolean;
|
||||
```
|
28
markdown/bitburner.bitnodebooleanoptions.md
Normal file
28
markdown/bitburner.bitnodebooleanoptions.md
Normal file
@ -0,0 +1,28 @@
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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 | |
|
||||
|
@ -0,0 +1,11 @@
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [bitburner](./bitburner.md) > [BitNodeBooleanOptions](./bitburner.bitnodebooleanoptions.md) > [restrictHomePCUpgrade](./bitburner.bitnodebooleanoptions.restricthomepcupgrade.md)
|
||||
|
||||
## BitNodeBooleanOptions.restrictHomePCUpgrade property
|
||||
|
||||
**Signature:**
|
||||
|
||||
```typescript
|
||||
restrictHomePCUpgrade: boolean;
|
||||
```
|
11
markdown/bitburner.bitnodeoptions.intelligenceoverride.md
Normal file
11
markdown/bitburner.bitnodeoptions.intelligenceoverride.md
Normal file
@ -0,0 +1,11 @@
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [bitburner](./bitburner.md) > [BitNodeOptions](./bitburner.bitnodeoptions.md) > [intelligenceOverride](./bitburner.bitnodeoptions.intelligenceoverride.md)
|
||||
|
||||
## BitNodeOptions.intelligenceOverride property
|
||||
|
||||
**Signature:**
|
||||
|
||||
```typescript
|
||||
intelligenceOverride: number | undefined;
|
||||
```
|
24
markdown/bitburner.bitnodeoptions.md
Normal file
24
markdown/bitburner.bitnodeoptions.md
Normal file
@ -0,0 +1,24 @@
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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> | |
|
||||
|
11
markdown/bitburner.bitnodeoptions.sourcefileoverrides.md
Normal file
11
markdown/bitburner.bitnodeoptions.sourcefileoverrides.md
Normal file
@ -0,0 +1,11 @@
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [bitburner](./bitburner.md) > [BitNodeOptions](./bitburner.bitnodeoptions.md) > [sourceFileOverrides](./bitburner.bitnodeoptions.sourcefileoverrides.md)
|
||||
|
||||
## BitNodeOptions.sourceFileOverrides property
|
||||
|
||||
**Signature:**
|
||||
|
||||
```typescript
|
||||
sourceFileOverrides: Map<number, number>;
|
||||
```
|
@ -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) | <p>restrictHomePCUpgrade: The home computer's maximum RAM and number of cores are lower than normal. Max RAM: 128GB. Max core: 1.</p><p>disableSleeveExpAndAugmentation: Your Sleeves do not gain experience when they perform action. You also cannot buy augmentations for them.</p> |
|
||||
| [BitNodeMultipliers](./bitburner.bitnodemultipliers.md) | All multipliers affecting the difficulty of the current challenge. |
|
||||
| [BitNodeOptions](./bitburner.bitnodeoptions.md) | <p>Default value: - sourceFileOverrides: an empty Map - intelligenceOverride: undefined - All boolean options: false</p><p>If you specify intelligenceOverride, it must be a non-negative integer.</p> |
|
||||
| [BitNodeRequirement](./bitburner.bitnoderequirement.md) | Player must be located in this BitNode. |
|
||||
| [Bladeburner](./bitburner.bladeburner.md) | Bladeburner API |
|
||||
| [BladeburnerCurAction](./bitburner.bladeburnercuraction.md) | Bladeburner current action. |
|
||||
|
13
markdown/bitburner.resetinfo.bitnodeoptions.md
Normal file
13
markdown/bitburner.resetinfo.bitnodeoptions.md
Normal file
@ -0,0 +1,13 @@
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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;
|
||||
```
|
@ -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 |
|
||||
|
@ -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:**
|
||||
|
||||
|
@ -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:**
|
||||
|
||||
|
@ -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. |
|
||||
|
@ -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 = (<AchievementDataJson>(<unknown>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<string, Achievement> {
|
||||
const achs: Record<string, Achievement> = {};
|
||||
for (let i = 1; i <= 12; i++) {
|
||||
@ -75,7 +67,7 @@ function sfAchievements(): Record<string, Achievement> {
|
||||
achs[ID] = {
|
||||
...achievementData[ID],
|
||||
Icon: ID,
|
||||
Visible: knowsAboutBitverse,
|
||||
Visible: knowAboutBitverse,
|
||||
Condition: () => Player.sourceFileLvl(i) >= 1,
|
||||
};
|
||||
}
|
||||
@ -498,7 +490,7 @@ export const achievements: Record<string, Achievement> = {
|
||||
INDECISIVE: {
|
||||
...achievementData.INDECISIVE,
|
||||
Icon: "1H",
|
||||
Visible: knowsAboutBitverse,
|
||||
Visible: knowAboutBitverse,
|
||||
Condition: (function () {
|
||||
let c = 0;
|
||||
setInterval(() => {
|
||||
@ -514,13 +506,13 @@ export const achievements: Record<string, Achievement> = {
|
||||
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() &&
|
||||
|
@ -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);
|
||||
|
@ -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) => {
|
||||
|
@ -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 <Typography color={props.color}>{customFormatPercent(props.base)}</Typography>;
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
<br />
|
||||
<br />
|
||||
It increases all of the player's multipliers by 0.1%
|
||||
<br />
|
||||
<br />
|
||||
You have found the following exploits:
|
||||
<br />
|
||||
<br />
|
||||
{Player.exploits.map((c) => (
|
||||
<React.Fragment key={c}>
|
||||
* {ExploitName(c)}
|
||||
<br />
|
||||
</React.Fragment>
|
||||
))}
|
||||
<ul>
|
||||
{Player.exploits.map((c) => (
|
||||
<li key={c}>
|
||||
{c}: {ExploitDescription[c]}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
),
|
||||
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<SourceFileData | null>(() => {
|
||||
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 (
|
||||
<ListItemButton
|
||||
key={i + 1}
|
||||
onClick={() => setSelectedSf({ n, lvl })}
|
||||
selected={selectedSf.n === n}
|
||||
onClick={() => setSelectedSfData(sourceFileData)}
|
||||
selected={selectedSfData.n === sourceFileData.n}
|
||||
sx={{ py: 0 }}
|
||||
>
|
||||
<ListItemText
|
||||
disableTypography
|
||||
primary={<Typography>{sfObj.name}</Typography>}
|
||||
primary={<Typography>{sourceFileData.name}</Typography>}
|
||||
secondary={
|
||||
<Typography>
|
||||
Level {lvl} / {maxLevel}
|
||||
</Typography>
|
||||
<>
|
||||
<Typography>
|
||||
Level: {sourceFileData.level} / {sourceFileData.maxLevel}
|
||||
</Typography>
|
||||
{sourceFileData.activeLevel < sourceFileData.level && (
|
||||
<Typography>Active level: {sourceFileData.activeLevel}</Typography>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</ListItemButton>
|
||||
@ -131,28 +139,25 @@ export function SourceFilesElement(): React.ReactElement {
|
||||
})}
|
||||
</List>
|
||||
</Box>
|
||||
<Box sx={{ m: 1 }}>
|
||||
<Typography variant="h6" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
|
||||
{safeGetSf(selectedSf.n)?.name}
|
||||
</Typography>
|
||||
<Typography component="div" sx={{ maxHeight: 350, overflowY: "scroll" }}>
|
||||
{(() => {
|
||||
const sfObj = safeGetSf(selectedSf.n);
|
||||
if (!sfObj) return;
|
||||
|
||||
const maxLevel = getMaxLevel(sfObj);
|
||||
|
||||
return (
|
||||
{selectedSfData !== null && (
|
||||
<Box sx={{ m: 1 }}>
|
||||
<Typography variant="h6" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
|
||||
{selectedSfData.name}
|
||||
</Typography>
|
||||
<Typography component="div" sx={{ maxHeight: 350, overflowY: "scroll" }}>
|
||||
Level: {selectedSfData.level} / {selectedSfData.maxLevel}
|
||||
<br />
|
||||
{selectedSfData.activeLevel < selectedSfData.level && (
|
||||
<>
|
||||
Level {selectedSf.lvl} / {maxLevel}
|
||||
Active level: {selectedSfData.activeLevel}
|
||||
<br />
|
||||
<br />
|
||||
{sfObj.info}
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<br />
|
||||
{selectedSfData.info}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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<number, number>(),
|
||||
intelligenceOverride: undefined,
|
||||
restrictHomePCUpgrade: false,
|
||||
disableGang: false,
|
||||
disableCorporation: false,
|
||||
disableBladeburner: false,
|
||||
disable4SData: false,
|
||||
disableHacknetServer: false,
|
||||
disableSleeveExpAndAugmentation: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function validateSourceFileOverrides(
|
||||
sourceFileOverrides: Map<number, number>,
|
||||
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);
|
||||
}
|
||||
|
433
src/BitNode/ui/BitNodeAdvancedOptions.tsx
Normal file
433
src/BitNode/ui/BitNodeAdvancedOptions.tsx
Normal file
@ -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) => (
|
||||
<Button
|
||||
key={level}
|
||||
onClick={() => {
|
||||
callbacks.setSfActiveLevel(sfNumber, level);
|
||||
}}
|
||||
sx={{
|
||||
marginRight: "0.5rem !important",
|
||||
border: level === sfActiveLevel ? `1px solid ${Settings.theme.info}` : "",
|
||||
minWidth: "40px",
|
||||
}}
|
||||
>
|
||||
{level}
|
||||
</Button>
|
||||
))
|
||||
) : (
|
||||
// The usage of TextField instead of NumberInput is intentional.
|
||||
<TextField
|
||||
sx={{ maxWidth: "185px" }}
|
||||
value={sfActiveLevel}
|
||||
onChange={(event) => {
|
||||
// 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);
|
||||
}}
|
||||
></TextField>
|
||||
);
|
||||
const extraInfo =
|
||||
sfNumber === 12 ? (
|
||||
<td>
|
||||
<Typography marginLeft="1rem">Max level: {sfLevel}</Typography>
|
||||
</td>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td>
|
||||
<Typography>{title}</Typography>
|
||||
</td>
|
||||
<td>{sourceFileLevelTool}</td>
|
||||
{extraInfo}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
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<number | null>(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) ? (
|
||||
<>
|
||||
<Typography>Note:</Typography>
|
||||
<ul style={{ marginTop: 0 }}>
|
||||
<li>{basicNote}</li>
|
||||
<li>
|
||||
Changing the active level of SF 10 does not affect your current sleeves or the maximum number of sleeves.
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Typography>Note: {basicNote}</Typography>
|
||||
<br />
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography>Override active level of Source-File:</Typography>
|
||||
<br />
|
||||
<Typography component="div">{note}</Typography>
|
||||
<div>
|
||||
<Select
|
||||
disabled={menuItemList.length === 0}
|
||||
value={selectElementValue ?? ""}
|
||||
onChange={(event) => {
|
||||
setSelectElementValue(Number(event.target.value));
|
||||
}}
|
||||
sx={{ minWidth: "80px" }}
|
||||
>
|
||||
{menuItemList.map((sfNumber) => (
|
||||
<MenuItem key={sfNumber} value={sfNumber}>
|
||||
SF-{sfNumber}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<Button
|
||||
disabled={menuItemList.length === 0}
|
||||
onClick={() => {
|
||||
if (!selectElementValue) {
|
||||
return;
|
||||
}
|
||||
const newSfOverrides = new JSONMap(sourceFileOverrides);
|
||||
newSfOverrides.set(selectElementValue, getSfLevel(selectElementValue));
|
||||
const newMenuItemList = getMenuItemList(newSfOverrides);
|
||||
if (newMenuItemList.length > 0) {
|
||||
setSelectElementValue(newMenuItemList[0]);
|
||||
} else {
|
||||
setSelectElementValue(null);
|
||||
}
|
||||
callbacks.setSfOverrides(newSfOverrides);
|
||||
}}
|
||||
sx={{ marginLeft: "1rem" }}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
<ButtonWithTooltip
|
||||
normalTooltip="Remove all overridden SF"
|
||||
disabledTooltip={sourceFileOverrides.size === 0 ? "No overridden SF" : ""}
|
||||
onClick={() => {
|
||||
callbacks.setSfOverrides(new JSONMap());
|
||||
}}
|
||||
buttonProps={{ sx: { marginLeft: "1rem" } }}
|
||||
>
|
||||
Remove all
|
||||
</ButtonWithTooltip>
|
||||
</div>
|
||||
<br />
|
||||
{sourceFileOverrides.size > 0 && (
|
||||
<>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<Tooltip title="Set active level for all chosen SF">
|
||||
<Typography minWidth="7rem">Set all SF</Typography>
|
||||
</Tooltip>
|
||||
</td>
|
||||
<td>
|
||||
{[0, 1, 2, 3].map((level) => (
|
||||
<ButtonWithTooltip
|
||||
key={level}
|
||||
onClick={() => {
|
||||
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}
|
||||
</ButtonWithTooltip>
|
||||
))}
|
||||
</td>
|
||||
</tr>
|
||||
{[...sourceFileOverrides.keys()].map((sfNumber) => (
|
||||
<SourceFileButtonRow
|
||||
key={sfNumber}
|
||||
sfNumber={sfNumber}
|
||||
sfLevel={getSfLevel(sfNumber)}
|
||||
sfActiveLevel={sourceFileOverrides.get(sfNumber) ?? 0}
|
||||
callbacks={callbacks}
|
||||
></SourceFileButtonRow>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function IntelligenceOverride({
|
||||
intelligenceOverride,
|
||||
callbacks,
|
||||
}: {
|
||||
intelligenceOverride: BitNodeAdvancedOptionsProps["intelligenceOverride"];
|
||||
callbacks: BitNodeAdvancedOptionsProps["callbacks"];
|
||||
}): React.ReactElement {
|
||||
const [enabled, setEnabled] = React.useState<boolean>(false);
|
||||
const [lastValueOfIntelligenceOverride, setLastValueOfIntelligenceOverride] = React.useState<number | undefined>(
|
||||
Player.skills.intelligence,
|
||||
);
|
||||
return (
|
||||
<OptionSwitch
|
||||
disabled={Player.skills.intelligence <= 0}
|
||||
checked={false}
|
||||
onChange={(value) => {
|
||||
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={
|
||||
<>
|
||||
<Typography component="div" display="flex" gap="1rem">
|
||||
<Typography display="flex" alignItems="center">
|
||||
Override Intelligence:
|
||||
</Typography>
|
||||
<TextField
|
||||
sx={{ maxWidth: "4rem" }}
|
||||
disabled={!enabled}
|
||||
value={intelligenceOverride !== undefined ? intelligenceOverride : ""}
|
||||
onChange={(event) => {
|
||||
// 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);
|
||||
}}
|
||||
></TextField>
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
tooltip={
|
||||
<>
|
||||
<Typography component="div">
|
||||
The Intelligence bonuses for you and your Sleeves will be limited by this value. For example:
|
||||
<ul>
|
||||
<li>
|
||||
If your Intelligence is 1000 and you set this value to 500, the "effective" Intelligence, which is used
|
||||
for bonus calculation, is only 500.
|
||||
</li>
|
||||
<li>
|
||||
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.
|
||||
</li>
|
||||
</ul>
|
||||
</Typography>
|
||||
<Typography>
|
||||
You will still gain Intelligence experience as normal. Intelligence Override only affects the Intelligence
|
||||
bonus.
|
||||
</Typography>
|
||||
<br />
|
||||
<Typography>
|
||||
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.
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
interface BitNodeAdvancedOptionsProps {
|
||||
targetBitNode: number;
|
||||
currentSourceFiles: Map<number, number>;
|
||||
sourceFileOverrides: JSONMap<number, number>;
|
||||
intelligenceOverride: number | undefined;
|
||||
bitNodeBooleanOptions: BitNodeBooleanOptions;
|
||||
callbacks: {
|
||||
setSfOverrides: (value: JSONMap<number, number>) => 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 (
|
||||
<Box component={Paper} sx={{ mt: 1, p: 1 }}>
|
||||
<ListItemButton disableGutters onClick={() => setOpen((old) => !old)} sx={{ padding: "4px 8px" }}>
|
||||
<ListItemText primary={<Typography variant="h6">Advanced options</Typography>} />
|
||||
{open ? <ExpandLess color="primary" /> : <ExpandMore color="primary" />}
|
||||
</ListItemButton>
|
||||
<Collapse in={open}>
|
||||
<Box sx={{ padding: "0 1rem" }}>
|
||||
<OptionSwitch
|
||||
checked={bitNodeBooleanOptions.restrictHomePCUpgrade}
|
||||
onChange={(value) => {
|
||||
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."
|
||||
/>
|
||||
<OptionSwitch
|
||||
disabled={getSfLevel(2) === 0 && targetBitNode !== 2}
|
||||
checked={bitNodeBooleanOptions.disableGang}
|
||||
onChange={(value) => {
|
||||
callbacks.setBooleanOption("disableGang", value);
|
||||
}}
|
||||
text="Disable Gang"
|
||||
tooltip="Disable Gang"
|
||||
/>
|
||||
<OptionSwitch
|
||||
disabled={getSfLevel(3) === 0 && targetBitNode !== 3}
|
||||
checked={bitNodeBooleanOptions.disableCorporation}
|
||||
onChange={(value) => {
|
||||
callbacks.setBooleanOption("disableCorporation", value);
|
||||
}}
|
||||
text="Disable Corporation"
|
||||
tooltip="Disable Corporation"
|
||||
/>
|
||||
<OptionSwitch
|
||||
disabled={getSfLevel(6) === 0 && getSfLevel(7) === 0 && targetBitNode !== 6 && targetBitNode !== 7}
|
||||
checked={bitNodeBooleanOptions.disableBladeburner}
|
||||
onChange={(value) => {
|
||||
callbacks.setBooleanOption("disableBladeburner", value);
|
||||
}}
|
||||
text="Disable Bladeburner"
|
||||
tooltip="Disable Bladeburner"
|
||||
/>
|
||||
<OptionSwitch
|
||||
checked={bitNodeBooleanOptions.disable4SData}
|
||||
onChange={(value) => {
|
||||
callbacks.setBooleanOption("disable4SData", value);
|
||||
}}
|
||||
text="Disable 4S Market Data"
|
||||
tooltip="Disable 4S Market Data"
|
||||
/>
|
||||
<OptionSwitch
|
||||
disabled={getSfLevel(9) === 0 && targetBitNode !== 9}
|
||||
checked={bitNodeBooleanOptions.disableHacknetServer}
|
||||
onChange={(value) => {
|
||||
callbacks.setBooleanOption("disableHacknetServer", value);
|
||||
}}
|
||||
text="Disable Hacknet Server"
|
||||
tooltip="Hacknet Node is re-enabled in place of Hacknet Server."
|
||||
/>
|
||||
<OptionSwitch
|
||||
disabled={getSfLevel(10) === 0 && targetBitNode !== 10}
|
||||
checked={bitNodeBooleanOptions.disableSleeveExpAndAugmentation}
|
||||
onChange={(value) => {
|
||||
callbacks.setBooleanOption("disableSleeveExpAndAugmentation", value);
|
||||
}}
|
||||
text="Disable Sleeves' experience and augmentation"
|
||||
tooltip="Sleeves cannot gain experience or install augmentations"
|
||||
/>
|
||||
<IntelligenceOverride
|
||||
intelligenceOverride={intelligenceOverride}
|
||||
callbacks={callbacks}
|
||||
></IntelligenceOverride>
|
||||
<br />
|
||||
<SourceFileOverrides
|
||||
currentSourceFiles={currentSourceFiles}
|
||||
sourceFileOverrides={sourceFileOverrides}
|
||||
callbacks={callbacks}
|
||||
getSfLevel={getSfLevel}
|
||||
></SourceFileOverrides>
|
||||
</Box>
|
||||
</Collapse>
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -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 (
|
||||
<Box component={Paper} sx={{ mt: 1, p: 1 }}>
|
||||
<ListItemButton disableGutters onClick={() => setOpen((old) => !old)}>
|
||||
<ListItemButton disableGutters onClick={() => setOpen((old) => !old)} sx={{ padding: "4px 8px" }}>
|
||||
<ListItemText primary={<Typography variant="h6">Bitnode Multipliers</Typography>} />
|
||||
{open ? <ExpandLess color="primary" /> : <ExpandMore color="primary" />}
|
||||
</ListItemButton>
|
||||
@ -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 (
|
||||
<Box sx={{ columnCount: 2, columnGap: 1, mb: n === 1 ? 0 : -2 }}>
|
||||
@ -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: {
|
||||
|
@ -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));
|
||||
|
@ -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<JSONMap<number, number>>(new JSONMap());
|
||||
const [intelligenceOverride, setIntelligenceOverride] = React.useState<number | undefined>();
|
||||
const [bitNodeBooleanOptions, setBitNodeBooleanOptions] = React.useState<BitNodeBooleanOptions>({
|
||||
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<number, number>) => {
|
||||
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 (
|
||||
<Modal open={props.open} onClose={props.onClose}>
|
||||
<Modal open={props.open} onClose={onClose}>
|
||||
<Typography variant="h4">
|
||||
BitNode-{props.n}: {bitNode.name}
|
||||
</Typography>
|
||||
@ -39,12 +106,25 @@ export function PortalModal(props: IProps): React.ReactElement {
|
||||
<br />
|
||||
<Typography component="div">{bitNode.info}</Typography>
|
||||
<BitnodeMultiplierDescription n={props.n} level={newLevel} />
|
||||
<BitNodeAdvancedOptions
|
||||
targetBitNode={props.n}
|
||||
currentSourceFiles={currentSourceFiles}
|
||||
sourceFileOverrides={sourceFileOverrides}
|
||||
intelligenceOverride={intelligenceOverride}
|
||||
bitNodeBooleanOptions={bitNodeBooleanOptions}
|
||||
callbacks={callbacks}
|
||||
></BitNodeAdvancedOptions>
|
||||
<br />
|
||||
<Button
|
||||
aria-label={`enter-bitnode-${bitNode.number.toString()}`}
|
||||
autoFocus={true}
|
||||
onClick={() => {
|
||||
enterBitNode(props.flume, props.destroyedBitNode, props.n);
|
||||
const bitNodeOptions = {
|
||||
sourceFileOverrides,
|
||||
intelligenceOverride,
|
||||
...bitNodeBooleanOptions,
|
||||
};
|
||||
enterBitNode(props.flume, props.destroyedBitNode, props.n, bitNodeOptions);
|
||||
props.onClose();
|
||||
}}
|
||||
>
|
||||
|
@ -23,7 +23,7 @@ export class StaneksGift extends BaseGift {
|
||||
}
|
||||
|
||||
baseSize(): number {
|
||||
return StanekConstants.BaseSize + currentNodeMults.StaneksGiftExtraSize + Player.sourceFileLvl(13);
|
||||
return StanekConstants.BaseSize + currentNodeMults.StaneksGiftExtraSize + Player.activeSourceFileLvl(13);
|
||||
}
|
||||
|
||||
width(): number {
|
||||
|
@ -32,12 +32,18 @@ export function SourceFilesDev({ parentRerender }: { parentRerender: () => void
|
||||
}
|
||||
if (sfLvl === 0) {
|
||||
Player.sourceFiles.delete(sfN);
|
||||
if (sfN === 10) Sleeve.recalculateNumOwned();
|
||||
Player.bitNodeOptions.sourceFileOverrides.delete(sfN);
|
||||
if (sfN === 10) {
|
||||
Sleeve.recalculateNumOwned();
|
||||
}
|
||||
parentRerender();
|
||||
return;
|
||||
}
|
||||
Player.sourceFiles.set(sfN, sfLvl);
|
||||
if (sfN === 10) Sleeve.recalculateNumOwned();
|
||||
Player.bitNodeOptions.sourceFileOverrides.set(sfN, sfLvl);
|
||||
if (sfN === 10) {
|
||||
Sleeve.recalculateNumOwned();
|
||||
}
|
||||
parentRerender();
|
||||
},
|
||||
[parentRerender],
|
||||
@ -113,6 +119,8 @@ export function SourceFilesDev({ parentRerender }: { parentRerender: () => void
|
||||
<Typography>Source-Files</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Typography>Note: This tool sets both the owned level and the overridden level.</Typography>
|
||||
<br />
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
|
@ -29,24 +29,20 @@ export enum Exploit {
|
||||
EditSaveFile = "EditSaveFile",
|
||||
}
|
||||
|
||||
const names: Record<Exploit, string> = {
|
||||
Bypass: "by circumventing the ram cost of document.",
|
||||
EditSaveFile: "by editing your save file.",
|
||||
PrototypeTampering: "by tampering with Numbers prototype.",
|
||||
TimeCompression: "by compressing time.",
|
||||
Unclickable: "by clicking the unclickable.",
|
||||
UndocumentedFunctionCall: "by looking beyond the documentation.",
|
||||
RealityAlteration: "by altering reality to suit your whims.",
|
||||
N00dles: "by harnessing the power of the n00dles.",
|
||||
YoureNotMeantToAccessThis: "by accessing the dev menu.",
|
||||
TrueRecursion: "by truly recursing.",
|
||||
INeedARainbow: "by using the power of the rainbow.",
|
||||
export const ExploitDescription: Record<Exploit, string> = {
|
||||
[Exploit.Bypass]: "by circumventing the ram cost of document.",
|
||||
[Exploit.EditSaveFile]: "by editing your save file.",
|
||||
[Exploit.PrototypeTampering]: "by tampering with Numbers prototype.",
|
||||
[Exploit.TimeCompression]: "by compressing time.",
|
||||
[Exploit.Unclickable]: "by clicking the unclickable.",
|
||||
[Exploit.UndocumentedFunctionCall]: "by looking beyond the documentation.",
|
||||
[Exploit.RealityAlteration]: "by altering reality to suit your whims.",
|
||||
[Exploit.N00dles]: "by harnessing the power of the n00dles.",
|
||||
[Exploit.YoureNotMeantToAccessThis]: "by accessing the dev menu.",
|
||||
[Exploit.TrueRecursion]: "by truly recursing.",
|
||||
[Exploit.INeedARainbow]: "by using the power of the rainbow.",
|
||||
};
|
||||
|
||||
export function ExploitName(exploit: Exploit): string {
|
||||
return names[exploit];
|
||||
}
|
||||
|
||||
// Needed in case player edits save file poorly
|
||||
export function sanitizeExploits(exploits: Exploit[]): Exploit[] {
|
||||
exploits = exploits.filter((e: Exploit) => Object.values(Exploit).includes(e));
|
||||
|
@ -149,7 +149,7 @@ export const getFactionAugmentationsFiltered = (faction: Faction): AugmentationN
|
||||
augs.push(Augmentations[AugmentationName.TheRedPill]);
|
||||
}
|
||||
|
||||
const rng = SFC32RNG(`BN${Player.bitNodeN}.${Player.sourceFileLvl(Player.bitNodeN)}`);
|
||||
const rng = SFC32RNG(`BN${Player.bitNodeN}.${Player.activeSourceFileLvl(Player.bitNodeN)}`);
|
||||
// Remove faction-unique augs that don't belong to this faction
|
||||
const uniqueFilter = (a: Augmentation): boolean => {
|
||||
// Keep all the non-unique one
|
||||
|
@ -302,7 +302,7 @@ export const haveSourceFile = (n: number): PlayerCondition => ({
|
||||
};
|
||||
},
|
||||
isSatisfied(p: PlayerObject): boolean {
|
||||
return p.bitNodeN == n || p.sourceFileLvl(n) > 0;
|
||||
return p.bitNodeN == n || p.activeSourceFileLvl(n) > 0;
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -60,7 +60,7 @@ export const AutoexecInput = (props: IProps): React.ReactElement => {
|
||||
);
|
||||
}
|
||||
// Stolen from Prestige.ts
|
||||
const minRam = Player.sourceFileLvl(9) >= 2 ? 128 : Player.sourceFileLvl(1) > 0 ? 32 : 8;
|
||||
const minRam = Player.activeSourceFileLvl(9) >= 2 ? 128 : Player.activeSourceFileLvl(1) > 0 ? 32 : 8;
|
||||
if (ramUsage <= minRam) {
|
||||
return (
|
||||
<Tooltip
|
||||
|
@ -829,5 +829,5 @@ function waitCycle(useOfflineCycles = true): Promise<void> {
|
||||
}
|
||||
|
||||
export function showWorldDemon() {
|
||||
return Player.hasAugmentation(AugmentationName.TheRedPill, true) && Player.sourceFileLvl(1);
|
||||
return Player.hasAugmentation(AugmentationName.TheRedPill, true) && Player.activeSourceFileLvl(1);
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import { getRecordEntries, getRecordValues } from "../../Types/Record";
|
||||
*/
|
||||
export function CalculateEffect(nodes: number, faction: GoOpponent): number {
|
||||
const power = getEffectPowerForFaction(faction);
|
||||
const sourceFileBonus = Player.sourceFileLvl(14) ? 2 : 1;
|
||||
const sourceFileBonus = Player.activeSourceFileLvl(14) ? 2 : 1;
|
||||
return (
|
||||
1 + Math.log(nodes + 1) * Math.pow(nodes + 1, 0.3) * 0.002 * power * currentNodeMults.GoPower * sourceFileBonus
|
||||
);
|
||||
@ -26,7 +26,7 @@ export function CalculateEffect(nodes: number, faction: GoOpponent): number {
|
||||
* for factions you are a member of
|
||||
*/
|
||||
export function getMaxFavor() {
|
||||
const sourceFileLevel = Player.sourceFileLvl(14);
|
||||
const sourceFileLevel = Player.activeSourceFileLvl(14);
|
||||
|
||||
if (sourceFileLevel === 1) {
|
||||
return 80;
|
||||
|
@ -323,8 +323,8 @@ export function getStats() {
|
||||
|
||||
/** Validate singularity access by throwing an error if the player does not have access. */
|
||||
export function checkCheatApiAccess(error: (s: string) => void): void {
|
||||
const hasSourceFile = Player.sourceFileLvl(14) > 1;
|
||||
const isBitnodeFourteenTwo = Player.sourceFileLvl(14) === 1 && Player.bitNodeN === 14;
|
||||
const hasSourceFile = Player.activeSourceFileLvl(14) > 1;
|
||||
const isBitnodeFourteenTwo = Player.activeSourceFileLvl(14) === 1 && Player.bitNodeN === 14;
|
||||
if (!hasSourceFile && !isBitnodeFourteenTwo) {
|
||||
error(
|
||||
`The go.cheat API requires Source-File 14.2 to run, a power up you obtain later in the game.
|
||||
@ -387,7 +387,7 @@ export async function determineCheatSuccess(
|
||||
* 15: +31,358,645%
|
||||
*/
|
||||
export function cheatSuccessChance(cheatCount: number) {
|
||||
const sourceFileBonus = Player.sourceFileLvl(14) === 3 ? 0.25 : 0;
|
||||
const sourceFileBonus = Player.activeSourceFileLvl(14) === 3 ? 0.25 : 0;
|
||||
const cheatCountScalar = (0.7 - 0.02 * cheatCount) ** cheatCount;
|
||||
return Math.max(Math.min(0.6 * cheatCountScalar * Player.mults.crime_success + sourceFileBonus, 1), 0);
|
||||
}
|
||||
|
@ -23,11 +23,12 @@ import { GetServer } from "../Server/AllServers";
|
||||
import { Server } from "../Server/Server";
|
||||
import { Companies } from "../Company/Companies";
|
||||
import { isMember } from "../utils/EnumHelper";
|
||||
import { canAccessBitNodeFeature } from "../BitNode/BitNodeUtils";
|
||||
|
||||
// Returns a boolean indicating whether the player has Hacknet Servers
|
||||
// (the upgraded form of Hacknet Nodes)
|
||||
export function hasHacknetServers(): boolean {
|
||||
return Player.bitNodeN === 9 || Player.sourceFileLvl(9) > 0;
|
||||
return canAccessBitNodeFeature(9) && !Player.bitNodeOptions.disableHacknetServer;
|
||||
}
|
||||
|
||||
export function purchaseHacknet(): number {
|
||||
|
@ -14,7 +14,7 @@ interface IProps {
|
||||
|
||||
export function CoresButton(props: IProps): React.ReactElement {
|
||||
const homeComputer = Player.getHomeComputer();
|
||||
const maxCores = homeComputer.cpuCores >= 8;
|
||||
const maxCores = Player.bitNodeOptions.restrictHomePCUpgrade || homeComputer.cpuCores >= 8;
|
||||
if (maxCores) {
|
||||
return <Button>Upgrade 'home' cores - MAX</Button>;
|
||||
}
|
||||
|
@ -19,7 +19,10 @@ interface IProps {
|
||||
|
||||
export function RamButton(props: IProps): React.ReactElement {
|
||||
const homeComputer = Player.getHomeComputer();
|
||||
if (homeComputer.maxRam >= ServerConstants.HomeComputerMaxRam) {
|
||||
if (
|
||||
(Player.bitNodeOptions.restrictHomePCUpgrade && homeComputer.maxRam >= 128) ||
|
||||
homeComputer.maxRam >= ServerConstants.HomeComputerMaxRam
|
||||
) {
|
||||
return <Button>Upgrade 'home' RAM - MAX</Button>;
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,7 @@ import { HacknetServer } from "../../Hacknet/HacknetServer";
|
||||
import { GetServer } from "../../Server/AllServers";
|
||||
import { ArcadeRoot } from "../../Arcade/ui/ArcadeRoot";
|
||||
import { currentNodeMults } from "../../BitNode/BitNodeMultipliers";
|
||||
import { canAccessBitNodeFeature, knowAboutBitverse } from "../../BitNode/BitNodeUtils";
|
||||
|
||||
interface SpecialLocationProps {
|
||||
loc: Location;
|
||||
@ -91,8 +92,10 @@ export function SpecialLocation(props: SpecialLocationProps): React.ReactElement
|
||||
function EatNoodles(): void {
|
||||
SnackbarEvents.emit("You ate some delicious noodles and feel refreshed", ToastVariant.SUCCESS, 2000);
|
||||
N00dles(); // This is the true power of the noodles.
|
||||
if (Player.sourceFiles.size > 0) Player.giveExploit(Exploit.N00dles);
|
||||
if (Player.sourceFileLvl(5) > 0 || Player.bitNodeN === 5) {
|
||||
if (knowAboutBitverse()) {
|
||||
Player.giveExploit(Exploit.N00dles);
|
||||
}
|
||||
if (canAccessBitNodeFeature(5)) {
|
||||
Player.exp.intelligence *= 1.0000000000000002;
|
||||
}
|
||||
Player.exp.hacking *= 1.0000000000000002;
|
||||
|
@ -8,6 +8,7 @@ import { SpecialServers } from "../Server/data/SpecialServers";
|
||||
import { Settings } from "../Settings/Settings";
|
||||
import { dialogBoxCreate } from "../ui/React/DialogBox";
|
||||
import { Server } from "../Server/Server";
|
||||
import { knowAboutBitverse } from "../BitNode/BitNodeUtils";
|
||||
|
||||
//Sends message to player, including a pop up
|
||||
function sendMessage(name: MessageFilename, forced = false): void {
|
||||
@ -63,9 +64,9 @@ function checkForMessagesToSend(): void {
|
||||
//If the daemon can be hacked, send the player icarus.msg
|
||||
if (
|
||||
Player.skills.hacking >= worldDaemon.requiredHackingSkill &&
|
||||
(Player.sourceFiles.size === 0 || !recvd(MessageFilename.RedPill))
|
||||
(!knowAboutBitverse() || !recvd(MessageFilename.RedPill))
|
||||
) {
|
||||
sendMessage(MessageFilename.RedPill, Player.sourceFiles.size === 0);
|
||||
sendMessage(MessageFilename.RedPill, !knowAboutBitverse());
|
||||
}
|
||||
//If the daemon cannot be hacked, send the player truthgazer.msg a single time.
|
||||
else if (!recvd(MessageFilename.TruthGazer)) {
|
||||
|
@ -1,5 +1,11 @@
|
||||
import type { NetscriptContext } from "./APIWrapper";
|
||||
import type { RunningScript as IRunningScript, Person as IPerson, Server as IServer, ScriptArg } from "@nsdefs";
|
||||
import type {
|
||||
RunningScript as IRunningScript,
|
||||
Person as IPerson,
|
||||
Server as IServer,
|
||||
ScriptArg,
|
||||
BitNodeOptions,
|
||||
} from "@nsdefs";
|
||||
import type { WorkerScript } from "./WorkerScript";
|
||||
|
||||
import React from "react";
|
||||
@ -49,6 +55,12 @@ import { CustomBoundary } from "../ui/Components/CustomBoundary";
|
||||
import { ServerConstants } from "../Server/data/Constants";
|
||||
import { basicErrorMessage, errorMessage, log } from "./ErrorMessages";
|
||||
import { assertString, debugType } from "./TypeAssertion";
|
||||
import {
|
||||
canAccessBitNodeFeature,
|
||||
getDefaultBitNodeOptions,
|
||||
validateSourceFileOverrides,
|
||||
} from "../BitNode/BitNodeUtils";
|
||||
import { JSONMap } from "../Types/Jsonable";
|
||||
|
||||
export const helpers = {
|
||||
string,
|
||||
@ -83,6 +95,7 @@ export const helpers = {
|
||||
getCannotFindRunningScriptErrorMessage,
|
||||
createPublicRunningScript,
|
||||
failOnHacknetServer,
|
||||
validateBitNodeOptions,
|
||||
};
|
||||
|
||||
/** RunOptions with non-optional, type-validated members, for passing between internal functions. */
|
||||
@ -279,7 +292,7 @@ function validateHGWOptions(ctx: NetscriptContext, opts: unknown): CompleteHGWOp
|
||||
|
||||
/** Validate singularity access by throwing an error if the player does not have access. */
|
||||
function checkSingularityAccess(ctx: NetscriptContext): void {
|
||||
if (Player.bitNodeN !== 4 && Player.sourceFileLvl(4) === 0) {
|
||||
if (!canAccessBitNodeFeature(4)) {
|
||||
throw errorMessage(
|
||||
ctx,
|
||||
`This singularity function requires Source-File 4 to run. A power up you obtain later in the game.
|
||||
@ -732,3 +745,40 @@ let customElementKey = 0;
|
||||
export function wrapUserNode(value: unknown) {
|
||||
return <CustomBoundary key={`PlayerContent${customElementKey++}`}>{value}</CustomBoundary>;
|
||||
}
|
||||
|
||||
function validateBitNodeOptions(ctx: NetscriptContext, bitNodeOptions: unknown): BitNodeOptions {
|
||||
const result = getDefaultBitNodeOptions();
|
||||
if (bitNodeOptions == null) {
|
||||
return result;
|
||||
}
|
||||
if (typeof bitNodeOptions !== "object") {
|
||||
throw errorMessage(ctx, `bitNodeOptions must be an object if it's specified. It was ${bitNodeOptions}.`);
|
||||
}
|
||||
const options = bitNodeOptions as Unknownify<BitNodeOptions>;
|
||||
if (!(options.sourceFileOverrides instanceof Map)) {
|
||||
throw errorMessage(ctx, `sourceFileOverrides must be a Map.`);
|
||||
}
|
||||
const validationResultForSourceFileOverrides = validateSourceFileOverrides(options.sourceFileOverrides, true);
|
||||
if (!validationResultForSourceFileOverrides.valid) {
|
||||
throw errorMessage(
|
||||
ctx,
|
||||
`sourceFileOverrides is invalid. Reason: ${validationResultForSourceFileOverrides.message}`,
|
||||
);
|
||||
}
|
||||
|
||||
result.sourceFileOverrides = new JSONMap(options.sourceFileOverrides);
|
||||
if (options.intelligenceOverride !== undefined) {
|
||||
result.intelligenceOverride = number(ctx, "intelligenceOverride", options.intelligenceOverride);
|
||||
} else {
|
||||
result.intelligenceOverride = undefined;
|
||||
}
|
||||
result.restrictHomePCUpgrade = !!options.restrictHomePCUpgrade;
|
||||
result.disableGang = !!options.disableGang;
|
||||
result.disableCorporation = !!options.disableCorporation;
|
||||
result.disableBladeburner = !!options.disableBladeburner;
|
||||
result.disable4SData = !!options.disable4SData;
|
||||
result.disableHacknetServer = !!options.disableHacknetServer;
|
||||
result.disableSleeveExpAndAugmentation = !!options.disableSleeveExpAndAugmentation;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -86,10 +86,16 @@ export const RamCostConstants = {
|
||||
|
||||
function SF4Cost(cost: number): () => number {
|
||||
return () => {
|
||||
if (Player.bitNodeN === 4) return cost;
|
||||
const sf4 = Player.sourceFileLvl(4);
|
||||
if (sf4 <= 1) return cost * 16;
|
||||
if (sf4 === 2) return cost * 4;
|
||||
if (Player.bitNodeN === 4) {
|
||||
return cost;
|
||||
}
|
||||
const sf4 = Player.activeSourceFileLvl(4);
|
||||
if (sf4 <= 1) {
|
||||
return cost * 16;
|
||||
}
|
||||
if (sf4 === 2) {
|
||||
return cost * 4;
|
||||
}
|
||||
return cost;
|
||||
};
|
||||
}
|
||||
|
@ -111,6 +111,7 @@ import { ServerConstants } from "./Server/data/Constants";
|
||||
import { assertFunction } from "./Netscript/TypeAssertion";
|
||||
import { Router } from "./ui/GameRoot";
|
||||
import { Page } from "./ui/Router";
|
||||
import { canAccessBitNodeFeature, validBitNodes } from "./BitNode/BitNodeUtils";
|
||||
|
||||
export const enums: NSEnums = {
|
||||
CityName,
|
||||
@ -972,13 +973,19 @@ export const ns: InternalAPI<NSFull> = {
|
||||
},
|
||||
getBitNodeMultipliers:
|
||||
(ctx) =>
|
||||
(_n = Player.bitNodeN, _lvl = Player.sourceFileLvl(Player.bitNodeN) + 1) => {
|
||||
if (Player.sourceFileLvl(5) <= 0 && Player.bitNodeN !== 5)
|
||||
(_n = Player.bitNodeN, _lvl = Player.activeSourceFileLvl(Player.bitNodeN) + 1) => {
|
||||
if (!canAccessBitNodeFeature(5)) {
|
||||
throw helpers.errorMessage(ctx, "Requires Source-File 5 to run.");
|
||||
}
|
||||
// TODO v3.0: check n and lvl with helpers.number() and Number.isInteger().
|
||||
const n = Math.round(helpers.number(ctx, "n", _n));
|
||||
const lvl = Math.round(helpers.number(ctx, "lvl", _lvl));
|
||||
if (n < 1 || n > 14) throw new Error("n must be between 1 and 14");
|
||||
if (lvl < 1) throw new Error("lvl must be >= 1");
|
||||
if (!validBitNodes.includes(n)) {
|
||||
throw new Error(`Invalid BitNode: ${n}.`);
|
||||
}
|
||||
if (lvl < 1) {
|
||||
throw new Error("SF level must be greater than or equal to 1.");
|
||||
}
|
||||
|
||||
return Object.assign({}, getBitNodeMultipliers(n, lvl));
|
||||
},
|
||||
@ -1800,6 +1807,10 @@ export const ns: InternalAPI<NSFull> = {
|
||||
currentNode: Player.bitNodeN,
|
||||
ownedAugs: new Map(Player.augmentations.map((aug) => [aug.name, aug.level])),
|
||||
ownedSF: new Map(Player.sourceFiles),
|
||||
bitNodeOptions: {
|
||||
...Player.bitNodeOptions,
|
||||
sourceFileOverrides: new Map(Player.bitNodeOptions.sourceFileOverrides),
|
||||
},
|
||||
}),
|
||||
getFunctionRamCost: (ctx) => (_name) => {
|
||||
const name = helpers.string(ctx, "name", _name);
|
||||
|
@ -12,6 +12,7 @@ import { Skills } from "../Bladeburner/data/Skills";
|
||||
import { assertString } from "../Netscript/TypeAssertion";
|
||||
import { BlackOperations, blackOpsArray } from "../Bladeburner/data/BlackOperations";
|
||||
import { checkSleeveAPIAccess, checkSleeveNumber } from "../NetscriptFunctions/Sleeve";
|
||||
import { canAccessBitNodeFeature } from "../BitNode/BitNodeUtils";
|
||||
|
||||
export function NetscriptBladeburner(): InternalAPI<INetscriptBladeburner> {
|
||||
const checkBladeburnerAccess = function (ctx: NetscriptContext): void {
|
||||
@ -19,7 +20,7 @@ export function NetscriptBladeburner(): InternalAPI<INetscriptBladeburner> {
|
||||
return;
|
||||
};
|
||||
const getBladeburner = function (ctx: NetscriptContext): Bladeburner {
|
||||
const apiAccess = Player.bitNodeN === 7 || Player.sourceFileLvl(7) > 0;
|
||||
const apiAccess = canAccessBitNodeFeature(7);
|
||||
if (!apiAccess) {
|
||||
throw helpers.errorMessage(ctx, "You have not unlocked the Bladeburner API.", "API ACCESS");
|
||||
}
|
||||
@ -298,28 +299,28 @@ export function NetscriptBladeburner(): InternalAPI<INetscriptBladeburner> {
|
||||
return !!attempt.success;
|
||||
},
|
||||
joinBladeburnerDivision: (ctx) => () => {
|
||||
if (Player.bitNodeN === 7 || Player.sourceFileLvl(7) > 0) {
|
||||
if (currentNodeMults.BladeburnerRank === 0) {
|
||||
return false; // Disabled in this bitnode
|
||||
}
|
||||
if (Player.bladeburner) {
|
||||
return true; // Already member
|
||||
} else if (
|
||||
Player.skills.strength >= 100 &&
|
||||
Player.skills.defense >= 100 &&
|
||||
Player.skills.dexterity >= 100 &&
|
||||
Player.skills.agility >= 100
|
||||
) {
|
||||
Player.startBladeburner();
|
||||
helpers.log(ctx, () => "You have been accepted into the Bladeburner division");
|
||||
|
||||
return true;
|
||||
} else {
|
||||
helpers.log(ctx, () => "You do not meet the requirements for joining the Bladeburner division");
|
||||
return false;
|
||||
}
|
||||
if (!canAccessBitNodeFeature(7) || Player.bitNodeOptions.disableBladeburner) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
if (currentNodeMults.BladeburnerRank === 0) {
|
||||
return false; // Disabled in this bitnode
|
||||
}
|
||||
if (Player.bladeburner) {
|
||||
return true; // Already member
|
||||
}
|
||||
if (
|
||||
Player.skills.strength < 100 ||
|
||||
Player.skills.defense < 100 ||
|
||||
Player.skills.dexterity < 100 ||
|
||||
Player.skills.agility < 100
|
||||
) {
|
||||
helpers.log(ctx, () => "You do not meet the requirements for joining the Bladeburner division");
|
||||
return false;
|
||||
}
|
||||
Player.startBladeburner();
|
||||
helpers.log(ctx, () => "You have been accepted into the Bladeburner division");
|
||||
|
||||
return true;
|
||||
},
|
||||
getBonusTime: (ctx) => () => {
|
||||
const bladeburner = getBladeburner(ctx);
|
||||
|
@ -59,6 +59,7 @@ import { ServerConstants } from "../Server/data/Constants";
|
||||
import { blackOpsArray } from "../Bladeburner/data/BlackOperations";
|
||||
import { calculateEffectiveRequiredReputation } from "../Company/utils";
|
||||
import { calculateFavorAfterResetting } from "../Faction/formulas/favor";
|
||||
import { validBitNodes } from "../BitNode/BitNodeUtils";
|
||||
|
||||
export function NetscriptSingularity(): InternalAPI<ISingularity> {
|
||||
const runAfterReset = function (cbScript: ScriptFilePath) {
|
||||
@ -628,7 +629,7 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
|
||||
|
||||
// Check if we're at max cores
|
||||
const homeComputer = Player.getHomeComputer();
|
||||
if (homeComputer.cpuCores >= 8) {
|
||||
if (Player.bitNodeOptions.restrictHomePCUpgrade || homeComputer.cpuCores >= 8) {
|
||||
helpers.log(ctx, () => `Your home computer is at max cores.`);
|
||||
return false;
|
||||
}
|
||||
@ -659,7 +660,10 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
|
||||
|
||||
// Check if we're at max RAM
|
||||
const homeComputer = Player.getHomeComputer();
|
||||
if (homeComputer.maxRam >= ServerConstants.HomeComputerMaxRam) {
|
||||
if (
|
||||
(Player.bitNodeOptions.restrictHomePCUpgrade && homeComputer.maxRam >= 128) ||
|
||||
homeComputer.maxRam >= ServerConstants.HomeComputerMaxRam
|
||||
) {
|
||||
helpers.log(ctx, () => `Your home computer is at max RAM.`);
|
||||
return false;
|
||||
}
|
||||
@ -1137,38 +1141,47 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
|
||||
}
|
||||
return item.price;
|
||||
},
|
||||
b1tflum3: (ctx) => (_nextBN, _cbScript) => {
|
||||
b1tflum3: (ctx) => (_nextBN, _cbScript, _bitNodeOptions) => {
|
||||
helpers.checkSingularityAccess(ctx);
|
||||
const nextBN = helpers.number(ctx, "nextBN", _nextBN);
|
||||
const cbScript = _cbScript
|
||||
? resolveScriptFilePath(helpers.string(ctx, "cbScript", _cbScript), ctx.workerScript.name)
|
||||
: false;
|
||||
if (cbScript === null) throw helpers.errorMessage(ctx, `Could not resolve file path: ${_cbScript}`);
|
||||
enterBitNode(true, Player.bitNodeN, nextBN);
|
||||
if (cbScript) setTimeout(() => runAfterReset(cbScript), 500);
|
||||
if (cbScript === null) {
|
||||
throw helpers.errorMessage(ctx, `Could not resolve file path: ${_cbScript}`);
|
||||
}
|
||||
enterBitNode(true, Player.bitNodeN, nextBN, helpers.validateBitNodeOptions(ctx, _bitNodeOptions));
|
||||
if (cbScript) {
|
||||
setTimeout(() => runAfterReset(cbScript), 500);
|
||||
}
|
||||
},
|
||||
destroyW0r1dD43m0n: (ctx) => (_nextBN, _cbScript) => {
|
||||
destroyW0r1dD43m0n: (ctx) => (_nextBN, _cbScript, _bitNodeOptions) => {
|
||||
helpers.checkSingularityAccess(ctx);
|
||||
const nextBN = helpers.number(ctx, "nextBN", _nextBN);
|
||||
if (nextBN > 14 || nextBN < 1 || !Number.isInteger(nextBN)) {
|
||||
throw new Error(`Invalid bitnode specified: ${_nextBN}`);
|
||||
if (!validBitNodes.includes(nextBN)) {
|
||||
throw new Error(`Invalid BitNode: ${_nextBN}.`);
|
||||
}
|
||||
const cbScript = _cbScript
|
||||
? resolveScriptFilePath(helpers.string(ctx, "cbScript", _cbScript), ctx.workerScript.name)
|
||||
: false;
|
||||
if (cbScript === null) throw helpers.errorMessage(ctx, `Could not resolve file path: ${_cbScript}`);
|
||||
if (cbScript === null) {
|
||||
throw helpers.errorMessage(ctx, `Could not resolve file path: ${_cbScript}`);
|
||||
}
|
||||
|
||||
const wd = GetServer(SpecialServers.WorldDaemon);
|
||||
if (!(wd instanceof Server)) {
|
||||
throw new Error("WorldDaemon is not a normal server. This is a bug. Please contact developers.");
|
||||
}
|
||||
const hackingRequirements = () => {
|
||||
if (Player.skills.hacking < wd.requiredHackingSkill) return false;
|
||||
if (!wd.hasAdminRights) return false;
|
||||
if (Player.skills.hacking < wd.requiredHackingSkill || !wd.hasAdminRights) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const bladeburnerRequirements = () => {
|
||||
if (!Player.bladeburner) return false;
|
||||
if (!Player.bladeburner) {
|
||||
return false;
|
||||
}
|
||||
return Player.bladeburner.numBlackOpsComplete >= blackOpsArray.length;
|
||||
};
|
||||
|
||||
@ -1179,8 +1192,10 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
|
||||
|
||||
wd.backdoorInstalled = true;
|
||||
calculateAchievements();
|
||||
enterBitNode(false, Player.bitNodeN, nextBN);
|
||||
if (cbScript) setTimeout(() => runAfterReset(cbScript), 500);
|
||||
enterBitNode(false, Player.bitNodeN, nextBN, helpers.validateBitNodeOptions(ctx, _bitNodeOptions));
|
||||
if (cbScript) {
|
||||
setTimeout(() => runAfterReset(cbScript), 500);
|
||||
}
|
||||
},
|
||||
getCurrentWork: (ctx) => () => {
|
||||
helpers.checkSingularityAccess(ctx);
|
||||
|
@ -15,6 +15,7 @@ import { helpers } from "../Netscript/NetscriptHelpers";
|
||||
import { getAugCost } from "../Augmentation/AugmentationHelpers";
|
||||
import { Factions } from "../Faction/Factions";
|
||||
import { SleeveWorkType } from "../PersonObjects/Sleeve/Work/Work";
|
||||
import { canAccessBitNodeFeature } from "../BitNode/BitNodeUtils";
|
||||
|
||||
export const checkSleeveAPIAccess = function (ctx: NetscriptContext) {
|
||||
if (Player.bitNodeN !== 10 && !Player.sourceFileLvl(10)) {
|
||||
@ -33,6 +34,23 @@ export const checkSleeveNumber = function (ctx: NetscriptContext, sleeveNumber:
|
||||
};
|
||||
|
||||
export function NetscriptSleeve(): InternalAPI<NetscriptSleeve> {
|
||||
const checkSleeveAPIAccess = function (ctx: NetscriptContext) {
|
||||
if (!canAccessBitNodeFeature(10)) {
|
||||
throw helpers.errorMessage(
|
||||
ctx,
|
||||
"You do not have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10.",
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const checkSleeveNumber = function (ctx: NetscriptContext, sleeveNumber: number) {
|
||||
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
|
||||
const msg = `Invalid sleeve number: ${sleeveNumber}`;
|
||||
helpers.log(ctx, () => msg);
|
||||
throw helpers.errorMessage(ctx, msg);
|
||||
}
|
||||
};
|
||||
|
||||
const sleeveFunctions: InternalAPI<NetscriptSleeve> = {
|
||||
getNumSleeves: (ctx) => () => {
|
||||
checkSleeveAPIAccess(ctx);
|
||||
|
@ -167,10 +167,8 @@ export function NetscriptStockMarket(): InternalAPI<TIX> {
|
||||
const symbol = helpers.string(ctx, "symbol", _symbol);
|
||||
const shares = helpers.number(ctx, "shares", _shares);
|
||||
checkTixApiAccess(ctx);
|
||||
if (Player.bitNodeN !== 8) {
|
||||
if (Player.sourceFileLvl(8) <= 1) {
|
||||
throw helpers.errorMessage(ctx, "You must either be in BitNode-8 or you must have Source-File 8 Level 2.");
|
||||
}
|
||||
if (Player.bitNodeN !== 8 && Player.activeSourceFileLvl(8) <= 1) {
|
||||
throw helpers.errorMessage(ctx, "You must either be in BitNode-8 or you must have Source-File 8 Level 2.");
|
||||
}
|
||||
const stock = getStockFromSymbol(ctx, symbol);
|
||||
const res = shortStock(stock, shares, ctx, {});
|
||||
@ -181,10 +179,8 @@ export function NetscriptStockMarket(): InternalAPI<TIX> {
|
||||
const symbol = helpers.string(ctx, "symbol", _symbol);
|
||||
const shares = helpers.number(ctx, "shares", _shares);
|
||||
checkTixApiAccess(ctx);
|
||||
if (Player.bitNodeN !== 8) {
|
||||
if (Player.sourceFileLvl(8) <= 1) {
|
||||
throw helpers.errorMessage(ctx, "You must either be in BitNode-8 or you must have Source-File 8 Level 2.");
|
||||
}
|
||||
if (Player.bitNodeN !== 8 && Player.activeSourceFileLvl(8) <= 1) {
|
||||
throw helpers.errorMessage(ctx, "You must either be in BitNode-8 or you must have Source-File 8 Level 2.");
|
||||
}
|
||||
const stock = getStockFromSymbol(ctx, symbol);
|
||||
const res = sellShort(stock, shares, ctx, {});
|
||||
@ -198,10 +194,8 @@ export function NetscriptStockMarket(): InternalAPI<TIX> {
|
||||
const type = helpers.string(ctx, "type", _type);
|
||||
const pos = helpers.string(ctx, "pos", _pos);
|
||||
checkTixApiAccess(ctx);
|
||||
if (Player.bitNodeN !== 8) {
|
||||
if (Player.sourceFileLvl(8) <= 2) {
|
||||
throw helpers.errorMessage(ctx, "You must either be in BitNode-8 or you must have Source-File 8 Level 3.");
|
||||
}
|
||||
if (Player.bitNodeN !== 8 && Player.activeSourceFileLvl(8) <= 2) {
|
||||
throw helpers.errorMessage(ctx, "You must either be in BitNode-8 or you must have Source-File 8 Level 3.");
|
||||
}
|
||||
const stock = getStockFromSymbol(ctx, symbol);
|
||||
|
||||
@ -238,10 +232,8 @@ export function NetscriptStockMarket(): InternalAPI<TIX> {
|
||||
const type = helpers.string(ctx, "type", _type);
|
||||
const pos = helpers.string(ctx, "pos", _pos);
|
||||
checkTixApiAccess(ctx);
|
||||
if (Player.bitNodeN !== 8) {
|
||||
if (Player.sourceFileLvl(8) <= 2) {
|
||||
throw helpers.errorMessage(ctx, "You must either be in BitNode-8 or you must have Source-File 8 Level 3.");
|
||||
}
|
||||
if (Player.bitNodeN !== 8 && Player.activeSourceFileLvl(8) <= 2) {
|
||||
throw helpers.errorMessage(ctx, "You must either be in BitNode-8 or you must have Source-File 8 Level 3.");
|
||||
}
|
||||
const stock = getStockFromSymbol(ctx, symbol);
|
||||
if (isNaN(shares) || isNaN(price)) {
|
||||
@ -281,10 +273,8 @@ export function NetscriptStockMarket(): InternalAPI<TIX> {
|
||||
},
|
||||
getOrders: (ctx) => () => {
|
||||
checkTixApiAccess(ctx);
|
||||
if (Player.bitNodeN !== 8) {
|
||||
if (Player.sourceFileLvl(8) <= 2) {
|
||||
throw helpers.errorMessage(ctx, "You must either be in BitNode-8 or have Source-File 8 Level 3.");
|
||||
}
|
||||
if (Player.bitNodeN !== 8 && Player.activeSourceFileLvl(8) <= 2) {
|
||||
throw helpers.errorMessage(ctx, "You must either be in BitNode-8 or have Source-File 8 Level 3.");
|
||||
}
|
||||
|
||||
const orders: StockOrder = {};
|
||||
@ -328,6 +318,11 @@ export function NetscriptStockMarket(): InternalAPI<TIX> {
|
||||
return forecast / 100; // Convert from percentage to decimal
|
||||
},
|
||||
purchase4SMarketData: (ctx) => () => {
|
||||
if (Player.bitNodeOptions.disable4SData) {
|
||||
helpers.log(ctx, () => "4S Market Data is disabled.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Player.has4SData) {
|
||||
helpers.log(ctx, () => "Already purchased 4S Market Data.");
|
||||
return true;
|
||||
@ -344,6 +339,11 @@ export function NetscriptStockMarket(): InternalAPI<TIX> {
|
||||
return true;
|
||||
},
|
||||
purchase4SMarketDataTixApi: (ctx) => () => {
|
||||
if (Player.bitNodeOptions.disable4SData) {
|
||||
helpers.log(ctx, () => "4S Market Data is disabled.");
|
||||
return false;
|
||||
}
|
||||
|
||||
checkTixApiAccess(ctx);
|
||||
|
||||
if (Player.has4SDataTixApi) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { Player as IPlayer } from "@nsdefs";
|
||||
import type { BitNodeOptions, Player as IPlayer } from "@nsdefs";
|
||||
import type { PlayerAchievement } from "../../Achievements/Achievements";
|
||||
import type { Bladeburner } from "../../Bladeburner/Bladeburner";
|
||||
import type { Corporation } from "../../Corporation/Corporation";
|
||||
@ -74,6 +74,22 @@ export class PlayerObject extends Person implements IPlayer {
|
||||
|
||||
entropy = 0;
|
||||
|
||||
bitNodeOptions: BitNodeOptions = {
|
||||
sourceFileOverrides: new JSONMap<number, number>(),
|
||||
intelligenceOverride: undefined,
|
||||
restrictHomePCUpgrade: false,
|
||||
disableGang: false,
|
||||
disableCorporation: false,
|
||||
disableBladeburner: false,
|
||||
disable4SData: false,
|
||||
disableHacknetServer: false,
|
||||
disableSleeveExpAndAugmentation: false,
|
||||
};
|
||||
|
||||
get activeSourceFiles(): JSONMap<number, number> {
|
||||
return new JSONMap([...this.sourceFiles, ...this.bitNodeOptions.sourceFileOverrides]);
|
||||
}
|
||||
|
||||
// Player-specific methods
|
||||
init = generalMethods.init;
|
||||
startWork = workMethods.startWork;
|
||||
@ -129,6 +145,7 @@ export class PlayerObject extends Person implements IPlayer {
|
||||
setBitNodeNumber = generalMethods.setBitNodeNumber;
|
||||
canAccessCotMG = generalMethods.canAccessCotMG;
|
||||
sourceFileLvl = generalMethods.sourceFileLvl;
|
||||
activeSourceFileLvl = generalMethods.activeSourceFileLvl;
|
||||
applyEntropy = augmentationMethods.applyEntropy;
|
||||
focusPenalty = generalMethods.focusPenalty;
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { canAccessBitNodeFeature } from "../../BitNode/BitNodeUtils";
|
||||
import { Bladeburner } from "../../Bladeburner/Bladeburner";
|
||||
|
||||
import type { PlayerObject } from "./PlayerObject";
|
||||
|
||||
export function canAccessBladeburner(this: PlayerObject): boolean {
|
||||
return this.bitNodeN === 6 || this.bitNodeN === 7 || this.sourceFileLvl(6) > 0 || this.sourceFileLvl(7) > 0;
|
||||
return (canAccessBitNodeFeature(6) || canAccessBitNodeFeature(7)) && !this.bitNodeOptions.disableBladeburner;
|
||||
}
|
||||
|
||||
export function startBladeburner(this: PlayerObject): void {
|
||||
|
@ -3,9 +3,10 @@ import { resetIndustryResearchTrees } from "../../Corporation/data/IndustryData"
|
||||
import { Corporation } from "../../Corporation/Corporation";
|
||||
|
||||
import type { PlayerObject } from "./PlayerObject";
|
||||
import { canAccessBitNodeFeature } from "../../BitNode/BitNodeUtils";
|
||||
|
||||
export function canAccessCorporation(this: PlayerObject): boolean {
|
||||
return this.bitNodeN === 3 || this.sourceFileLvl(3) > 0;
|
||||
return canAccessBitNodeFeature(3) && !this.bitNodeOptions.disableCorporation;
|
||||
}
|
||||
|
||||
export function startCorporation(this: PlayerObject, corpName: string, seedFunded: boolean): void {
|
||||
@ -17,7 +18,7 @@ export function startCorporation(this: PlayerObject, corpName: string, seedFunde
|
||||
//reset the research tree in case the corporation was restarted
|
||||
resetIndustryResearchTrees();
|
||||
|
||||
if (this.bitNodeN === 3 || this.sourceFileLvl(3) === 3) {
|
||||
if (this.bitNodeN === 3 || this.activeSourceFileLvl(3) === 3) {
|
||||
this.corporation.unlocks.add(CorpUnlockName.WarehouseAPI);
|
||||
this.corporation.unlocks.add(CorpUnlockName.OfficeAPI);
|
||||
}
|
||||
|
@ -6,12 +6,16 @@ import { Factions } from "../../Faction/Factions";
|
||||
import { Gang } from "../../Gang/Gang";
|
||||
import { GangConstants } from "../../Gang/data/Constants";
|
||||
import { isFactionWork } from "../../Work/FactionWork";
|
||||
import { canAccessBitNodeFeature } from "../../BitNode/BitNodeUtils";
|
||||
|
||||
export function canAccessGang(this: PlayerObject): boolean {
|
||||
if (this.bitNodeOptions.disableGang) {
|
||||
return false;
|
||||
}
|
||||
if (this.bitNodeN === 2) {
|
||||
return true;
|
||||
}
|
||||
if (this.sourceFileLvl(2) <= 0) {
|
||||
if (this.activeSourceFileLvl(2) === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -19,7 +23,7 @@ export function canAccessGang(this: PlayerObject): boolean {
|
||||
}
|
||||
|
||||
export function isAwareOfGang(this: PlayerObject): boolean {
|
||||
return this.bitNodeN === 2 || this.sourceFileLvl(2) >= 1;
|
||||
return canAccessBitNodeFeature(2) && !this.bitNodeOptions.disableGang;
|
||||
}
|
||||
|
||||
export function getGangFaction(this: PlayerObject): Faction {
|
||||
|
@ -49,6 +49,7 @@ import { achievements } from "../../Achievements/Achievements";
|
||||
|
||||
import { isCompanyWork } from "../../Work/CompanyWork";
|
||||
import { isMember } from "../../utils/EnumHelper";
|
||||
import { canAccessBitNodeFeature } from "../../BitNode/BitNodeUtils";
|
||||
|
||||
export function init(this: PlayerObject): void {
|
||||
/* Initialize Player's home computer */
|
||||
@ -415,7 +416,7 @@ export function reapplyAllSourceFiles(this: PlayerObject): void {
|
||||
//Will always be called after reapplyAllAugmentations() so multipliers do not have to be reset
|
||||
//this.resetMultipliers();
|
||||
|
||||
for (const [bn, lvl] of this.sourceFiles) {
|
||||
for (const [bn, lvl] of this.activeSourceFiles) {
|
||||
const srcFileKey = "SourceFile" + bn;
|
||||
const sourceFileObject = SourceFiles[srcFileKey];
|
||||
if (!sourceFileObject) {
|
||||
@ -536,7 +537,7 @@ export function gotoLocation(this: PlayerObject, to: LocationName): boolean {
|
||||
}
|
||||
|
||||
export function canAccessGrafting(this: PlayerObject): boolean {
|
||||
return this.bitNodeN === 10 || this.sourceFileLvl(10) > 0;
|
||||
return canAccessBitNodeFeature(10);
|
||||
}
|
||||
|
||||
export function giveExploit(this: PlayerObject, exploit: Exploit): void {
|
||||
@ -560,13 +561,20 @@ export function getCasinoWinnings(this: PlayerObject): number {
|
||||
}
|
||||
|
||||
export function canAccessCotMG(this: PlayerObject): boolean {
|
||||
return this.bitNodeN === 13 || this.sourceFileLvl(13) > 0;
|
||||
return canAccessBitNodeFeature(13);
|
||||
}
|
||||
|
||||
export function sourceFileLvl(this: PlayerObject, n: number): number {
|
||||
return this.sourceFiles.get(n) ?? 0;
|
||||
}
|
||||
|
||||
export function activeSourceFileLvl(this: PlayerObject, n: number): number {
|
||||
if (this.bitNodeOptions.sourceFileOverrides.has(n)) {
|
||||
return this.bitNodeOptions.sourceFileOverrides.get(n) ?? 0;
|
||||
}
|
||||
return this.sourceFiles.get(n) ?? 0;
|
||||
}
|
||||
|
||||
export function focusPenalty(this: PlayerObject): number {
|
||||
let focus = 1;
|
||||
if (!this.hasAugmentation(AugmentationName.NeuroreceptorManager, true)) {
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { Sleeve } from "../Sleeve";
|
||||
import { Player } from "@player";
|
||||
import { formatExp, formatPercent } from "../../../ui/formatNumber";
|
||||
import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions";
|
||||
import { CONSTANTS } from "../../../Constants";
|
||||
@ -7,6 +6,7 @@ import { Typography } from "@mui/material";
|
||||
import { StatsTable } from "../../../ui/React/StatsTable";
|
||||
import { Modal } from "../../../ui/React/Modal";
|
||||
import React from "react";
|
||||
import { canAccessBitNodeFeature } from "../../../BitNode/BitNodeUtils";
|
||||
|
||||
interface IProps {
|
||||
open: boolean;
|
||||
@ -30,7 +30,7 @@ export function MoreStatsModal(props: IProps): React.ReactElement {
|
||||
[<>Agility: </>, props.sleeve.skills.agility, <> ({formatExp(props.sleeve.exp.agility)} exp)</>],
|
||||
[<>Charisma: </>, props.sleeve.skills.charisma, <> ({formatExp(props.sleeve.exp.charisma)} exp)</>],
|
||||
[
|
||||
...(Player.sourceFileLvl(5) > 0 || Player.bitNodeN === 5
|
||||
...(canAccessBitNodeFeature(5)
|
||||
? [
|
||||
<>Intelligence: </>,
|
||||
props.sleeve.skills.intelligence,
|
||||
|
@ -25,6 +25,7 @@ import { isSleeveClassWork } from "../Work/SleeveClassWork";
|
||||
import { isSleeveFactionWork } from "../Work/SleeveFactionWork";
|
||||
import { isSleeveCompanyWork } from "../Work/SleeveCompanyWork";
|
||||
import { isSleeveCrimeWork } from "../Work/SleeveCrimeWork";
|
||||
import { canAccessBitNodeFeature } from "../../../BitNode/BitNodeUtils";
|
||||
|
||||
const CYCLES_PER_SEC = 1000 / CONSTANTS.MilliPerCycle;
|
||||
|
||||
@ -76,7 +77,7 @@ export function StatsElement(props: IProps): React.ReactElement {
|
||||
color={Settings.theme.cha}
|
||||
data={{ level: props.sleeve.skills.charisma, exp: props.sleeve.exp.charisma }}
|
||||
/>
|
||||
{(Player.sourceFileLvl(5) > 0 || Player.bitNodeN === 5) && (
|
||||
{canAccessBitNodeFeature(5) && (
|
||||
<StatsRow
|
||||
name="Intelligence"
|
||||
color={Settings.theme.int}
|
||||
@ -111,7 +112,7 @@ export function StatsElement(props: IProps): React.ReactElement {
|
||||
export function EarningsElement(props: IProps): React.ReactElement {
|
||||
const { classes } = useStyles();
|
||||
|
||||
let data: (string | JSX.Element)[][] = [];
|
||||
let data: [string, string | JSX.Element][] = [];
|
||||
if (isSleeveCrimeWork(props.sleeve.currentWork)) {
|
||||
const gains = props.sleeve.currentWork.getExp(props.sleeve);
|
||||
data = [
|
||||
@ -150,20 +151,21 @@ export function EarningsElement(props: IProps): React.ReactElement {
|
||||
];
|
||||
}
|
||||
|
||||
companyWork: if (isSleeveCompanyWork(props.sleeve.currentWork)) {
|
||||
if (isSleeveCompanyWork(props.sleeve.currentWork)) {
|
||||
const job = Player.jobs[props.sleeve.currentWork.companyName];
|
||||
if (!job) break companyWork;
|
||||
const rates = props.sleeve.currentWork.getGainRates(props.sleeve, job);
|
||||
data = [
|
||||
[`Money:`, <MoneyRate key="money-rate" money={CYCLES_PER_SEC * rates.money} />],
|
||||
[`Hacking Exp:`, `${formatExp(CYCLES_PER_SEC * rates.hackExp)} / sec`],
|
||||
[`Strength Exp:`, `${formatExp(CYCLES_PER_SEC * rates.strExp)} / sec`],
|
||||
[`Defense Exp:`, `${formatExp(CYCLES_PER_SEC * rates.defExp)} / sec`],
|
||||
[`Dexterity Exp:`, `${formatExp(CYCLES_PER_SEC * rates.dexExp)} / sec`],
|
||||
[`Agility Exp:`, `${formatExp(CYCLES_PER_SEC * rates.agiExp)} / sec`],
|
||||
[`Charisma Exp:`, `${formatExp(CYCLES_PER_SEC * rates.chaExp)} / sec`],
|
||||
[`Reputation:`, <ReputationRate key="reputation-rate" reputation={CYCLES_PER_SEC * rates.reputation} />],
|
||||
];
|
||||
if (job) {
|
||||
const rates = props.sleeve.currentWork.getGainRates(props.sleeve, job);
|
||||
data = [
|
||||
[`Money:`, <MoneyRate key="money-rate" money={CYCLES_PER_SEC * rates.money} />],
|
||||
[`Hacking Exp:`, `${formatExp(CYCLES_PER_SEC * rates.hackExp)} / sec`],
|
||||
[`Strength Exp:`, `${formatExp(CYCLES_PER_SEC * rates.strExp)} / sec`],
|
||||
[`Defense Exp:`, `${formatExp(CYCLES_PER_SEC * rates.defExp)} / sec`],
|
||||
[`Dexterity Exp:`, `${formatExp(CYCLES_PER_SEC * rates.dexExp)} / sec`],
|
||||
[`Agility Exp:`, `${formatExp(CYCLES_PER_SEC * rates.agiExp)} / sec`],
|
||||
[`Charisma Exp:`, `${formatExp(CYCLES_PER_SEC * rates.chaExp)} / sec`],
|
||||
[`Reputation:`, <ReputationRate key="reputation-rate" reputation={CYCLES_PER_SEC * rates.reputation} />],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -1,3 +1,9 @@
|
||||
import { Player } from "@player";
|
||||
|
||||
export function calculateIntelligenceBonus(intelligence: number, weight = 1): number {
|
||||
return 1 + (weight * Math.pow(intelligence, 0.8)) / 600;
|
||||
const effectiveIntelligence =
|
||||
Player.bitNodeOptions.intelligenceOverride !== undefined
|
||||
? Math.min(Player.bitNodeOptions.intelligenceOverride, intelligence)
|
||||
: intelligence;
|
||||
return 1 + (weight * Math.pow(effectiveIntelligence, 0.8)) / 600;
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import { initCircadianModulator } from "./Augmentation/Augmentations";
|
||||
import { Go } from "./Go/Go";
|
||||
import { calculateExp } from "./PersonObjects/formulas/skill";
|
||||
import { currentNodeMults } from "./BitNode/BitNodeMultipliers";
|
||||
import { canAccessBitNodeFeature } from "./BitNode/BitNodeUtils";
|
||||
|
||||
const BitNode8StartingMoney = 250e6;
|
||||
function delayedDialog(message: string) {
|
||||
@ -82,7 +83,7 @@ export function prestigeAugmentation(): void {
|
||||
homeComp.programs.push(program);
|
||||
}
|
||||
}
|
||||
if (Player.sourceFileLvl(5) > 0 || Player.bitNodeN === 5) {
|
||||
if (canAccessBitNodeFeature(5)) {
|
||||
homeComp.programs.push(CompletedProgramName.formulas);
|
||||
}
|
||||
|
||||
@ -146,7 +147,7 @@ export function prestigeAugmentation(): void {
|
||||
if (Player.bitNodeN === 8) {
|
||||
Player.money = BitNode8StartingMoney;
|
||||
}
|
||||
if (Player.bitNodeN === 8 || Player.sourceFileLvl(8) > 0) {
|
||||
if (canAccessBitNodeFeature(8)) {
|
||||
Player.hasWseAccount = true;
|
||||
Player.hasTixApiAccess = true;
|
||||
}
|
||||
@ -169,7 +170,7 @@ export function prestigeAugmentation(): void {
|
||||
// Bitnode 13: Church of the Machine God
|
||||
if (Player.hasAugmentation(AugmentationName.StaneksGift1, true)) {
|
||||
joinFaction(Factions[FactionName.ChurchOfTheMachineGod]);
|
||||
} else if (Player.bitNodeN != 13) {
|
||||
} else if (Player.bitNodeN !== 13) {
|
||||
if (Player.augmentations.some((a) => a.name !== AugmentationName.NeuroFluxGovernor)) {
|
||||
Factions[FactionName.ChurchOfTheMachineGod].isBanned = true;
|
||||
}
|
||||
@ -215,9 +216,9 @@ export function prestigeSourceFile(isFlume: boolean): void {
|
||||
// Re-create foreign servers
|
||||
initForeignServers(Player.getHomeComputer());
|
||||
|
||||
if (Player.sourceFileLvl(9) >= 2) {
|
||||
if (Player.activeSourceFileLvl(9) >= 2) {
|
||||
homeComp.setMaxRam(128);
|
||||
} else if (Player.sourceFileLvl(1) > 0) {
|
||||
} else if (Player.activeSourceFileLvl(1) > 0) {
|
||||
homeComp.setMaxRam(32);
|
||||
} else {
|
||||
homeComp.setMaxRam(8);
|
||||
@ -234,10 +235,10 @@ export function prestigeSourceFile(isFlume: boolean): void {
|
||||
}
|
||||
|
||||
// Give levels of NeuroFluxGovernor for Source-File 12. Must be done here before Augmentations are recalculated
|
||||
if (Player.sourceFileLvl(12) > 0) {
|
||||
if (Player.activeSourceFileLvl(12) > 0) {
|
||||
Player.augmentations.push({
|
||||
name: AugmentationName.NeuroFluxGovernor,
|
||||
level: Player.sourceFileLvl(12),
|
||||
level: Player.activeSourceFileLvl(12),
|
||||
});
|
||||
}
|
||||
|
||||
@ -246,7 +247,7 @@ export function prestigeSourceFile(isFlume: boolean): void {
|
||||
Player.reapplyAllAugmentations();
|
||||
Player.reapplyAllSourceFiles();
|
||||
|
||||
if (Player.sourceFileLvl(5) > 0 || Player.bitNodeN === 5) {
|
||||
if (canAccessBitNodeFeature(5)) {
|
||||
homeComp.programs.push(CompletedProgramName.formulas);
|
||||
}
|
||||
|
||||
@ -269,7 +270,7 @@ export function prestigeSourceFile(isFlume: boolean): void {
|
||||
if (Player.bitNodeN === 8) {
|
||||
Player.money = BitNode8StartingMoney;
|
||||
}
|
||||
if (Player.bitNodeN === 8 || Player.sourceFileLvl(8) > 0) {
|
||||
if (Player.bitNodeN === 8 || Player.activeSourceFileLvl(8) > 0) {
|
||||
Player.hasWseAccount = true;
|
||||
Player.hasTixApiAccess = true;
|
||||
}
|
||||
@ -282,7 +283,7 @@ export function prestigeSourceFile(isFlume: boolean): void {
|
||||
}
|
||||
|
||||
// BitNode 12: The Recursion
|
||||
if (Player.bitNodeN === 12 && Player.sourceFileLvl(12) > 100) {
|
||||
if (Player.bitNodeN === 12 && Player.activeSourceFileLvl(12) > 100) {
|
||||
delayedDialog("Saynt_Garmo is watching you");
|
||||
}
|
||||
|
||||
@ -301,7 +302,7 @@ export function prestigeSourceFile(isFlume: boolean): void {
|
||||
|
||||
// Source-File 9 (level 3) effect
|
||||
// also now applies when entering bn9 until install
|
||||
if (Player.sourceFileLvl(9) >= 3 || Player.bitNodeN === 9) {
|
||||
if ((Player.activeSourceFileLvl(9) >= 3 || Player.bitNodeN === 9) && !Player.bitNodeOptions.disableHacknetServer) {
|
||||
const hserver = Player.createHacknetServer();
|
||||
|
||||
hserver.level = 100;
|
||||
@ -319,7 +320,9 @@ export function prestigeSourceFile(isFlume: boolean): void {
|
||||
staneksGift.prestigeSourceFile();
|
||||
|
||||
// Gain int exp
|
||||
if (Player.sourceFileLvl(5) !== 0 && !isFlume) Player.gainIntelligenceExp(300);
|
||||
if (Player.activeSourceFileLvl(5) !== 0 && !isFlume) {
|
||||
Player.gainIntelligenceExp(300);
|
||||
}
|
||||
|
||||
// Clear recent scripts
|
||||
recentScripts.splice(0, recentScripts.length);
|
||||
|
@ -13,6 +13,7 @@ import { calculateHackingTime, calculateGrowTime, calculateWeakenTime } from "..
|
||||
import { CompletedProgramName, FactionName } from "@enums";
|
||||
import { Router } from "../ui/GameRoot";
|
||||
import { Page } from "../ui/Router";
|
||||
import { knowAboutBitverse } from "../BitNode/BitNodeUtils";
|
||||
|
||||
function requireHackingLevel(lvl: number) {
|
||||
return function () {
|
||||
@ -22,7 +23,7 @@ function requireHackingLevel(lvl: number) {
|
||||
|
||||
function bitFlumeRequirements() {
|
||||
return function () {
|
||||
return Player.sourceFiles.size > 0 && Player.skills.hacking >= 1;
|
||||
return knowAboutBitverse() && Player.skills.hacking >= 1;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
/** Implementation for what happens when you destroy a BitNode */
|
||||
import React from "react";
|
||||
import { Player } from "@player";
|
||||
import { type BitNodeOptions } from "@nsdefs";
|
||||
import { SourceFiles } from "./SourceFile/SourceFiles";
|
||||
|
||||
import { dialogBoxCreate } from "./ui/React/DialogBox";
|
||||
import { Router } from "./ui/GameRoot";
|
||||
import { Page } from "./ui/Router";
|
||||
import { prestigeSourceFile } from "./Prestige";
|
||||
import { setBitNodeOptions } from "./BitNode/BitNodeUtils";
|
||||
|
||||
function giveSourceFile(bitNodeNumber: number): void {
|
||||
const sourceFileKey = "SourceFile" + bitNodeNumber.toString();
|
||||
@ -48,7 +50,12 @@ function giveSourceFile(bitNodeNumber: number): void {
|
||||
}
|
||||
}
|
||||
|
||||
export function enterBitNode(isFlume: boolean, destroyedBitNode: number, newBitNode: number): void {
|
||||
export function enterBitNode(
|
||||
isFlume: boolean,
|
||||
destroyedBitNode: number,
|
||||
newBitNode: number,
|
||||
bitNodeOptions: BitNodeOptions,
|
||||
): void {
|
||||
if (!isFlume) {
|
||||
giveSourceFile(destroyedBitNode);
|
||||
} else if (Player.sourceFileLvl(5) === 0 && newBitNode !== 5) {
|
||||
@ -61,6 +68,9 @@ export function enterBitNode(isFlume: boolean, destroyedBitNode: number, newBitN
|
||||
// Set new Bit Node
|
||||
Player.bitNodeN = newBitNode;
|
||||
|
||||
// Set BitNode options
|
||||
setBitNodeOptions(bitNodeOptions);
|
||||
|
||||
if (newBitNode === 6) {
|
||||
Router.toPage(Page.BladeburnerCinematic);
|
||||
} else {
|
||||
|
38
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
38
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
@ -76,6 +76,8 @@ interface ResetInfo {
|
||||
ownedAugs: Map<string, number>;
|
||||
/** A map of owned SF to their levels. Keyed by the SF number. Map values are the SF level. */
|
||||
ownedSF: Map<number, number>;
|
||||
/** Current BitNode options */
|
||||
bitNodeOptions: BitNodeOptions;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
@ -1701,6 +1703,36 @@ export interface GraftingTask {
|
||||
*/
|
||||
export type Task = StudyTask | CompanyWorkTask | CreateProgramWorkTask | CrimeTask | FactionWorkTask | GraftingTask;
|
||||
|
||||
/**
|
||||
* Default value:
|
||||
* - sourceFileOverrides: an empty Map
|
||||
* - intelligenceOverride: undefined
|
||||
* - All boolean options: false
|
||||
*
|
||||
* If you specify intelligenceOverride, it must be a non-negative integer.
|
||||
*/
|
||||
export interface BitNodeOptions extends BitNodeBooleanOptions {
|
||||
sourceFileOverrides: Map<number, number>;
|
||||
intelligenceOverride: number | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export interface BitNodeBooleanOptions {
|
||||
restrictHomePCUpgrade: boolean;
|
||||
disableGang: boolean;
|
||||
disableCorporation: boolean;
|
||||
disableBladeburner: boolean;
|
||||
disable4SData: boolean;
|
||||
disableHacknetServer: boolean;
|
||||
disableSleeveExpAndAugmentation: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Singularity API
|
||||
* @remarks
|
||||
@ -2615,8 +2647,9 @@ export interface Singularity {
|
||||
*
|
||||
* @param nextBN - BN number to jump to
|
||||
* @param callbackScript - Name of the script to launch in the next BN.
|
||||
* @param bitNodeOptions - BitNode options for the next BN.
|
||||
*/
|
||||
b1tflum3(nextBN: number, callbackScript?: string): void;
|
||||
b1tflum3(nextBN: number, callbackScript?: string, bitNodeOptions?: BitNodeOptions): void;
|
||||
|
||||
/**
|
||||
* Destroy the w0r1d_d43m0n and move on to the next BN.
|
||||
@ -2629,8 +2662,9 @@ export interface Singularity {
|
||||
*
|
||||
* @param nextBN - BN number to jump to
|
||||
* @param callbackScript - Name of the script to launch in the next BN.
|
||||
* @param bitNodeOptions - BitNode options for the next BN.
|
||||
*/
|
||||
destroyW0r1dD43m0n(nextBN: number, callbackScript?: string): void;
|
||||
destroyW0r1dD43m0n(nextBN: number, callbackScript?: string, bitNodeOptions?: BitNodeOptions): void;
|
||||
|
||||
/**
|
||||
* Get the current work the player is doing.
|
||||
|
@ -164,7 +164,10 @@ export function purchaseRamForHomeComputer(): void {
|
||||
}
|
||||
|
||||
const homeComputer = Player.getHomeComputer();
|
||||
if (homeComputer.maxRam >= ServerConstants.HomeComputerMaxRam) {
|
||||
if (
|
||||
(Player.bitNodeOptions.restrictHomePCUpgrade && homeComputer.maxRam >= 128) ||
|
||||
homeComputer.maxRam >= ServerConstants.HomeComputerMaxRam
|
||||
) {
|
||||
dialogBoxCreate(`You cannot upgrade your home computer RAM because it is at its maximum possible value`);
|
||||
return;
|
||||
}
|
||||
|
@ -56,6 +56,7 @@ import { hash } from "../../hash/hash";
|
||||
import { Locations } from "../../Locations/Locations";
|
||||
import { useRerender } from "../../ui/React/hooks";
|
||||
import { playerHasDiscoveredGo } from "../../Go/effects/effect";
|
||||
import { knowAboutBitverse } from "../../BitNode/BitNodeUtils";
|
||||
|
||||
const RotatedDoubleArrowIcon = React.forwardRef(function RotatedDoubleArrowIcon(
|
||||
props: { color: "primary" | "secondary" | "error" },
|
||||
@ -141,12 +142,12 @@ export function SidebarRoot(props: { page: Page }): React.ReactElement {
|
||||
Player.factions.length > 0 ||
|
||||
Player.augmentations.length > 0 ||
|
||||
Player.queuedAugmentations.length > 0 ||
|
||||
Player.sourceFiles.size > 0;
|
||||
knowAboutBitverse();
|
||||
|
||||
const canOpenAugmentations =
|
||||
Player.augmentations.length > 0 ||
|
||||
Player.queuedAugmentations.length > 0 ||
|
||||
Player.sourceFiles.size > 0 ||
|
||||
knowAboutBitverse() ||
|
||||
Player.exploits.length > 0;
|
||||
|
||||
const canOpenSleeves = Player.sleeves.length > 0;
|
||||
|
@ -105,6 +105,9 @@ function PurchaseWseAccountButton(props: IProps): React.ReactElement {
|
||||
|
||||
function PurchaseTixApiAccessButton(props: IProps): React.ReactElement {
|
||||
function purchaseTixApiAccess(): void {
|
||||
if (Player.bitNodeOptions.disable4SData) {
|
||||
return;
|
||||
}
|
||||
if (Player.hasTixApiAccess) {
|
||||
return;
|
||||
}
|
||||
@ -135,6 +138,9 @@ function PurchaseTixApiAccessButton(props: IProps): React.ReactElement {
|
||||
|
||||
function Purchase4SMarketDataButton(props: IProps): React.ReactElement {
|
||||
function purchase4SMarketData(): void {
|
||||
if (Player.bitNodeOptions.disable4SData) {
|
||||
return;
|
||||
}
|
||||
if (Player.has4SData) {
|
||||
return;
|
||||
}
|
||||
@ -184,18 +190,22 @@ export function InfoAndPurchases(props: IProps): React.ReactElement {
|
||||
the TIX API lets you write code to create your own algorithmic/automated trading strategies.
|
||||
</Typography>
|
||||
<PurchaseTixApiAccessButton {...props} />
|
||||
<Typography variant="h5" color="primary">
|
||||
{FactionName.FourSigma} (4S) Market Data Feed
|
||||
</Typography>
|
||||
<Typography>
|
||||
{FactionName.FourSigma}'s (4S) Market Data Feed provides information about stocks that will help your trading
|
||||
strategies.
|
||||
<IconButton onClick={() => setHelpOpen(true)}>
|
||||
<HelpIcon />
|
||||
</IconButton>
|
||||
</Typography>
|
||||
<Purchase4SMarketDataTixApiAccessButton {...props} />
|
||||
<Purchase4SMarketDataButton {...props} />
|
||||
{!Player.bitNodeOptions.disable4SData && (
|
||||
<>
|
||||
<Typography variant="h5" color="primary">
|
||||
{FactionName.FourSigma} (4S) Market Data Feed
|
||||
</Typography>
|
||||
<Typography>
|
||||
{FactionName.FourSigma}'s (4S) Market Data Feed provides information about stocks that will help your
|
||||
trading strategies.
|
||||
<IconButton onClick={() => setHelpOpen(true)}>
|
||||
<HelpIcon />
|
||||
</IconButton>
|
||||
</Typography>
|
||||
<Purchase4SMarketDataTixApiAccessButton {...props} />
|
||||
<Purchase4SMarketDataButton {...props} />
|
||||
</>
|
||||
)}
|
||||
<Typography>
|
||||
Commission Fees: Every transaction you make has a{" "}
|
||||
<Money money={StockMarketConstants.StockMarketCommission} forPurchase={true} /> commission fee.
|
||||
|
@ -270,12 +270,12 @@ export function StockTicker(props: IProps): React.ReactElement {
|
||||
|
||||
// Whether the player has access to orders besides market orders (limit/stop)
|
||||
function hasOrderAccess(): boolean {
|
||||
return Player.bitNodeN === 8 || Player.sourceFileLvl(8) >= 3;
|
||||
return Player.bitNodeN === 8 || Player.activeSourceFileLvl(8) >= 3;
|
||||
}
|
||||
|
||||
// Whether the player has access to shorting stocks
|
||||
function hasShortAccess(): boolean {
|
||||
return Player.bitNodeN === 8 || Player.sourceFileLvl(8) >= 2;
|
||||
return Player.bitNodeN === 8 || Player.activeSourceFileLvl(8) >= 2;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -65,7 +65,7 @@ function ShortPosition(props: IProps): React.ReactElement {
|
||||
percentageGains = 0;
|
||||
}
|
||||
|
||||
if (Player.bitNodeN === 8 || Player.sourceFileLvl(8) >= 2) {
|
||||
if (Player.bitNodeN === 8 || Player.activeSourceFileLvl(8) >= 2) {
|
||||
return (
|
||||
<>
|
||||
<Box display="flex">
|
||||
|
@ -20,6 +20,20 @@ import { Company } from "../Company/Company";
|
||||
import { CompanyPosition } from "../Company/CompanyPosition";
|
||||
import { isMember } from "../utils/EnumHelper";
|
||||
|
||||
function processWorkStats(person: IPerson, workStats: WorkStats): WorkStats {
|
||||
// "person" can be a normal object that the player passes to NS APIs, so we cannot use `person instanceof Sleeve`.
|
||||
if (Player.bitNodeOptions.disableSleeveExpAndAugmentation && "shock" in person) {
|
||||
workStats.hackExp = 0;
|
||||
workStats.strExp = 0;
|
||||
workStats.defExp = 0;
|
||||
workStats.dexExp = 0;
|
||||
workStats.agiExp = 0;
|
||||
workStats.chaExp = 0;
|
||||
workStats.intExp = 0;
|
||||
}
|
||||
return workStats;
|
||||
}
|
||||
|
||||
const gameCPS = 1000 / CONSTANTS.MilliPerCycle; // 5 cycles per second
|
||||
export const FactionWorkStats: Record<FactionWorkType, WorkStats> = {
|
||||
[FactionWorkType.hacking]: newWorkStats({ hackExp: 2 }),
|
||||
@ -60,7 +74,7 @@ export function calculateCrimeWorkStats(person: IPerson, crime: Crime): WorkStat
|
||||
currentNodeMults.CrimeExpGain,
|
||||
false,
|
||||
);
|
||||
return gains;
|
||||
return processWorkStats(person, gains);
|
||||
}
|
||||
|
||||
/** @returns faction rep rate per cycle */
|
||||
@ -75,9 +89,9 @@ export const calculateFactionRep = (person: IPerson, type: FactionWorkType, favo
|
||||
|
||||
/** @returns per-cycle WorkStats */
|
||||
export function calculateFactionExp(person: IPerson, type: FactionWorkType): WorkStats {
|
||||
return scaleWorkStats(
|
||||
multWorkStats(FactionWorkStats[type], person.mults),
|
||||
currentNodeMults.FactionWorkExpGain / gameCPS,
|
||||
return processWorkStats(
|
||||
person,
|
||||
scaleWorkStats(multWorkStats(FactionWorkStats[type], person.mults), currentNodeMults.FactionWorkExpGain / gameCPS),
|
||||
);
|
||||
}
|
||||
|
||||
@ -102,7 +116,7 @@ export function calculateClassEarnings(person: IPerson, type: ClassType, locatio
|
||||
person.mults,
|
||||
);
|
||||
earnings.money = calculateCost(classs, location) / gameCPS;
|
||||
return earnings;
|
||||
return processWorkStats(person, earnings);
|
||||
}
|
||||
|
||||
/** @returns per-cycle WorkStats */
|
||||
@ -114,7 +128,7 @@ export const calculateCompanyWorkStats = (
|
||||
): WorkStats => {
|
||||
// If player has SF-11, calculate salary multiplier from favor
|
||||
const favorMult = isNaN(favor) ? 1 : 1 + favor / 100;
|
||||
const bn11Mult = Player.sourceFileLvl(11) > 0 ? favorMult : 1;
|
||||
const bn11Mult = Player.activeSourceFileLvl(11) > 0 ? favorMult : 1;
|
||||
|
||||
const gains = scaleWorkStats(
|
||||
multWorkStats(
|
||||
@ -138,5 +152,5 @@ export const calculateCompanyWorkStats = (
|
||||
|
||||
gains.reputation = jobPerformance * worker.mults.company_rep * favorMult * currentNodeMults.CompanyWorkRepGain;
|
||||
|
||||
return gains;
|
||||
return processWorkStats(worker, gains);
|
||||
};
|
||||
|
@ -17,6 +17,7 @@ import { StatsRow } from "./React/StatsRow";
|
||||
import { StatsTable } from "./React/StatsTable";
|
||||
import { useRerender } from "./React/hooks";
|
||||
import { getMaxFavor } from "../Go/effects/effect";
|
||||
import { canAccessBitNodeFeature, knowAboutBitverse } from "../BitNode/BitNodeUtils";
|
||||
|
||||
interface EmployersModalProps {
|
||||
open: boolean;
|
||||
@ -68,7 +69,7 @@ function MultiplierTable(props: MultTableProps): React.ReactElement {
|
||||
{props.rows.map((data) => {
|
||||
const { mult, value, effValue = null, color = props.color } = data;
|
||||
|
||||
if (effValue !== null && effValue !== value && Player.sourceFileLvl(5) > 0) {
|
||||
if (effValue !== null && effValue !== value && canAccessBitNodeFeature(5)) {
|
||||
return (
|
||||
<StatsRow key={mult} name={mult} color={color} data={{}}>
|
||||
<>
|
||||
@ -100,9 +101,9 @@ function MultiplierTable(props: MultTableProps): React.ReactElement {
|
||||
}
|
||||
|
||||
function CurrentBitNode(): React.ReactElement {
|
||||
if (Player.sourceFiles.size > 0) {
|
||||
if (knowAboutBitverse()) {
|
||||
const index = "BitNode" + Player.bitNodeN;
|
||||
const lvl = Math.min(Player.sourceFileLvl(Player.bitNodeN) + 1, Player.bitNodeN === 12 ? Infinity : 3);
|
||||
const lvl = Math.min(Player.sourceFileLvl(Player.bitNodeN) + 1, Player.bitNodeN === 12 ? Number.MAX_VALUE : 3);
|
||||
return (
|
||||
<Paper sx={{ mb: 1, p: 1 }}>
|
||||
<Typography variant="h5">
|
||||
@ -194,7 +195,7 @@ function MoneyModal({ open, onClose }: IMoneyModalProps): React.ReactElement {
|
||||
{convertMoneySourceTrackerToString(Player.moneySourceA)}
|
||||
</>
|
||||
);
|
||||
if (Player.sourceFiles.size > 0) {
|
||||
if (knowAboutBitverse()) {
|
||||
content = (
|
||||
<>
|
||||
{content}
|
||||
@ -224,7 +225,7 @@ export function CharacterStats(): React.ReactElement {
|
||||
const timeRows = [
|
||||
["Since last Augmentation installation", convertTimeMsToTimeElapsedString(Player.playtimeSinceLastAug)],
|
||||
];
|
||||
if (Player.sourceFiles.size > 0) {
|
||||
if (knowAboutBitverse()) {
|
||||
timeRows.push(["Since last Bitnode destroyed", convertTimeMsToTimeElapsedString(Player.playtimeSinceLastBitnode)]);
|
||||
}
|
||||
timeRows.push(["Total", convertTimeMsToTimeElapsedString(Player.totalPlaytime)]);
|
||||
@ -265,13 +266,11 @@ export function CharacterStats(): React.ReactElement {
|
||||
data={{ content: `${Player.purchasedServers.length} / ${getPurchaseServerLimit()}` }}
|
||||
/>
|
||||
<StatsRow
|
||||
name={`Hacknet ${Player.bitNodeN === 9 || Player.sourceFileLvl(9) > 0 ? "Servers" : "Nodes"} owned`}
|
||||
name={`Hacknet ${canAccessBitNodeFeature(9) ? "Servers" : "Nodes"} owned`}
|
||||
color={Settings.theme.primary}
|
||||
data={{
|
||||
content: `${Player.hacknetNodes.length}${
|
||||
Player.bitNodeN === 9 || Player.sourceFileLvl(9) > 0
|
||||
? ` / ${HacknetServerConstants.MaxServers}`
|
||||
: ""
|
||||
canAccessBitNodeFeature(9) ? ` / ${HacknetServerConstants.MaxServers}` : ""
|
||||
}`,
|
||||
}}
|
||||
/>
|
||||
@ -317,7 +316,7 @@ export function CharacterStats(): React.ReactElement {
|
||||
color={Settings.theme.cha}
|
||||
data={{ level: Player.skills.charisma, exp: Player.exp.charisma }}
|
||||
/>
|
||||
{Player.skills.intelligence > 0 && (Player.bitNodeN === 5 || Player.sourceFileLvl(5) > 0) && (
|
||||
{Player.skills.intelligence > 0 && canAccessBitNodeFeature(5) && (
|
||||
<StatsRow
|
||||
name="Intelligence"
|
||||
color={Settings.theme.int}
|
||||
@ -332,7 +331,7 @@ export function CharacterStats(): React.ReactElement {
|
||||
<Paper sx={{ p: 1, mb: 1 }}>
|
||||
<Typography variant="h5" color="primary" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
|
||||
Multipliers
|
||||
{Player.sourceFileLvl(5) > 0 && (
|
||||
{canAccessBitNodeFeature(5) && (
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
@ -553,15 +552,15 @@ export function CharacterStats(): React.ReactElement {
|
||||
},
|
||||
]}
|
||||
color={Settings.theme.primary}
|
||||
noMargin={!Player.sourceFileLvl(14) && Player.bitNodeN !== 14}
|
||||
noMargin={!canAccessBitNodeFeature(14)}
|
||||
/>
|
||||
)}
|
||||
{(Player.sourceFileLvl(14) || Player.bitNodeN === 14) && (
|
||||
{canAccessBitNodeFeature(14) && (
|
||||
<MultiplierTable
|
||||
rows={[
|
||||
{
|
||||
mult: "IPvGO Node Power bonus",
|
||||
value: Player.sourceFileLvl(14) ? 2 * currentNodeMults.GoPower : currentNodeMults.GoPower,
|
||||
value: Player.activeSourceFileLvl(14) ? 2 * currentNodeMults.GoPower : currentNodeMults.GoPower,
|
||||
},
|
||||
{
|
||||
mult: "IPvGO Max Favor",
|
||||
@ -590,7 +589,7 @@ export function CharacterStats(): React.ReactElement {
|
||||
|
||||
<CurrentBitNode />
|
||||
|
||||
{(Player.bitNodeN === 5 || Player.sourceFileLvl(5) > 0) && (
|
||||
{canAccessBitNodeFeature(5) && (
|
||||
<Paper sx={{ p: 1, mb: 1 }}>
|
||||
<Typography variant="h5">BitNode Multipliers</Typography>
|
||||
<BitNodeMultipliersDisplay n={Player.bitNodeN} />
|
||||
|
@ -118,6 +118,20 @@ export function Val({ name, color }: ValProps): React.ReactElement {
|
||||
return clearSubscription;
|
||||
}, [name]);
|
||||
|
||||
if (
|
||||
name === "Int" &&
|
||||
Player.bitNodeOptions.intelligenceOverride !== undefined &&
|
||||
Player.bitNodeOptions.intelligenceOverride < Player.skills.intelligence
|
||||
) {
|
||||
return (
|
||||
<Tooltip title={`Intelligence: ${formatSkill(Player.skills.intelligence)}`}>
|
||||
<Typography color={color}>
|
||||
{formatSkill(Player.bitNodeOptions.intelligenceOverride)}
|
||||
<sup>*</sup>
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
return <Typography color={color}>{formattedVals[name]()}</Typography>;
|
||||
}
|
||||
|
||||
|
@ -30,8 +30,8 @@ export function OptionSwitch({
|
||||
disabled={disabled}
|
||||
control={<Switch checked={value} onChange={handleSwitchChange} />}
|
||||
label={
|
||||
<Tooltip title={<Typography>{tooltip}</Typography>}>
|
||||
<Typography>{text}</Typography>
|
||||
<Tooltip title={<Typography component="div">{tooltip}</Typography>}>
|
||||
<Typography component="div">{text}</Typography>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
|
@ -56,6 +56,19 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = `
|
||||
"achievements": [],
|
||||
"augmentations": [],
|
||||
"bitNodeN": 1,
|
||||
"bitNodeOptions": {
|
||||
"disable4SData": false,
|
||||
"disableBladeburner": false,
|
||||
"disableCorporation": false,
|
||||
"disableGang": false,
|
||||
"disableHacknetServer": false,
|
||||
"disableSleeveExpAndAugmentation": false,
|
||||
"restrictHomePCUpgrade": false,
|
||||
"sourceFileOverrides": {
|
||||
"ctor": "JSONMap",
|
||||
"data": [],
|
||||
},
|
||||
},
|
||||
"bladeburner": {
|
||||
"ctor": "Bladeburner",
|
||||
"data": {
|
||||
|
Loading…
Reference in New Issue
Block a user