mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-19 22:23:51 +01:00
Bulk of design overhaul
This commit is contained in:
parent
02d0d0df53
commit
9db90709fd
355
src/GameOptions/ui/CurrentOptionsPage.tsx
Normal file
355
src/GameOptions/ui/CurrentOptionsPage.tsx
Normal file
@ -0,0 +1,355 @@
|
||||
import { MenuItem, Select, SelectChangeEvent, TextField, Tooltip, Typography } from "@mui/material";
|
||||
import React, { useState } from "react";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { OptionSwitch } from "../../ui/React/OptionSwitch";
|
||||
import { formatTime } from "../../utils/helpers/formatTime";
|
||||
import { GameOptionsTabs } from "../GameOptionsTabs";
|
||||
import { GameOptionsPage } from "./GameOptionsPage";
|
||||
import { OptionsSlider } from "./OptionsSlider";
|
||||
|
||||
interface IProps {
|
||||
currentTab: GameOptionsTabs;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
|
||||
const [execTime, setExecTime] = useState(Settings.CodeInstructionRunTime);
|
||||
const [recentScriptsSize, setRecentScriptsSize] = useState(Settings.MaxRecentScriptsCapacity);
|
||||
const [logSize, setLogSize] = useState(Settings.MaxLogCapacity);
|
||||
const [portSize, setPortSize] = useState(Settings.MaxPortCapacity);
|
||||
const [terminalSize, setTerminalSize] = useState(Settings.MaxTerminalCapacity);
|
||||
const [autosaveInterval, setAutosaveInterval] = useState(Settings.AutosaveInterval);
|
||||
const [timestampFormat, setTimestampFormat] = useState(Settings.TimestampsFormat);
|
||||
const [locale, setLocale] = useState(Settings.Locale);
|
||||
|
||||
function handleExecTimeChange(event: any, newValue: number | number[]): void {
|
||||
setExecTime(newValue as number);
|
||||
Settings.CodeInstructionRunTime = newValue as number;
|
||||
}
|
||||
|
||||
function handleRecentScriptsSizeChange(event: any, newValue: number | number[]): void {
|
||||
setRecentScriptsSize(newValue as number);
|
||||
Settings.MaxRecentScriptsCapacity = newValue as number;
|
||||
}
|
||||
|
||||
function handleLogSizeChange(event: any, newValue: number | number[]): void {
|
||||
setLogSize(newValue as number);
|
||||
Settings.MaxLogCapacity = newValue as number;
|
||||
}
|
||||
|
||||
function handlePortSizeChange(event: any, newValue: number | number[]): void {
|
||||
setPortSize(newValue as number);
|
||||
Settings.MaxPortCapacity = newValue as number;
|
||||
}
|
||||
|
||||
function handleTerminalSizeChange(event: any, newValue: number | number[]): void {
|
||||
setTerminalSize(newValue as number);
|
||||
Settings.MaxTerminalCapacity = newValue as number;
|
||||
}
|
||||
|
||||
function handleAutosaveIntervalChange(event: any, newValue: number | number[]): void {
|
||||
setAutosaveInterval(newValue as number);
|
||||
Settings.AutosaveInterval = newValue as number;
|
||||
}
|
||||
|
||||
function handleLocaleChange(event: SelectChangeEvent<string>): void {
|
||||
setLocale(event.target.value as string);
|
||||
Settings.Locale = event.target.value as string;
|
||||
}
|
||||
|
||||
function handleTimestampFormatChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setTimestampFormat(event.target.value);
|
||||
Settings.TimestampsFormat = event.target.value;
|
||||
}
|
||||
|
||||
const pages = {
|
||||
[GameOptionsTabs.SYSTEM]: (
|
||||
<GameOptionsPage title="System">
|
||||
<OptionsSlider
|
||||
label=".script exec time (ms)"
|
||||
value={execTime}
|
||||
callback={handleExecTimeChange}
|
||||
step={1}
|
||||
min={5}
|
||||
max={100}
|
||||
tooltip={
|
||||
<>
|
||||
The minimum number of milliseconds it takes to execute an operation in Netscript. Setting this too low can
|
||||
result in poor performance if you have many scripts running.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<OptionsSlider
|
||||
label="Recently killed scripts size"
|
||||
value={recentScriptsSize}
|
||||
callback={handleRecentScriptsSizeChange}
|
||||
step={25}
|
||||
min={0}
|
||||
max={500}
|
||||
tooltip={
|
||||
<>
|
||||
The maximum number of lines a script's logs can hold. Setting this too high can cause the game to use a
|
||||
lot of memory if you have many scripts running.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<OptionsSlider
|
||||
label="Netscript log size"
|
||||
value={logSize}
|
||||
callback={handleLogSizeChange}
|
||||
step={20}
|
||||
min={20}
|
||||
max={500}
|
||||
tooltip={
|
||||
<>
|
||||
The maximum number of lines a script's logs can hold. Setting this too high can cause the game to use a
|
||||
lot of memory if you have many scripts running.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<OptionsSlider
|
||||
label="Netscript port size"
|
||||
value={portSize}
|
||||
callback={handlePortSizeChange}
|
||||
step={1}
|
||||
min={20}
|
||||
max={100}
|
||||
tooltip={
|
||||
<>
|
||||
The maximum number of entries that can be written to a port using Netscript's write() function. Setting
|
||||
this too high can cause the game to use a lot of memory.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<OptionsSlider
|
||||
label="Terminal capacity"
|
||||
value={terminalSize}
|
||||
callback={handleTerminalSizeChange}
|
||||
step={50}
|
||||
min={50}
|
||||
max={500}
|
||||
tooltip={
|
||||
<>
|
||||
The maximum number of entries that can be written to the terminal. Setting this too high can cause the
|
||||
game to use a lot of memory.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<OptionsSlider
|
||||
label="Autosave interval (s)"
|
||||
value={autosaveInterval}
|
||||
callback={handleAutosaveIntervalChange}
|
||||
step={30}
|
||||
min={0}
|
||||
max={600}
|
||||
tooltip={<>The time (in seconds) between each autosave. Set to 0 to disable autosave.</>}
|
||||
/>
|
||||
</GameOptionsPage>
|
||||
),
|
||||
[GameOptionsTabs.INTERFACE]: (
|
||||
<GameOptionsPage title="Interface">
|
||||
<OptionSwitch
|
||||
checked={Settings.DisableASCIIArt}
|
||||
onChange={(newValue) => (Settings.DisableASCIIArt = newValue)}
|
||||
text="Disable ascii art"
|
||||
tooltip={<>If this is set all ASCII art will be disabled.</>}
|
||||
/>
|
||||
<OptionSwitch
|
||||
checked={Settings.DisableTextEffects}
|
||||
onChange={(newValue) => (Settings.DisableTextEffects = newValue)}
|
||||
text="Disable text effects"
|
||||
tooltip={
|
||||
<>
|
||||
If this is set, text effects will not be displayed. This can help if text is difficult to read in certain
|
||||
areas.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<OptionSwitch
|
||||
checked={Settings.DisableOverviewProgressBars}
|
||||
onChange={(newValue) => (Settings.DisableOverviewProgressBars = newValue)}
|
||||
text="Disable Overview Progress Bars"
|
||||
tooltip={<>If this is set, the progress bars in the character overview will be hidden.</>}
|
||||
/>
|
||||
<OptionSwitch
|
||||
checked={Settings.UseIEC60027_2}
|
||||
onChange={(newValue) => (Settings.UseIEC60027_2 = newValue)}
|
||||
text="Use GiB instead of GB"
|
||||
tooltip={
|
||||
<>If this is set all references to memory will use GiB instead of GB, in accordance with IEC 60027-2.</>
|
||||
}
|
||||
/>
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
Terminal commands and log entries will be timestamped. See https://date-fns.org/docs/Getting-Started/
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<span>
|
||||
<TextField
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<Typography
|
||||
color={
|
||||
formatTime(timestampFormat) === "format error" && timestampFormat !== "" ? "error" : "success"
|
||||
}
|
||||
>
|
||||
Timestamp format:
|
||||
</Typography>
|
||||
),
|
||||
}}
|
||||
value={timestampFormat}
|
||||
onChange={handleTimestampFormatChange}
|
||||
placeholder="yyyy-MM-dd hh:mm:ss"
|
||||
/>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<>
|
||||
<Tooltip title={<Typography>Sets the locale for displaying numbers.</Typography>}>
|
||||
<Typography>Locale </Typography>
|
||||
</Tooltip>
|
||||
<Select value={locale} onChange={handleLocaleChange}>
|
||||
<MenuItem value="en">en</MenuItem>
|
||||
<MenuItem value="bg">bg</MenuItem>
|
||||
<MenuItem value="cs">cs</MenuItem>
|
||||
<MenuItem value="da-dk">da-dk</MenuItem>
|
||||
<MenuItem value="de">de</MenuItem>
|
||||
<MenuItem value="en-au">en-au</MenuItem>
|
||||
<MenuItem value="en-gb">en-gb</MenuItem>
|
||||
<MenuItem value="es">es</MenuItem>
|
||||
<MenuItem value="fr">fr</MenuItem>
|
||||
<MenuItem value="hu">hu</MenuItem>
|
||||
<MenuItem value="it">it</MenuItem>
|
||||
<MenuItem value="lv">lv</MenuItem>
|
||||
<MenuItem value="no">no</MenuItem>
|
||||
<MenuItem value="pl">pl</MenuItem>
|
||||
<MenuItem value="ru">ru</MenuItem>
|
||||
</Select>
|
||||
</>
|
||||
</GameOptionsPage>
|
||||
),
|
||||
[GameOptionsTabs.GAMEPLAY]: (
|
||||
<GameOptionsPage title="Gameplay">
|
||||
<OptionSwitch
|
||||
checked={Settings.SuppressMessages}
|
||||
onChange={(newValue) => (Settings.SuppressMessages = newValue)}
|
||||
text="Suppress story messages"
|
||||
tooltip={
|
||||
<>
|
||||
If this is set, then any messages you receive will not appear as popups on the screen. They will still get
|
||||
sent to your home computer as '.msg' files and can be viewed with the 'cat' Terminal command.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<OptionSwitch
|
||||
checked={Settings.SuppressFactionInvites}
|
||||
onChange={(newValue) => (Settings.SuppressFactionInvites = newValue)}
|
||||
text="Suppress faction invites"
|
||||
tooltip={
|
||||
<>
|
||||
If this is set, then any faction invites you receive will not appear as popups on the screen. Your
|
||||
outstanding faction invites can be viewed in the 'Factions' page.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<OptionSwitch
|
||||
checked={Settings.SuppressTravelConfirmation}
|
||||
onChange={(newValue) => (Settings.SuppressTravelConfirmation = newValue)}
|
||||
text="Suppress travel confirmations"
|
||||
tooltip={
|
||||
<>
|
||||
If this is set, the confirmation message before traveling will not show up. You will automatically be
|
||||
deducted the travel cost as soon as you click.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<OptionSwitch
|
||||
checked={Settings.SuppressBuyAugmentationConfirmation}
|
||||
onChange={(newValue) => (Settings.SuppressBuyAugmentationConfirmation = newValue)}
|
||||
text="Suppress augmentations confirmation"
|
||||
tooltip={<>If this is set, the confirmation message before buying augmentation will not show up.</>}
|
||||
/>
|
||||
<OptionSwitch
|
||||
checked={Settings.SuppressTIXPopup}
|
||||
onChange={(newValue) => (Settings.SuppressTIXPopup = newValue)}
|
||||
text="Suppress TIX messages"
|
||||
tooltip={<>If this is set, the stock market will never create any popup.</>}
|
||||
/>
|
||||
<OptionSwitch
|
||||
checked={Settings.SuppressSavedGameToast}
|
||||
onChange={(newValue) => (Settings.SuppressSavedGameToast = newValue)}
|
||||
text="Suppress Auto-Save Game Toast"
|
||||
tooltip={<>If this is set, there will be no "Game Saved!" toast appearing after an auto-save.</>}
|
||||
/>
|
||||
<OptionSwitch
|
||||
checked={Settings.SuppressAutosaveDisabledWarnings}
|
||||
onChange={(newValue) => (Settings.SuppressAutosaveDisabledWarnings = newValue)}
|
||||
text="Suppress Auto-Save Disabled Warning"
|
||||
tooltip={<>If this is set, there will be no warning triggered when auto-save is disabled (at 0).</>}
|
||||
/>
|
||||
<OptionSwitch
|
||||
checked={Settings.SaveGameOnFileSave}
|
||||
onChange={(newValue) => (Settings.SaveGameOnFileSave = newValue)}
|
||||
text="Save game on file save"
|
||||
tooltip={<>Save your game any time a file is saved in the script editor.</>}
|
||||
/>
|
||||
{props.player.bladeburner && (
|
||||
<OptionSwitch
|
||||
checked={Settings.SuppressBladeburnerPopup}
|
||||
onChange={(newValue) => (Settings.SuppressBladeburnerPopup = newValue)}
|
||||
text="Suppress bladeburner popup"
|
||||
tooltip={
|
||||
<>
|
||||
If this is set, then having your Bladeburner actions interrupted by being busy with something else will
|
||||
not display a popup message.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</GameOptionsPage>
|
||||
),
|
||||
[GameOptionsTabs.MISC]: (
|
||||
<GameOptionsPage title="Misc">
|
||||
<OptionSwitch
|
||||
checked={Settings.DisableHotkeys}
|
||||
onChange={(newValue) => (Settings.DisableHotkeys = newValue)}
|
||||
text="Disable hotkeys"
|
||||
tooltip={
|
||||
<>
|
||||
If this is set, then most hotkeys (keyboard shortcuts) in the game are disabled. This includes Terminal
|
||||
commands, hotkeys to navigate between different parts of the game, and the "Save and Close (Ctrl + b)"
|
||||
hotkey in the Text Editor.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<OptionSwitch
|
||||
checked={Settings.EnableBashHotkeys}
|
||||
onChange={(newValue) => (Settings.EnableBashHotkeys = newValue)}
|
||||
text="Enable bash hotkeys"
|
||||
tooltip={
|
||||
<>
|
||||
Improved Bash emulation mode. Setting this to 1 enables several new Terminal shortcuts and features that
|
||||
more closely resemble a real Bash-style shell. Note that when this mode is enabled, the default browser
|
||||
shortcuts are overriden by the new Bash shortcuts.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<OptionSwitch
|
||||
checked={Settings.ExcludeRunningScriptsFromSave}
|
||||
onChange={(newValue) => (Settings.ExcludeRunningScriptsFromSave = newValue)}
|
||||
text="Exclude Running Scripts from Save"
|
||||
tooltip={
|
||||
<>
|
||||
If this is set, the save file will exclude all running scripts. This is only useful if your save is
|
||||
lagging a lot. You'll have to restart your script every time you launch the game.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</GameOptionsPage>
|
||||
),
|
||||
};
|
||||
|
||||
return pages[props.currentTab];
|
||||
};
|
106
src/GameOptions/ui/GameOptionsRoot.tsx
Normal file
106
src/GameOptions/ui/GameOptionsRoot.tsx
Normal file
@ -0,0 +1,106 @@
|
||||
import DownloadIcon from "@mui/icons-material/Download";
|
||||
import PaletteIcon from "@mui/icons-material/Palette";
|
||||
import SaveIcon from "@mui/icons-material/Save";
|
||||
import UploadIcon from "@mui/icons-material/Upload";
|
||||
import { Box, Button, Container, Grid, Link, ListItem, Tooltip, Typography } from "@mui/material";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { FileDiagnosticModal } from "../../Diagnostic/FileDiagnosticModal";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { ImportData, saveObject } from "../../SaveObject";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { StyleEditorButton } from "../../Themes/ui/StyleEditorButton";
|
||||
import { ThemeEditorButton } from "../../Themes/ui/ThemeEditorButton";
|
||||
import { ConfirmationModal } from "../../ui/React/ConfirmationModal";
|
||||
import { DeleteGameButton } from "../../ui/React/DeleteGameButton";
|
||||
import { SnackbarEvents, ToastVariant } from "../../ui/React/Snackbar";
|
||||
import { SoftResetButton } from "../../ui/React/SoftResetButton";
|
||||
import { IRouter } from "../../ui/Router";
|
||||
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
|
||||
import { GameOptionsTabs } from "../GameOptionsTabs";
|
||||
import { CurrentOptionsPage } from "./CurrentOptionsPage";
|
||||
import { GameOptionsSidebar } from "./GameOptionsSidebar";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
width: 50,
|
||||
padding: theme.spacing(2),
|
||||
userSelect: "none",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
interface IProps {
|
||||
player: IPlayer;
|
||||
router: IRouter;
|
||||
save: () => void;
|
||||
export: () => void;
|
||||
forceKill: () => void;
|
||||
softReset: () => void;
|
||||
}
|
||||
|
||||
export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
const classes = useStyles();
|
||||
const [currentTab, setCurrentTab] = useState(GameOptionsTabs.SYSTEM);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container disableGutters maxWidth="lg" sx={{ mx: 0 }}>
|
||||
<Typography variant="h4">Options</Typography>
|
||||
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 3fr", gap: 1 }}>
|
||||
<GameOptionsSidebar
|
||||
tab={currentTab}
|
||||
setTab={(tab: GameOptionsTabs) => setCurrentTab(tab)}
|
||||
player={props.player}
|
||||
router={props.router}
|
||||
save={props.save}
|
||||
export={props.export}
|
||||
forceKill={props.forceKill}
|
||||
softReset={props.softReset}
|
||||
/>
|
||||
<CurrentOptionsPage currentTab={currentTab} player={props.player} />
|
||||
</Box>
|
||||
</Container>
|
||||
|
||||
<div className={classes.root} style={{ width: "90%" }}>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
{!location.href.startsWith("file://") && (
|
||||
<>
|
||||
<ListItem>
|
||||
<Typography>danielyxie / BigD (Original developer): </Typography>
|
||||
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
|
||||
<input type="hidden" name="cmd" value="_s-xclick" />
|
||||
<input
|
||||
type="hidden"
|
||||
name="encrypted"
|
||||
value="-----BEGIN PKCS7-----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-----END PKCS7-----"
|
||||
/>
|
||||
<input
|
||||
type="image"
|
||||
src="https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif"
|
||||
name="submit"
|
||||
alt="PayPal - The safer, easier way to pay online!"
|
||||
/>
|
||||
</form>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<Typography>
|
||||
hydroflame (Current maintainer):{" "}
|
||||
<Link href="https://www.google.com/search?q=Where+to+donate+blood+near+me%3F" target="_blank">
|
||||
Donate blood!
|
||||
</Link>{" "}
|
||||
</Typography>
|
||||
</ListItem>
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
238
src/GameOptions/ui/GameOptionsSidebar.tsx
Normal file
238
src/GameOptions/ui/GameOptionsSidebar.tsx
Normal file
@ -0,0 +1,238 @@
|
||||
import { Download, Palette, Save, Upload } from "@mui/icons-material";
|
||||
import { Box, Button, Link, List, ListItemButton, Paper, Tooltip, Typography } from "@mui/material";
|
||||
import { default as React, useRef, useState } from "react";
|
||||
import { FileDiagnosticModal } from "../../Diagnostic/FileDiagnosticModal";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { ImportData, saveObject } from "../../SaveObject";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { StyleEditorButton } from "../../Themes/ui/StyleEditorButton";
|
||||
import { ThemeEditorButton } from "../../Themes/ui/ThemeEditorButton";
|
||||
import { ConfirmationModal } from "../../ui/React/ConfirmationModal";
|
||||
import { DeleteGameButton } from "../../ui/React/DeleteGameButton";
|
||||
import { SnackbarEvents, ToastVariant } from "../../ui/React/Snackbar";
|
||||
import { SoftResetButton } from "../../ui/React/SoftResetButton";
|
||||
import { IRouter } from "../../ui/Router";
|
||||
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
|
||||
import { GameOptionsTabs } from "../GameOptionsTabs";
|
||||
|
||||
interface IProps {
|
||||
tab: GameOptionsTabs;
|
||||
setTab: (tab: GameOptionsTabs) => void;
|
||||
player: IPlayer;
|
||||
router: IRouter;
|
||||
save: () => void;
|
||||
export: () => void;
|
||||
forceKill: () => void;
|
||||
softReset: () => void;
|
||||
}
|
||||
|
||||
interface ITabProps {
|
||||
sideBarProps: IProps;
|
||||
tab: GameOptionsTabs;
|
||||
tabName: string;
|
||||
}
|
||||
|
||||
const SideBarTab = (props: ITabProps): React.ReactElement => {
|
||||
return (
|
||||
<ListItemButton
|
||||
selected={props.sideBarProps.tab === props.tab}
|
||||
onClick={() => props.sideBarProps.setTab(props.tab)}
|
||||
>
|
||||
<Typography>{props.tabName}</Typography>
|
||||
</ListItemButton>
|
||||
);
|
||||
};
|
||||
|
||||
export const GameOptionsSidebar = (props: IProps): React.ReactElement => {
|
||||
const importInput = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [diagnosticOpen, setDiagnosticOpen] = useState(false);
|
||||
const [importSaveOpen, setImportSaveOpen] = useState(false);
|
||||
const [importData, setImportData] = useState<ImportData | null>(null);
|
||||
|
||||
function startImport(): void {
|
||||
if (!window.File || !window.FileReader || !window.FileList || !window.Blob) return;
|
||||
const ii = importInput.current;
|
||||
if (ii === null) throw new Error("import input should not be null");
|
||||
ii.click();
|
||||
}
|
||||
|
||||
async function onImport(event: React.ChangeEvent<HTMLInputElement>): Promise<void> {
|
||||
try {
|
||||
const base64Save = await saveObject.getImportStringFromFile(event.target.files);
|
||||
const data = await saveObject.getImportDataFromString(base64Save);
|
||||
setImportData(data);
|
||||
setImportSaveOpen(true);
|
||||
} catch (ex: any) {
|
||||
SnackbarEvents.emit(ex.toString(), ToastVariant.ERROR, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmedImportGame(): Promise<void> {
|
||||
if (!importData) return;
|
||||
|
||||
try {
|
||||
await saveObject.importGame(importData.base64);
|
||||
} catch (ex: any) {
|
||||
SnackbarEvents.emit(ex.toString(), ToastVariant.ERROR, 5000);
|
||||
}
|
||||
|
||||
setImportSaveOpen(false);
|
||||
setImportData(null);
|
||||
}
|
||||
|
||||
function compareSaveGame(): void {
|
||||
if (!importData) return;
|
||||
props.router.toImportSave(importData.base64);
|
||||
setImportSaveOpen(false);
|
||||
setImportData(null);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Paper sx={{ height: "fit-content", mb: 1 }}>
|
||||
<List>
|
||||
<SideBarTab sideBarProps={props} tab={GameOptionsTabs.SYSTEM} tabName="System" />
|
||||
<SideBarTab sideBarProps={props} tab={GameOptionsTabs.GAMEPLAY} tabName="Gameplay" />
|
||||
<SideBarTab sideBarProps={props} tab={GameOptionsTabs.INTERFACE} tabName="Interface" />
|
||||
<SideBarTab sideBarProps={props} tab={GameOptionsTabs.MISC} tabName="Misc" />
|
||||
</List>
|
||||
</Paper>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
width: "fit-content",
|
||||
height: "fit-content",
|
||||
gridTemplateAreas: `"save delete"
|
||||
"export import"
|
||||
"kill kill"
|
||||
"reset diagnose"
|
||||
"browse browse"
|
||||
"theme style"
|
||||
"links links"`,
|
||||
gridTemplateColumns: "1fr 1fr",
|
||||
}}
|
||||
>
|
||||
<Button onClick={() => props.save()} startIcon={<Save />} sx={{ gridArea: "save" }}>
|
||||
Save Game
|
||||
</Button>
|
||||
<Box sx={{ gridArea: "delete", "& .MuiButton-root": { width: "100%" } }}>
|
||||
<DeleteGameButton />
|
||||
</Box>
|
||||
<Tooltip title={<Typography>Export your game to a text file.</Typography>}>
|
||||
<Button onClick={() => props.export()} startIcon={<Download />} sx={{ gridArea: "export" }}>
|
||||
Export Game
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
Import your game from a text file.
|
||||
<br />
|
||||
This will <strong>overwrite</strong> your current game. Back it up first!
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Button onClick={startImport} startIcon={<Upload />} sx={{ gridArea: "import" }}>
|
||||
Import Game
|
||||
<input ref={importInput} id="import-game-file-selector" type="file" hidden onChange={onImport} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<ConfirmationModal
|
||||
open={importSaveOpen}
|
||||
onClose={() => setImportSaveOpen(false)}
|
||||
onConfirm={() => confirmedImportGame()}
|
||||
additionalButton={<Button onClick={compareSaveGame}>Compare Save</Button>}
|
||||
confirmationText={
|
||||
<>
|
||||
Importing a new game will <strong>completely wipe</strong> the current data!
|
||||
<br />
|
||||
<br />
|
||||
Make sure to have a backup of your current save file before importing.
|
||||
<br />
|
||||
The file you are attempting to import seems valid.
|
||||
{(importData?.playerData?.lastSave ?? 0) > 0 && (
|
||||
<>
|
||||
<br />
|
||||
<br />
|
||||
The export date of the save file is{" "}
|
||||
<strong>{new Date(importData?.playerData?.lastSave ?? 0).toLocaleString()}</strong>
|
||||
</>
|
||||
)}
|
||||
{(importData?.playerData?.totalPlaytime ?? 0) > 0 && (
|
||||
<>
|
||||
<br />
|
||||
<br />
|
||||
Total play time of imported game:{" "}
|
||||
{convertTimeMsToTimeElapsedString(importData?.playerData?.totalPlaytime ?? 0)}
|
||||
</>
|
||||
)}
|
||||
<br />
|
||||
<br />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
Forcefully kill all active running scripts, in case there is a bug or some unexpected issue with the game.
|
||||
After using this, save the game and then reload the page. This is different then normal kill in that
|
||||
normal kill will tell the script to shut down while force kill just removes the references to it (and it
|
||||
should crash on it's own). This will not remove the files on your computer. Just forcefully kill all
|
||||
running instance of all scripts.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Button onClick={() => props.forceKill()} sx={{ gridArea: "kill" }}>
|
||||
Force kill all active scripts
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<SoftResetButton noConfirmation={Settings.SuppressBuyAugmentationConfirmation} onTriggered={props.softReset} />
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
If your save file is extremely big you can use this button to view a map of all the files on every server.
|
||||
Be careful there might be spoilers.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Button onClick={() => setDiagnosticOpen(true)} sx={{ gridArea: "diagnose" }}>
|
||||
Diagnose files
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title="Head to the theme browser to see a collection of prebuilt themes.">
|
||||
<Button startIcon={<Palette />} onClick={() => props.router.toThemeBrowser()} sx={{ gridArea: "browse" }}>
|
||||
Theme Browser
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Box sx={{ gridArea: "theme", "& .MuiButton-root": { width: "100%" } }}>
|
||||
<ThemeEditorButton router={props.router} />
|
||||
</Box>
|
||||
<Box sx={{ gridArea: "style", "& .MuiButton-root": { width: "100%" } }}>
|
||||
<StyleEditorButton />
|
||||
</Box>
|
||||
<Box sx={{ gridArea: "links" }}>
|
||||
<Link href="https://github.com/danielyxie/bitburner/issues/new" target="_blank">
|
||||
<Typography>Report bug</Typography>
|
||||
</Link>
|
||||
<Link href="https://bitburner.readthedocs.io/en/latest/changelog.html" target="_blank">
|
||||
<Typography>Changelog</Typography>
|
||||
</Link>
|
||||
<Link href="https://bitburner.readthedocs.io/en/latest/index.html" target="_blank">
|
||||
<Typography>Documentation</Typography>
|
||||
</Link>
|
||||
<Link href="https://discord.gg/TFc3hKD" target="_blank">
|
||||
<Typography>Discord</Typography>
|
||||
</Link>
|
||||
<Link href="https://www.reddit.com/r/bitburner" target="_blank">
|
||||
<Typography>Reddit</Typography>
|
||||
</Link>
|
||||
<Link href="https://plaza.dsolver.ca/games/bitburner" target="_blank">
|
||||
<Typography>Incremental game plaza</Typography>
|
||||
</Link>
|
||||
</Box>
|
||||
</Box>
|
||||
<FileDiagnosticModal open={diagnosticOpen} onClose={() => setDiagnosticOpen(false)} />
|
||||
</Box>
|
||||
);
|
||||
};
|
36
src/GameOptions/ui/OptionsSlider.tsx
Normal file
36
src/GameOptions/ui/OptionsSlider.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { Slider, Tooltip, Typography, Box } from "@mui/material";
|
||||
import React from "react";
|
||||
|
||||
interface IProps {
|
||||
value: any;
|
||||
callback: (event: any, newValue: number | number[]) => void;
|
||||
step: number;
|
||||
min: number;
|
||||
max: number;
|
||||
tooltip: React.ReactElement;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export const OptionsSlider = (props: IProps): React.ReactElement => {
|
||||
return (
|
||||
<Box>
|
||||
<Tooltip title={<Typography>{props.tooltip}</Typography>}>
|
||||
<Typography>{props.label}</Typography>
|
||||
</Tooltip>
|
||||
<Slider
|
||||
value={props.value}
|
||||
onChange={props.callback}
|
||||
step={props.step}
|
||||
min={props.min}
|
||||
max={props.max}
|
||||
valueLabelDisplay="auto"
|
||||
sx={{
|
||||
"& .MuiSlider-thumb": {
|
||||
height: "12px",
|
||||
width: "12px",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
@ -44,7 +44,7 @@ import { CorporationRoot } from "../Corporation/ui/CorporationRoot";
|
||||
import { InfiltrationRoot } from "../Infiltration/ui/InfiltrationRoot";
|
||||
import { GraftingRoot } from "../PersonObjects/Grafting/ui/GraftingRoot";
|
||||
import { WorkInProgressRoot } from "./WorkInProgressRoot";
|
||||
import { GameOptionsRoot } from "./React/GameOptionsRoot";
|
||||
import { GameOptionsRoot } from "../GameOptions/ui/GameOptionsRoot";
|
||||
import { SleeveRoot } from "../PersonObjects/Sleeve/ui/SleeveRoot";
|
||||
import { HacknetRoot } from "../Hacknet/ui/HacknetRoot";
|
||||
import { GenericLocation } from "../Locations/ui/GenericLocation";
|
||||
|
@ -1,667 +0,0 @@
|
||||
import React, { useState, useRef } from "react";
|
||||
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Slider from "@mui/material/Slider";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Select, { SelectChangeEvent } from "@mui/material/Select";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Button from "@mui/material/Button";
|
||||
|
||||
import Box from "@mui/material/Box";
|
||||
import List from "@mui/material/List";
|
||||
import ListItem from "@mui/material/ListItem";
|
||||
import Link from "@mui/material/Link";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import TextField from "@mui/material/TextField";
|
||||
|
||||
import DownloadIcon from "@mui/icons-material/Download";
|
||||
import UploadIcon from "@mui/icons-material/Upload";
|
||||
import SaveIcon from "@mui/icons-material/Save";
|
||||
import PaletteIcon from "@mui/icons-material/Palette";
|
||||
|
||||
import { FileDiagnosticModal } from "../../Diagnostic/FileDiagnosticModal";
|
||||
import { ConfirmationModal } from "./ConfirmationModal";
|
||||
|
||||
import { SnackbarEvents, ToastVariant } from "./Snackbar";
|
||||
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { DeleteGameButton } from "./DeleteGameButton";
|
||||
import { SoftResetButton } from "./SoftResetButton";
|
||||
import { IRouter } from "../Router";
|
||||
import { ThemeEditorButton } from "../../Themes/ui/ThemeEditorButton";
|
||||
import { StyleEditorButton } from "../../Themes/ui/StyleEditorButton";
|
||||
import { formatTime } from "../../utils/helpers/formatTime";
|
||||
import { OptionSwitch } from "./OptionSwitch";
|
||||
import { ImportData, saveObject } from "../../SaveObject";
|
||||
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
width: 50,
|
||||
padding: theme.spacing(2),
|
||||
userSelect: "none",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
interface IProps {
|
||||
player: IPlayer;
|
||||
router: IRouter;
|
||||
save: () => void;
|
||||
export: () => void;
|
||||
forceKill: () => void;
|
||||
softReset: () => void;
|
||||
}
|
||||
|
||||
export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
const classes = useStyles();
|
||||
const importInput = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [execTime, setExecTime] = useState(Settings.CodeInstructionRunTime);
|
||||
const [recentScriptsSize, setRecentScriptsSize] = useState(Settings.MaxRecentScriptsCapacity);
|
||||
const [logSize, setLogSize] = useState(Settings.MaxLogCapacity);
|
||||
const [portSize, setPortSize] = useState(Settings.MaxPortCapacity);
|
||||
const [terminalSize, setTerminalSize] = useState(Settings.MaxTerminalCapacity);
|
||||
const [autosaveInterval, setAutosaveInterval] = useState(Settings.AutosaveInterval);
|
||||
const [timestampFormat, setTimestampFormat] = useState(Settings.TimestampsFormat);
|
||||
const [locale, setLocale] = useState(Settings.Locale);
|
||||
const [diagnosticOpen, setDiagnosticOpen] = useState(false);
|
||||
const [importSaveOpen, setImportSaveOpen] = useState(false);
|
||||
const [importData, setImportData] = useState<ImportData | null>(null);
|
||||
|
||||
function handleExecTimeChange(event: any, newValue: number | number[]): void {
|
||||
setExecTime(newValue as number);
|
||||
Settings.CodeInstructionRunTime = newValue as number;
|
||||
}
|
||||
|
||||
function handleRecentScriptsSizeChange(event: any, newValue: number | number[]): void {
|
||||
setRecentScriptsSize(newValue as number);
|
||||
Settings.MaxRecentScriptsCapacity = newValue as number;
|
||||
}
|
||||
|
||||
function handleLogSizeChange(event: any, newValue: number | number[]): void {
|
||||
setLogSize(newValue as number);
|
||||
Settings.MaxLogCapacity = newValue as number;
|
||||
}
|
||||
|
||||
function handlePortSizeChange(event: any, newValue: number | number[]): void {
|
||||
setPortSize(newValue as number);
|
||||
Settings.MaxPortCapacity = newValue as number;
|
||||
}
|
||||
|
||||
function handleTerminalSizeChange(event: any, newValue: number | number[]): void {
|
||||
setTerminalSize(newValue as number);
|
||||
Settings.MaxTerminalCapacity = newValue as number;
|
||||
}
|
||||
|
||||
function handleAutosaveIntervalChange(event: any, newValue: number | number[]): void {
|
||||
setAutosaveInterval(newValue as number);
|
||||
Settings.AutosaveInterval = newValue as number;
|
||||
}
|
||||
|
||||
function handleLocaleChange(event: SelectChangeEvent<string>): void {
|
||||
setLocale(event.target.value as string);
|
||||
Settings.Locale = event.target.value as string;
|
||||
}
|
||||
|
||||
function handleTimestampFormatChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setTimestampFormat(event.target.value);
|
||||
Settings.TimestampsFormat = event.target.value;
|
||||
}
|
||||
|
||||
function startImport(): void {
|
||||
if (!window.File || !window.FileReader || !window.FileList || !window.Blob) return;
|
||||
const ii = importInput.current;
|
||||
if (ii === null) throw new Error("import input should not be null");
|
||||
ii.click();
|
||||
}
|
||||
|
||||
async function onImport(event: React.ChangeEvent<HTMLInputElement>): Promise<void> {
|
||||
try {
|
||||
const base64Save = await saveObject.getImportStringFromFile(event.target.files);
|
||||
const data = await saveObject.getImportDataFromString(base64Save);
|
||||
setImportData(data);
|
||||
setImportSaveOpen(true);
|
||||
} catch (ex: any) {
|
||||
SnackbarEvents.emit(ex.toString(), ToastVariant.ERROR, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmedImportGame(): Promise<void> {
|
||||
if (!importData) return;
|
||||
|
||||
try {
|
||||
await saveObject.importGame(importData.base64);
|
||||
} catch (ex: any) {
|
||||
SnackbarEvents.emit(ex.toString(), ToastVariant.ERROR, 5000);
|
||||
}
|
||||
|
||||
setImportSaveOpen(false);
|
||||
setImportData(null);
|
||||
}
|
||||
|
||||
function compareSaveGame(): void {
|
||||
if (!importData) return;
|
||||
props.router.toImportSave(importData.base64);
|
||||
setImportSaveOpen(false);
|
||||
setImportData(null);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.root} style={{ width: "90%" }}>
|
||||
<Typography variant="h4" gutterBottom>
|
||||
Options
|
||||
</Typography>
|
||||
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<List>
|
||||
<ListItem>
|
||||
<Box display="grid" sx={{ width: "fit-content", gridTemplateColumns: "1fr 3.5fr", gap: 1 }}>
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
The minimum number of milliseconds it takes to execute an operation in Netscript. Setting this too
|
||||
low can result in poor performance if you have many scripts running.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>.script exec time (ms)</Typography>
|
||||
</Tooltip>
|
||||
<Slider
|
||||
value={execTime}
|
||||
onChange={handleExecTimeChange}
|
||||
step={1}
|
||||
min={5}
|
||||
max={100}
|
||||
valueLabelDisplay="auto"
|
||||
/>
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
The maximum number of recently killed script entries being tracked. Setting this too high can
|
||||
cause the game to use a lot of memory.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>Recently killed scripts size</Typography>
|
||||
</Tooltip>
|
||||
<Slider
|
||||
value={recentScriptsSize}
|
||||
onChange={handleRecentScriptsSizeChange}
|
||||
step={25}
|
||||
min={0}
|
||||
max={500}
|
||||
valueLabelDisplay="auto"
|
||||
/>
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
The maximum number of lines a script's logs can hold. Setting this too high can cause the game to
|
||||
use a lot of memory if you have many scripts running.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>Netscript log size</Typography>
|
||||
</Tooltip>
|
||||
<Slider
|
||||
value={logSize}
|
||||
onChange={handleLogSizeChange}
|
||||
step={20}
|
||||
min={20}
|
||||
max={500}
|
||||
valueLabelDisplay="auto"
|
||||
/>
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
The maximum number of entries that can be written to a port using Netscript's write() function.
|
||||
Setting this too high can cause the game to use a lot of memory.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>Netscript port size</Typography>
|
||||
</Tooltip>
|
||||
<Slider
|
||||
value={portSize}
|
||||
onChange={handlePortSizeChange}
|
||||
step={1}
|
||||
min={20}
|
||||
max={100}
|
||||
valueLabelDisplay="auto"
|
||||
/>
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
The maximum number of entries that can be written to the terminal. Setting this too high can cause
|
||||
the game to use a lot of memory.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>Terminal capacity</Typography>
|
||||
</Tooltip>
|
||||
<Slider
|
||||
value={terminalSize}
|
||||
onChange={handleTerminalSizeChange}
|
||||
step={50}
|
||||
min={50}
|
||||
max={500}
|
||||
valueLabelDisplay="auto"
|
||||
marks
|
||||
/>
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>The time (in seconds) between each autosave. Set to 0 to disable autosave.</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>Autosave interval (s)</Typography>
|
||||
</Tooltip>
|
||||
<Slider
|
||||
value={autosaveInterval}
|
||||
onChange={handleAutosaveIntervalChange}
|
||||
step={30}
|
||||
min={0}
|
||||
max={600}
|
||||
valueLabelDisplay="auto"
|
||||
marks
|
||||
/>
|
||||
</Box>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<OptionSwitch
|
||||
checked={Settings.SuppressMessages}
|
||||
onChange={(newValue) => (Settings.SuppressMessages = newValue)}
|
||||
text="Suppress story messages"
|
||||
tooltip={
|
||||
<>
|
||||
If this is set, then any messages you receive will not appear as popups on the screen. They will
|
||||
still get sent to your home computer as '.msg' files and can be viewed with the 'cat' Terminal
|
||||
command.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<OptionSwitch
|
||||
checked={Settings.SuppressFactionInvites}
|
||||
onChange={(newValue) => (Settings.SuppressFactionInvites = newValue)}
|
||||
text="Suppress faction invites"
|
||||
tooltip={
|
||||
<>
|
||||
If this is set, then any faction invites you receive will not appear as popups on the screen. Your
|
||||
outstanding faction invites can be viewed in the 'Factions' page.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<OptionSwitch
|
||||
checked={Settings.SuppressTravelConfirmation}
|
||||
onChange={(newValue) => (Settings.SuppressTravelConfirmation = newValue)}
|
||||
text="Suppress travel confirmations"
|
||||
tooltip={
|
||||
<>
|
||||
If this is set, the confirmation message before traveling will not show up. You will automatically
|
||||
be deducted the travel cost as soon as you click.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<OptionSwitch
|
||||
checked={Settings.SuppressBuyAugmentationConfirmation}
|
||||
onChange={(newValue) => (Settings.SuppressBuyAugmentationConfirmation = newValue)}
|
||||
text="Suppress augmentations confirmation"
|
||||
tooltip={<>If this is set, the confirmation message before buying augmentation will not show up.</>}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<OptionSwitch
|
||||
checked={Settings.SuppressTIXPopup}
|
||||
onChange={(newValue) => (Settings.SuppressTIXPopup = newValue)}
|
||||
text="Suppress TIX messages"
|
||||
tooltip={<>If this is set, the stock market will never create any popup.</>}
|
||||
/>
|
||||
</ListItem>
|
||||
{!!props.player.bladeburner && (
|
||||
<ListItem>
|
||||
<OptionSwitch
|
||||
checked={Settings.SuppressBladeburnerPopup}
|
||||
onChange={(newValue) => (Settings.SuppressBladeburnerPopup = newValue)}
|
||||
text="Suppress bladeburner popup"
|
||||
tooltip={
|
||||
<>
|
||||
If this is set, then having your Bladeburner actions interrupted by being busy with something else
|
||||
will not display a popup message.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
)}
|
||||
<ListItem>
|
||||
<OptionSwitch
|
||||
checked={Settings.SuppressSavedGameToast}
|
||||
onChange={(newValue) => (Settings.SuppressSavedGameToast = newValue)}
|
||||
text="Suppress Auto-Save Game Toast"
|
||||
tooltip={<>If this is set, there will be no "Game Saved!" toast appearing after an auto-save.</>}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<OptionSwitch
|
||||
checked={Settings.SuppressAutosaveDisabledWarnings}
|
||||
onChange={(newValue) => (Settings.SuppressAutosaveDisabledWarnings = newValue)}
|
||||
text="Suppress Auto-Save Disabled Warning"
|
||||
tooltip={<>If this is set, there will be no warning triggered when auto-save is disabled (at 0).</>}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<OptionSwitch
|
||||
checked={Settings.DisableHotkeys}
|
||||
onChange={(newValue) => (Settings.DisableHotkeys = newValue)}
|
||||
text="Disable hotkeys"
|
||||
tooltip={
|
||||
<>
|
||||
If this is set, then most hotkeys (keyboard shortcuts) in the game are disabled. This includes
|
||||
Terminal commands, hotkeys to navigate between different parts of the game, and the "Save and Close
|
||||
(Ctrl + b)" hotkey in the Text Editor.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<OptionSwitch
|
||||
checked={Settings.DisableASCIIArt}
|
||||
onChange={(newValue) => (Settings.DisableASCIIArt = newValue)}
|
||||
text="Disable ascii art"
|
||||
tooltip={<>If this is set all ASCII art will be disabled.</>}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<OptionSwitch
|
||||
checked={Settings.DisableTextEffects}
|
||||
onChange={(newValue) => (Settings.DisableTextEffects = newValue)}
|
||||
text="Disable text effects"
|
||||
tooltip={
|
||||
<>
|
||||
If this is set, text effects will not be displayed. This can help if text is difficult to read in
|
||||
certain areas.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<OptionSwitch
|
||||
checked={Settings.DisableOverviewProgressBars}
|
||||
onChange={(newValue) => (Settings.DisableOverviewProgressBars = newValue)}
|
||||
text="Disable Overview Progress Bars"
|
||||
tooltip={<>If this is set, the progress bars in the character overview will be hidden.</>}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<OptionSwitch
|
||||
checked={Settings.EnableBashHotkeys}
|
||||
onChange={(newValue) => (Settings.EnableBashHotkeys = newValue)}
|
||||
text="Enable bash hotkeys"
|
||||
tooltip={
|
||||
<>
|
||||
Improved Bash emulation mode. Setting this to 1 enables several new Terminal shortcuts and features
|
||||
that more closely resemble a real Bash-style shell. Note that when this mode is enabled, the default
|
||||
browser shortcuts are overriden by the new Bash shortcuts.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<OptionSwitch
|
||||
checked={Settings.UseIEC60027_2}
|
||||
onChange={(newValue) => (Settings.UseIEC60027_2 = newValue)}
|
||||
text="Use GiB instead of GB"
|
||||
tooltip={
|
||||
<>
|
||||
If this is set all references to memory will use GiB instead of GB, in accordance with IEC 60027-2.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<OptionSwitch
|
||||
checked={Settings.ExcludeRunningScriptsFromSave}
|
||||
onChange={(newValue) => (Settings.ExcludeRunningScriptsFromSave = newValue)}
|
||||
text="Exclude Running Scripts from Save"
|
||||
tooltip={
|
||||
<>
|
||||
If this is set, the save file will exclude all running scripts. This is only useful if your save is
|
||||
lagging a lot. You'll have to restart your script every time you launch the game.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
Terminal commands and log entries will be timestamped. See
|
||||
https://date-fns.org/docs/Getting-Started/
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<span>
|
||||
<TextField
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<Typography
|
||||
color={
|
||||
formatTime(timestampFormat) === "format error" && timestampFormat !== ""
|
||||
? "error"
|
||||
: "success"
|
||||
}
|
||||
>
|
||||
Timestamp format:
|
||||
</Typography>
|
||||
),
|
||||
}}
|
||||
value={timestampFormat}
|
||||
onChange={handleTimestampFormatChange}
|
||||
placeholder="yyyy-MM-dd hh:mm:ss"
|
||||
/>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<OptionSwitch
|
||||
checked={Settings.SaveGameOnFileSave}
|
||||
onChange={(newValue) => (Settings.SaveGameOnFileSave = newValue)}
|
||||
text="Save game on file save"
|
||||
tooltip={<>Save your game any time a file is saved in the script editor.</>}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<Tooltip title={<Typography>Sets the locale for displaying numbers.</Typography>}>
|
||||
<Typography>Locale </Typography>
|
||||
</Tooltip>
|
||||
<Select value={locale} onChange={handleLocaleChange}>
|
||||
<MenuItem value="en">en</MenuItem>
|
||||
<MenuItem value="bg">bg</MenuItem>
|
||||
<MenuItem value="cs">cs</MenuItem>
|
||||
<MenuItem value="da-dk">da-dk</MenuItem>
|
||||
<MenuItem value="de">de</MenuItem>
|
||||
<MenuItem value="en-au">en-au</MenuItem>
|
||||
<MenuItem value="en-gb">en-gb</MenuItem>
|
||||
<MenuItem value="es">es</MenuItem>
|
||||
<MenuItem value="fr">fr</MenuItem>
|
||||
<MenuItem value="hu">hu</MenuItem>
|
||||
<MenuItem value="it">it</MenuItem>
|
||||
<MenuItem value="lv">lv</MenuItem>
|
||||
<MenuItem value="no">no</MenuItem>
|
||||
<MenuItem value="pl">pl</MenuItem>
|
||||
<MenuItem value="ru">ru</MenuItem>
|
||||
</Select>
|
||||
</ListItem>
|
||||
</List>
|
||||
{!location.href.startsWith("file://") && (
|
||||
<>
|
||||
<ListItem>
|
||||
<Typography>danielyxie / BigD (Original developer): </Typography>
|
||||
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
|
||||
<input type="hidden" name="cmd" value="_s-xclick" />
|
||||
<input
|
||||
type="hidden"
|
||||
name="encrypted"
|
||||
value="-----BEGIN PKCS7-----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-----END PKCS7-----"
|
||||
/>
|
||||
<input
|
||||
type="image"
|
||||
src="https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif"
|
||||
name="submit"
|
||||
alt="PayPal - The safer, easier way to pay online!"
|
||||
/>
|
||||
</form>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<Typography>
|
||||
hydroflame (Current maintainer):{" "}
|
||||
<Link href="https://www.google.com/search?q=Where+to+donate+blood+near+me%3F" target="_blank">
|
||||
Donate blood!
|
||||
</Link>{" "}
|
||||
</Typography>
|
||||
</ListItem>
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
<Box sx={{ display: "grid", width: "fit-content", height: "fit-content" }}>
|
||||
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
|
||||
<Button onClick={() => props.save()} startIcon={<SaveIcon />}>
|
||||
Save Game
|
||||
</Button>
|
||||
<DeleteGameButton />
|
||||
</Box>
|
||||
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
|
||||
<Tooltip title={<Typography>Export your game to a text file.</Typography>}>
|
||||
<Button onClick={() => props.export()} startIcon={<DownloadIcon />}>
|
||||
Export Game
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
Import your game from a text file.
|
||||
<br />
|
||||
This will <strong>overwrite</strong> your current game. Back it up first!
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Button onClick={startImport} startIcon={<UploadIcon />}>
|
||||
Import Game
|
||||
<input ref={importInput} id="import-game-file-selector" type="file" hidden onChange={onImport} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<ConfirmationModal
|
||||
open={importSaveOpen}
|
||||
onClose={() => setImportSaveOpen(false)}
|
||||
onConfirm={() => confirmedImportGame()}
|
||||
additionalButton={<Button onClick={compareSaveGame}>Compare Save</Button>}
|
||||
confirmationText={
|
||||
<>
|
||||
Importing a new game will <strong>completely wipe</strong> the current data!
|
||||
<br />
|
||||
<br />
|
||||
Make sure to have a backup of your current save file before importing.
|
||||
<br />
|
||||
The file you are attempting to import seems valid.
|
||||
{(importData?.playerData?.lastSave ?? 0) > 0 && (
|
||||
<>
|
||||
<br />
|
||||
<br />
|
||||
The export date of the save file is{" "}
|
||||
<strong>{new Date(importData?.playerData?.lastSave ?? 0).toLocaleString()}</strong>
|
||||
</>
|
||||
)}
|
||||
{(importData?.playerData?.totalPlaytime ?? 0) > 0 && (
|
||||
<>
|
||||
<br />
|
||||
<br />
|
||||
Total play time of imported game:{" "}
|
||||
{convertTimeMsToTimeElapsedString(importData?.playerData?.totalPlaytime ?? 0)}
|
||||
</>
|
||||
)}
|
||||
<br />
|
||||
<br />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ display: "grid" }}>
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
Forcefully kill all active running scripts, in case there is a bug or some unexpected issue with the
|
||||
game. After using this, save the game and then reload the page. This is different then normal kill in
|
||||
that normal kill will tell the script to shut down while force kill just removes the references to it
|
||||
(and it should crash on it's own). This will not remove the files on your computer. Just forcefully
|
||||
kill all running instance of all scripts.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Button onClick={() => props.forceKill()}>Force kill all active scripts</Button>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
|
||||
<SoftResetButton
|
||||
noConfirmation={Settings.SuppressBuyAugmentationConfirmation}
|
||||
onTriggered={props.softReset}
|
||||
/>
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
If your save file is extremely big you can use this button to view a map of all the files on every
|
||||
server. Be careful there might be spoilers.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Button onClick={() => setDiagnosticOpen(true)}>Diagnose files</Button>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr" }}>
|
||||
<Tooltip title="Head to the theme browser to see a collection of prebuilt themes.">
|
||||
<Button startIcon={<PaletteIcon />} onClick={() => props.router.toThemeBrowser()}>
|
||||
Theme Browser
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<ThemeEditorButton router={props.router} />
|
||||
<StyleEditorButton />
|
||||
</Box>
|
||||
<Box>
|
||||
<Link href="https://github.com/danielyxie/bitburner/issues/new" target="_blank">
|
||||
<Typography>Report bug</Typography>
|
||||
</Link>
|
||||
<Link href="https://bitburner.readthedocs.io/en/latest/changelog.html" target="_blank">
|
||||
<Typography>Changelog</Typography>
|
||||
</Link>
|
||||
<Link href="https://bitburner.readthedocs.io/en/latest/index.html" target="_blank">
|
||||
<Typography>Documentation</Typography>
|
||||
</Link>
|
||||
<Link href="https://discord.gg/TFc3hKD" target="_blank">
|
||||
<Typography>Discord</Typography>
|
||||
</Link>
|
||||
<Link href="https://www.reddit.com/r/bitburner" target="_blank">
|
||||
<Typography>Reddit</Typography>
|
||||
</Link>
|
||||
<Link href="https://plaza.dsolver.ca/games/bitburner" target="_blank">
|
||||
<Typography>Incremental game plaza</Typography>
|
||||
</Link>
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
<FileDiagnosticModal open={diagnosticOpen} onClose={() => setDiagnosticOpen(false)} />
|
||||
</div>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user