mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-20 08:13:50 +02:00
448 lines
16 KiB
TypeScript
448 lines
16 KiB
TypeScript
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 "1".
|
|
if (event.target.value === "") {
|
|
callbacks.setIntelligenceOverride(1);
|
|
return;
|
|
}
|
|
const value = Number.parseInt(event.target.value);
|
|
if (!Number.isInteger(value) || value < 1) {
|
|
return;
|
|
}
|
|
callbacks.setIntelligenceOverride(value);
|
|
}}
|
|
></TextField>
|
|
</Typography>
|
|
</>
|
|
}
|
|
tooltip={
|
|
<>
|
|
<Typography component="div">
|
|
Your intelligence and your Sleeves' intelligence will be temporarily set to this value if it is lower than
|
|
their current values. For example:
|
|
<ul>
|
|
<li>
|
|
If your intelligence is 1000 and you set this value to 500, your intelligence will be temporarily set to
|
|
500.
|
|
</li>
|
|
<li>
|
|
If a Sleeve's intelligence is 200 and you set this value to 500, that Sleeve's intelligence is still
|
|
200.
|
|
</li>
|
|
</ul>
|
|
</Typography>
|
|
<Typography>
|
|
Note that you still gain intelligence experience as normal.
|
|
<br />
|
|
For example, suppose you have 1e6 intelligence exp (intelligence skill = 242) and set the intelligence
|
|
override to 100. At the start of the BitNode, your intelligence skill will be set to 100 (equivalent to
|
|
~11255.318 intelligence exp).
|
|
<br />
|
|
If you gain 500e3 intelligence exp during the BitNode, your intelligence skill will increase to 220 (total
|
|
intelligence exp = 11255 + 500e3 = 511255). After performing bitflume, the exp gained during the BitNode is
|
|
added to your original exp. Your intelligence skill will then become 255 (total intelligence exp = 1e6 +
|
|
500e3 = 1.5e6).
|
|
</Typography>
|
|
<br />
|
|
<Typography>
|
|
The overridden intelligence will be shown in the character overview. 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" }}>
|
|
<Typography>
|
|
These options enable unique gameplay that is intended for experienced players. If you are a new player, you
|
|
can safely ignore these options and come back to try them later.
|
|
</Typography>
|
|
<br />
|
|
<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, regardless of BitNode and SF level"
|
|
/>
|
|
<OptionSwitch
|
|
disabled={getSfLevel(3) === 0 && targetBitNode !== 3}
|
|
checked={bitNodeBooleanOptions.disableCorporation}
|
|
onChange={(value) => {
|
|
callbacks.setBooleanOption("disableCorporation", value);
|
|
}}
|
|
text="Disable Corporation"
|
|
tooltip="Disable Corporation, regardless of BitNode and SF level"
|
|
/>
|
|
<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, regardless of BitNode and SF level"
|
|
/>
|
|
<OptionSwitch
|
|
checked={bitNodeBooleanOptions.disable4SData}
|
|
onChange={(value) => {
|
|
callbacks.setBooleanOption("disable4SData", value);
|
|
}}
|
|
text="Disable 4S Market Data"
|
|
tooltip="Disable 4S Market Data, regardless of BitNode and SF level"
|
|
/>
|
|
<OptionSwitch
|
|
disabled={getSfLevel(9) === 0 && targetBitNode !== 9}
|
|
checked={bitNodeBooleanOptions.disableHacknetServer}
|
|
onChange={(value) => {
|
|
callbacks.setBooleanOption("disableHacknetServer", value);
|
|
}}
|
|
text="Disable Hacknet Server"
|
|
tooltip="Disable Hacknet Server, regardless of BitNode and SF level. 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>
|
|
);
|
|
}
|