import React, { useState, useEffect } from "react"; import { KEYCODE } from "../../utils/helpers/keyCodes"; import clsx from "clsx"; import { styled, Theme, CSSObject } from "@mui/material/styles"; import createStyles from "@mui/styles/createStyles"; import makeStyles from "@mui/styles/makeStyles"; import MuiDrawer from "@mui/material/Drawer"; import List from "@mui/material/List"; import Divider from "@mui/material/Divider"; import Tooltip from "@mui/material/Tooltip"; import ChevronLeftIcon from "@mui/icons-material/ChevronLeft"; import ChevronRightIcon from "@mui/icons-material/ChevronRight"; import ListItem from "@mui/material/ListItem"; import ListItemIcon from "@mui/material/ListItemIcon"; import ListItemText from "@mui/material/ListItemText"; import Typography from "@mui/material/Typography"; import Collapse from "@mui/material/Collapse"; import Badge from "@mui/material/Badge"; import ComputerIcon from "@mui/icons-material/Computer"; import LastPageIcon from "@mui/icons-material/LastPage"; // Terminal import CreateIcon from "@mui/icons-material/Create"; // Create Script import StorageIcon from "@mui/icons-material/Storage"; // Active Scripts import BugReportIcon from "@mui/icons-material/BugReport"; // Create Program import EqualizerIcon from "@mui/icons-material/Equalizer"; // Stats import ContactsIcon from "@mui/icons-material/Contacts"; // Factions import DoubleArrowIcon from "@mui/icons-material/DoubleArrow"; // Augmentations import AccountTreeIcon from "@mui/icons-material/AccountTree"; // Hacknet import PeopleAltIcon from "@mui/icons-material/PeopleAlt"; // Sleeves import LocationCityIcon from "@mui/icons-material/LocationCity"; // City import AirplanemodeActiveIcon from "@mui/icons-material/AirplanemodeActive"; // Travel import WorkIcon from "@mui/icons-material/Work"; // Job import TrendingUpIcon from "@mui/icons-material/TrendingUp"; // Stock Market import FormatBoldIcon from "@mui/icons-material/FormatBold"; // Bladeburner import BusinessIcon from "@mui/icons-material/Business"; // Corp import SportsMmaIcon from "@mui/icons-material/SportsMma"; // Gang import CheckIcon from "@mui/icons-material/Check"; // Milestones import HelpIcon from "@mui/icons-material/Help"; // Tutorial import SettingsIcon from "@mui/icons-material/Settings"; // options import DeveloperBoardIcon from "@mui/icons-material/DeveloperBoard"; // Dev import EmojiEventsIcon from "@mui/icons-material/EmojiEvents"; // Achievements import AccountBoxIcon from "@mui/icons-material/AccountBox"; import PublicIcon from "@mui/icons-material/Public"; import LiveHelpIcon from "@mui/icons-material/LiveHelp"; import ExpandLessIcon from "@mui/icons-material/ExpandLess"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import { IRouter, Page } from "../../ui/Router"; import { IPlayer } from "../../PersonObjects/IPlayer"; import { CONSTANTS } from "../../Constants"; import { iTutorialSteps, iTutorialNextStep, ITutorial } from "../../InteractiveTutorial"; import { getAvailableCreatePrograms } from "../../Programs/ProgramHelpers"; import { Settings } from "../../Settings/Settings"; import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; import { ProgramsSeen } from "../../Programs/ui/ProgramsRoot"; import { InvitationsSeen } from "../../Faction/ui/FactionsRoot"; import { hash } from "../../hash/hash"; import { Locations } from "../../Locations/Locations"; const openedMixin = (theme: Theme): CSSObject => ({ width: theme.spacing(31), transition: theme.transitions.create("width", { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.enteringScreen, }), overflowX: "hidden", }); const closedMixin = (theme: Theme): CSSObject => ({ transition: theme.transitions.create("width", { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen, }), overflowX: "hidden", width: `calc(${theme.spacing(2)} + 1px)`, [theme.breakpoints.up("sm")]: { width: `calc(${theme.spacing(7)} + 1px)`, }, }); const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== "open" })(({ theme, open }) => ({ width: theme.spacing(31), whiteSpace: "nowrap", boxSizing: "border-box", ...(open && { ...openedMixin(theme), "& .MuiDrawer-paper": openedMixin(theme), }), ...(!open && { ...closedMixin(theme), "& .MuiDrawer-paper": closedMixin(theme), }), })); const useStyles = makeStyles((theme: Theme) => createStyles({ active: { borderLeft: "3px solid " + theme.palette.primary.main, }, listitem: {}, }), ); interface IProps { player: IPlayer; router: IRouter; page: Page; opened: boolean; onToggled: (newValue: boolean) => void; } export function SidebarRoot(props: IProps): React.ReactElement { const setRerender = useState(false)[1]; function rerender(): void { setRerender((old) => !old); } useEffect(() => { const id = setInterval(rerender, 200); return () => clearInterval(id); }, []); const [hackingOpen, setHackingOpen] = useState(true); const [characterOpen, setCharacterOpen] = useState(true); const [worldOpen, setWorldOpen] = useState(true); const [helpOpen, setHelpOpen] = useState(true); const flashTerminal = ITutorial.currStep === iTutorialSteps.CharacterGoToTerminalPage || ITutorial.currStep === iTutorialSteps.ActiveScriptsPage; const flashStats = ITutorial.currStep === iTutorialSteps.GoToCharacterPage; const flashActiveScripts = ITutorial.currStep === iTutorialSteps.TerminalGoToActiveScriptsPage; const flashHacknet = ITutorial.currStep === iTutorialSteps.GoToHacknetNodesPage; const flashCity = ITutorial.currStep === iTutorialSteps.HacknetNodesGoToWorldPage; const flashTutorial = ITutorial.currStep === iTutorialSteps.WorldDescription; const augmentationCount = props.player.queuedAugmentations.length; const invitationsCount = props.player.factionInvitations.filter((f) => !InvitationsSeen.includes(f)).length; const programCount = getAvailableCreatePrograms(props.player).length - ProgramsSeen.length; const canOpenFactions = props.player.factionInvitations.length > 0 || props.player.factions.length > 0 || props.player.augmentations.length > 0 || props.player.queuedAugmentations.length > 0 || props.player.sourceFiles.length > 0; const canOpenAugmentations = props.player.augmentations.length > 0 || props.player.queuedAugmentations.length > 0 || props.player.sourceFiles.length > 0; const canOpenSleeves = props.player.sleeves.length > 0; const canCorporation = !!props.player.corporation; const canGang = !!props.player.gang; const canJob = Object.values(props.player.jobs).length > 0; const canStockMarket = props.player.hasWseAccount; const canBladeburner = !!props.player.bladeburner; const canStaneksGift = props.player.augmentations.some((aug) => aug.name === AugmentationNames.StaneksGift1); function clickTerminal(): void { props.router.toTerminal(); if (flashTerminal) iTutorialNextStep(); } function clickScriptEditor(): void { props.router.toScriptEditor(); } function clickStats(): void { props.router.toStats(); if (flashStats) iTutorialNextStep(); } function clickActiveScripts(): void { props.router.toActiveScripts(); if (flashActiveScripts) iTutorialNextStep(); } function clickCreateProgram(): void { props.router.toCreateProgram(); } function clickStaneksGift(): void { props.router.toStaneksGift(); } function clickFactions(): void { props.router.toFactions(); } function clickAugmentations(): void { props.router.toAugmentations(); } function clickSleeves(): void { props.router.toSleeves(); } function clickHacknet(): void { props.router.toHacknetNodes(); if (flashHacknet) iTutorialNextStep(); } function clickCity(): void { props.router.toCity(); if (flashCity) iTutorialNextStep(); } function clickTravel(): void { props.router.toTravel(); } function clickJob(): void { props.router.toJob(Locations[Object.keys(props.player.jobs)[0]]); } function clickStockMarket(): void { props.router.toStockMarket(); } function clickBladeburner(): void { props.router.toBladeburner(); } function clickCorp(): void { props.router.toCorporation(); } function clickGang(): void { props.router.toGang(); } function clickTutorial(): void { props.router.toTutorial(); if (flashTutorial) iTutorialNextStep(); } function clickMilestones(): void { props.router.toMilestones(); } function clickOptions(): void { props.router.toGameOptions(); } function clickDev(): void { props.router.toDevMenu(); } function clickAchievements(): void { props.router.toAchievements(); } useEffect(() => { // Shortcuts to navigate through the game // Alt-t - Terminal // Alt-c - Character // Alt-e - Script editor // Alt-s - Active scripts // Alt-h - Hacknet Nodes // Alt-w - City // Alt-j - Job // Alt-r - Travel Agency of current city // Alt-p - Create program // Alt-f - Factions // Alt-a - Augmentations // Alt-u - Tutorial // Alt-o - Options // Alt-b - Bladeburner // Alt-g - Gang function handleShortcuts(this: Document, event: KeyboardEvent): void { if (Settings.DisableHotkeys) return; if ((props.player.currentWork && props.player.focus) || props.router.page() === Page.BitVerse) return; if (event.code === KEYCODE.T && event.altKey) { event.preventDefault(); clickTerminal(); } else if (event.code === KEYCODE.C && event.altKey) { event.preventDefault(); clickStats(); } else if (event.code === KEYCODE.E && event.altKey) { event.preventDefault(); clickScriptEditor(); } else if (event.code === KEYCODE.S && event.altKey) { event.preventDefault(); clickActiveScripts(); } else if (event.code === KEYCODE.H && event.altKey) { event.preventDefault(); clickHacknet(); } else if (event.code === KEYCODE.W && event.altKey) { event.preventDefault(); clickCity(); } else if (event.code === KEYCODE.J && event.altKey && !event.ctrlKey && !event.metaKey && canJob) { // ctrl/cmd + alt + j is shortcut to open Chrome dev tools event.preventDefault(); clickJob(); } else if (event.code === KEYCODE.R && event.altKey) { event.preventDefault(); clickTravel(); } else if (event.code === KEYCODE.P && event.altKey) { event.preventDefault(); clickCreateProgram(); } else if (event.code === KEYCODE.F && event.altKey) { if (props.page == Page.Terminal && Settings.EnableBashHotkeys) { return; } event.preventDefault(); clickFactions(); } else if (event.code === KEYCODE.A && event.altKey) { event.preventDefault(); clickAugmentations(); } else if (event.code === KEYCODE.U && event.altKey) { event.preventDefault(); clickTutorial(); } else if (event.code === KEYCODE.O && event.altKey) { event.preventDefault(); clickOptions(); } else if (event.code === KEYCODE.B && event.altKey && props.player.bladeburner) { event.preventDefault(); clickBladeburner(); } else if (event.code === KEYCODE.G && event.altKey && props.player.gang) { event.preventDefault(); clickGang(); } } document.addEventListener("keydown", handleShortcuts); return () => document.removeEventListener("keydown", handleShortcuts); }, []); const classes = useStyles(); const [open, setOpen] = useState(props.opened); const toggleDrawer = (): void => setOpen((old) => !old); useEffect(() => { props.onToggled(open); }, [open]); return ( {!open ? : } Bitburner v{CONSTANTS.VersionString} } /> setHackingOpen((old) => !old)}> Hacking} /> {hackingOpen ? : } Terminal Script Editor Active Scripts 0 ? programCount : undefined} color="error"> Create Program {canStaneksGift && ( Stanek's Gift )} setCharacterOpen((old) => !old)}> Character} /> {characterOpen ? : } Stats {canOpenFactions && ( Factions )} {canOpenAugmentations && ( Augmentations )} Hacknet {canOpenSleeves && ( Sleeves )} setWorldOpen((old) => !old)}> World} /> {worldOpen ? : } City Travel {canJob && ( Job )} {canStockMarket && ( Stock Market )} {canBladeburner && ( Bladeburner )} {canCorporation && ( Corp )} {canGang && ( Gang )} setHelpOpen((old) => !old)}> Help} /> {helpOpen ? : } Milestones Tutorial Achievements Options {process.env.NODE_ENV === "development" && ( Dev )} ); }