bitburner-src/src/ui/GameRoot.tsx

564 lines
18 KiB
TypeScript
Raw Normal View History

2021-09-20 05:29:02 +02:00
import React, { useState, useEffect } from "react";
2021-09-17 08:04:44 +02:00
import { IPlayer } from "../PersonObjects/IPlayer";
import { IEngine } from "../IEngine";
import { ITerminal } from "../Terminal/ITerminal";
import { installAugmentations } from "../Augmentation/AugmentationHelpers";
2021-09-21 22:49:38 +02:00
import { saveObject } from "../SaveObject";
2021-09-17 08:04:44 +02:00
import { onExport } from "../ExportBonus";
import { LocationName } from "../Locations/data/LocationNames";
2021-09-18 10:01:07 +02:00
import { Location } from "../Locations/Location";
import { Locations } from "../Locations/Locations";
2021-12-14 00:44:52 +01:00
import { ITutorial, iTutorialStart } from "../InteractiveTutorial";
2021-09-19 06:46:39 +02:00
import { InteractiveTutorialRoot } from "./InteractiveTutorial/InteractiveTutorialRoot";
import { ITutorialEvents } from "./InteractiveTutorial/ITutorialEvents";
2021-09-18 10:01:07 +02:00
2021-09-17 08:04:44 +02:00
import { Faction } from "../Faction/Faction";
import { prestigeAugmentation } from "../Prestige";
2021-09-25 20:42:57 +02:00
import { dialogBoxCreate } from "./React/DialogBox";
2021-10-07 22:04:04 +02:00
import { GetAllServers } from "../Server/AllServers";
2021-09-18 01:43:08 +02:00
import { Factions } from "../Faction/Factions";
2021-09-17 08:31:19 +02:00
import { buyStock, sellStock, shortStock, sellShort } from "../StockMarket/BuyingAndSelling";
import {
cancelOrder,
eventEmitterForUiReset,
initStockMarketFnForReact,
placeOrder,
StockMarket,
} from "../StockMarket/StockMarket";
2021-09-17 08:04:44 +02:00
import { Theme } from "@mui/material/styles";
import makeStyles from "@mui/styles/makeStyles";
import createStyles from "@mui/styles/createStyles";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
2021-12-17 18:48:34 +01:00
import { Page, IRouter, ScriptEditorRouteOptions } from "./Router";
2021-09-19 06:46:39 +02:00
import { Overview } from "./React/Overview";
2021-09-17 08:04:44 +02:00
import { SidebarRoot } from "../Sidebar/ui/SidebarRoot";
2021-09-18 01:43:08 +02:00
import { AugmentationsRoot } from "../Augmentation/ui/AugmentationsRoot";
2021-09-17 08:04:44 +02:00
import { DevMenuRoot } from "../DevMenu";
2021-09-18 01:43:08 +02:00
import { BladeburnerRoot } from "../Bladeburner/ui/BladeburnerRoot";
import { GangRoot } from "../Gang/ui/GangRoot";
2021-09-17 08:04:44 +02:00
import { CorporationRoot } from "../Corporation/ui/CorporationRoot";
2021-09-18 01:43:08 +02:00
import { InfiltrationRoot } from "../Infiltration/ui/InfiltrationRoot";
2022-03-19 05:22:21 +01:00
import { GraftingRoot } from "../PersonObjects/Grafting/ui/GraftingRoot";
2021-09-18 01:43:08 +02:00
import { WorkInProgressRoot } from "./WorkInProgressRoot";
2022-03-06 05:05:55 +01:00
import { GameOptionsRoot } from "./React/GameOptionsRoot";
2021-09-17 08:04:44 +02:00
import { SleeveRoot } from "../PersonObjects/Sleeve/ui/SleeveRoot";
import { HacknetRoot } from "../Hacknet/ui/HacknetRoot";
2021-09-18 10:01:07 +02:00
import { GenericLocation } from "../Locations/ui/GenericLocation";
import { LocationCity } from "../Locations/ui/City";
2021-09-17 08:04:44 +02:00
import { ProgramsRoot } from "../Programs/ui/ProgramsRoot";
import { Root as ScriptEditorRoot } from "../ScriptEditor/ui/ScriptEditorRoot";
2021-09-17 08:04:44 +02:00
import { MilestonesRoot } from "../Milestones/ui/MilestonesRoot";
import { TerminalRoot } from "../Terminal/ui/TerminalRoot";
import { TutorialRoot } from "../Tutorial/ui/TutorialRoot";
2022-03-06 05:05:55 +01:00
import { ActiveScriptsRoot } from "./ActiveScripts/ActiveScriptsRoot";
2021-09-17 08:04:44 +02:00
import { FactionsRoot } from "../Faction/ui/FactionsRoot";
import { FactionRoot } from "../Faction/ui/FactionRoot";
2021-09-18 18:13:20 +02:00
import { CharacterStats } from "./CharacterStats";
2021-09-17 08:04:44 +02:00
import { TravelAgencyRoot } from "../Locations/ui/TravelAgencyRoot";
2021-09-17 08:31:19 +02:00
import { StockMarketRoot } from "../StockMarket/ui/StockMarketRoot";
2021-09-18 01:43:08 +02:00
import { BitverseRoot } from "../BitNode/ui/BitverseRoot";
2021-09-25 23:21:50 +02:00
import { StaneksGiftRoot } from "../CotMG/ui/StaneksGiftRoot";
import { staneksGift } from "../CotMG/Helper";
2021-09-18 01:43:08 +02:00
import { CharacterOverview } from "./React/CharacterOverview";
import { BladeburnerCinematic } from "../Bladeburner/ui/BladeburnerCinematic";
2021-09-17 08:04:44 +02:00
import { workerScripts } from "../Netscript/WorkerScripts";
import { Unclickable } from "../Exploits/Unclickable";
2021-10-13 23:25:58 +02:00
import { Snackbar, SnackbarProvider } from "./React/Snackbar";
2021-10-01 07:00:50 +02:00
import { LogBoxManager } from "./React/LogBoxManager";
import { AlertManager } from "./React/AlertManager";
2021-10-02 04:53:23 +02:00
import { PromptManager } from "./React/PromptManager";
2021-10-01 19:08:37 +02:00
import { InvitationModal } from "../Faction/ui/InvitationModal";
import { calculateAchievements } from "../Achievements/Achievements";
2021-09-17 08:04:44 +02:00
2021-09-18 01:43:08 +02:00
import { enterBitNode } from "../RedPill";
import { Context } from "./Context";
2021-11-02 22:28:19 +01:00
import { RecoveryMode, RecoveryRoot } from "./React/RecoveryRoot";
import { AchievementsRoot } from "../Achievements/AchievementsRoot";
import { ErrorBoundary } from "./ErrorBoundary";
import { Settings } from "../Settings/Settings";
import { ThemeBrowser } from "../Themes/ui/ThemeBrowser";
import { ImportSaveRoot } from "./React/ImportSaveRoot";
import { BypassWrapper } from "./React/BypassWrapper";
2021-09-17 08:04:44 +02:00
import _wrap from "lodash/wrap";
import _functions from "lodash/functions";
2022-03-31 18:04:06 +02:00
import { Apr1 } from "./Apr1";
2021-10-26 21:50:55 +02:00
const htmlLocation = location;
2021-09-17 08:04:44 +02:00
interface IProps {
terminal: ITerminal;
player: IPlayer;
engine: IEngine;
}
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
"-ms-overflow-style": "none" /* for Internet Explorer, Edge */,
"scrollbar-width": "none" /* for Firefox */,
2021-09-18 01:43:08 +02:00
margin: theme.spacing(0),
flexGrow: 1,
display: "block",
padding: "8px",
minHeight: "100vh",
2022-01-16 00:13:35 +01:00
boxSizing: "border-box",
2021-09-17 08:04:44 +02:00
},
}),
);
2022-03-11 21:19:10 +01:00
const uninitialized = (): any => {
throw new Error("Router called before initialization");
};
2021-09-18 01:43:08 +02:00
export let Router: IRouter = {
isInitialized: false,
2022-03-11 21:19:10 +01:00
page: uninitialized,
allowRouting: uninitialized,
toActiveScripts: uninitialized,
toAugmentations: uninitialized,
toBitVerse: uninitialized,
toBladeburner: uninitialized,
toStats: uninitialized,
toCity: uninitialized,
toCorporation: uninitialized,
toCreateProgram: uninitialized,
toDevMenu: uninitialized,
toFaction: uninitialized,
toFactions: uninitialized,
toGameOptions: uninitialized,
toGang: uninitialized,
toHacknetNodes: uninitialized,
toInfiltration: uninitialized,
toJob: uninitialized,
toMilestones: uninitialized,
2022-03-19 05:22:21 +01:00
toGrafting: uninitialized,
2022-03-11 21:19:10 +01:00
toScriptEditor: uninitialized,
toSleeves: uninitialized,
toStockMarket: uninitialized,
toTerminal: uninitialized,
toTravel: uninitialized,
toTutorial: uninitialized,
toWork: uninitialized,
toBladeburnerCinematic: uninitialized,
toLocation: uninitialized,
toStaneksGift: uninitialized,
toAchievements: uninitialized,
toThemeBrowser: uninitialized,
toImportSave: uninitialized,
2021-09-18 01:43:08 +02:00
};
function determineStartPage(player: IPlayer): Page {
2021-11-02 22:28:19 +01:00
if (RecoveryMode) return Page.Recovery;
2021-09-18 01:43:08 +02:00
if (player.isWorking) return Page.Work;
return Page.Terminal;
}
2021-09-17 08:04:44 +02:00
export function GameRoot({ player, engine, terminal }: IProps): React.ReactElement {
const classes = useStyles();
const [{ files, vim }, setEditorOptions] = useState({ files: {}, vim: false });
const [page, setPage] = useState(determineStartPage(player));
2021-09-19 06:46:39 +02:00
const setRerender = useState(0)[1];
const [augPage, setAugPage] = useState<boolean>(false);
2021-09-18 10:01:07 +02:00
const [faction, setFaction] = useState<Faction>(
player.currentWorkFactionName ? Factions[player.currentWorkFactionName] : (undefined as unknown as Faction),
2021-09-18 01:43:08 +02:00
);
2021-09-18 10:01:07 +02:00
if (faction === undefined && page === Page.Faction)
throw new Error("Trying to go to a page without the proper setup");
2021-09-17 08:04:44 +02:00
2021-09-18 01:43:08 +02:00
const [flume, setFlume] = useState<boolean>(false);
const [quick, setQuick] = useState<boolean>(false);
2021-09-18 10:01:07 +02:00
const [location, setLocation] = useState<Location>(undefined as unknown as Location);
if (location === undefined && (page === Page.Infiltration || page === Page.Location || page === Page.Job))
throw new Error("Trying to go to a page without the proper setup");
2021-09-18 01:43:08 +02:00
const [cinematicText, setCinematicText] = useState("");
const [errorBoundaryKey, setErrorBoundaryKey] = useState<number>(0);
const [sidebarOpened, setSideBarOpened] = useState(Settings.IsSidebarOpened);
const [importString, setImportString] = useState<string>(undefined as unknown as string);
const [importAutomatic, setImportAutomatic] = useState<boolean>(false);
if (importString === undefined && page === Page.ImportSave)
throw new Error("Trying to go to a page without the proper setup");
const [allowRoutingCalls, setAllowRoutingCalls] = useState(true);
function resetErrorBoundary(): void {
2022-01-16 00:13:35 +01:00
setErrorBoundaryKey(errorBoundaryKey + 1);
}
2021-09-18 01:43:08 +02:00
2021-09-19 06:46:39 +02:00
function rerender(): void {
setRerender((old) => old + 1);
}
useEffect(() => {
return ITutorialEvents.subscribe(rerender);
}, []);
2021-10-26 21:50:55 +02:00
function killAllScripts(): void {
for (const server of GetAllServers()) {
server.runningScripts = [];
}
saveObject.saveGame();
setTimeout(() => htmlLocation.reload(), 2000);
}
2021-09-18 01:43:08 +02:00
Router = {
isInitialized: true,
2021-09-18 01:43:08 +02:00
page: () => page,
allowRouting: (value: boolean) => setAllowRoutingCalls(value),
toActiveScripts: () => setPage(Page.ActiveScripts),
toAugmentations: () => setPage(Page.Augmentations),
toBladeburner: () => setPage(Page.Bladeburner),
toStats: () => setPage(Page.Stats),
toCorporation: () => setPage(Page.Corporation),
toCreateProgram: () => setPage(Page.CreateProgram),
toDevMenu: () => setPage(Page.DevMenu),
toFaction: (faction: Faction, augPage = false) => {
setAugPage(augPage);
setPage(Page.Faction);
2021-09-18 01:43:08 +02:00
if (faction) setFaction(faction);
2021-09-17 08:04:44 +02:00
},
toFactions: () => setPage(Page.Factions),
toGameOptions: () => setPage(Page.Options),
toGang: () => setPage(Page.Gang),
toHacknetNodes: () => setPage(Page.Hacknet),
toMilestones: () => setPage(Page.Milestones),
2022-03-19 05:22:21 +01:00
toGrafting: () => setPage(Page.Grafting),
toScriptEditor: (files: Record<string, string>, options?: ScriptEditorRouteOptions) => {
setEditorOptions({
files,
vim: !!options?.vim,
});
setPage(Page.ScriptEditor);
2021-09-17 08:58:02 +02:00
},
toSleeves: () => setPage(Page.Sleeves),
toStockMarket: () => setPage(Page.StockMarket),
toTerminal: () => setPage(Page.Terminal),
toTutorial: () => setPage(Page.Tutorial),
2021-09-17 08:31:19 +02:00
toJob: () => {
2021-09-18 10:01:07 +02:00
setLocation(Locations[player.companyName]);
setPage(Page.Job);
2021-09-17 08:31:19 +02:00
},
2021-09-17 08:04:44 +02:00
toCity: () => {
setPage(Page.City);
2021-09-17 08:04:44 +02:00
},
toTravel: () => {
player.gotoLocation(LocationName.TravelAgency);
setPage(Page.Travel);
2021-09-17 08:04:44 +02:00
},
2021-09-18 01:43:08 +02:00
toBitVerse: (flume: boolean, quick: boolean) => {
setFlume(flume);
setQuick(quick);
calculateAchievements();
setPage(Page.BitVerse);
2021-09-18 01:43:08 +02:00
},
2021-09-18 10:01:07 +02:00
toInfiltration: (location: Location) => {
2021-09-18 01:43:08 +02:00
setLocation(location);
setPage(Page.Infiltration);
2021-09-18 01:43:08 +02:00
},
toWork: () => setPage(Page.Work),
2021-09-18 01:43:08 +02:00
toBladeburnerCinematic: () => {
setPage(Page.BladeburnerCinematic);
2021-09-18 01:43:08 +02:00
setCinematicText(cinematicText);
},
2021-09-18 10:01:07 +02:00
toLocation: (location: Location) => {
setLocation(location);
setPage(Page.Location);
2021-09-18 10:01:07 +02:00
},
2021-09-25 23:21:50 +02:00
toStaneksGift: () => {
setPage(Page.StaneksGift);
2021-09-25 23:21:50 +02:00
},
toAchievements: () => {
setPage(Page.Achievements);
},
toThemeBrowser: () => {
setPage(Page.ThemeBrowser);
},
toImportSave: (base64save: string, automatic = false) => {
setImportString(base64save);
setImportAutomatic(automatic);
setPage(Page.ImportSave);
},
2021-09-17 08:04:44 +02:00
};
useEffect(() => {
// Wrap Router navigate functions to be able to disable the execution
2022-03-11 21:19:10 +01:00
_functions(Router)
.filter((fnName) => fnName.startsWith("to"))
.forEach((fnName) => {
// @ts-ignore - tslint does not like this, couldn't find a way to make it cooperate
Router[fnName] = _wrap(Router[fnName], (func, ...args) => {
if (!allowRoutingCalls) {
// Let's just log to console.
console.log(`Routing is currently disabled - Attempted router.${fnName}()`);
return;
}
2022-03-11 21:19:10 +01:00
// Call the function normally
return func(...args);
});
});
});
2021-09-17 08:58:02 +02:00
useEffect(() => {
2021-09-19 06:46:39 +02:00
if (page !== Page.Terminal) window.scrollTo(0, 0);
2021-09-17 08:58:02 +02:00
});
function softReset(): void {
dialogBoxCreate("Soft Reset!");
installAugmentations(true);
resetErrorBoundary();
Router.toTerminal();
}
let mainPage = <Typography>Cannot load</Typography>;
let withSidebar = true;
let withPopups = true;
let bypassGame = false;
switch (page) {
case Page.Recovery: {
mainPage = <RecoveryRoot router={Router} softReset={softReset} />;
withSidebar = false;
withPopups = false;
bypassGame = true;
break;
}
case Page.BitVerse: {
mainPage = <BitverseRoot flume={flume} enter={enterBitNode} quick={quick} />;
withSidebar = false;
withPopups = false;
break;
}
case Page.Infiltration: {
mainPage = <InfiltrationRoot location={location} />;
withSidebar = false;
withPopups = false;
break;
}
case Page.BladeburnerCinematic: {
mainPage = <BladeburnerCinematic />;
withSidebar = false;
withPopups = false;
break;
}
case Page.Work: {
mainPage = <WorkInProgressRoot />;
withSidebar = false;
break;
}
case Page.Terminal: {
mainPage = <TerminalRoot terminal={terminal} router={Router} player={player} />;
break;
}
case Page.Sleeves: {
mainPage = <SleeveRoot />;
break;
}
case Page.StaneksGift: {
mainPage = <StaneksGiftRoot staneksGift={staneksGift} />;
break;
}
case Page.Stats: {
mainPage = <CharacterStats />;
break;
}
case Page.ScriptEditor: {
mainPage = (
<ScriptEditorRoot
files={files}
hostname={player.getCurrentServer().hostname}
player={player}
router={Router}
vim={vim}
/>
);
break;
}
case Page.ActiveScripts: {
mainPage = <ActiveScriptsRoot workerScripts={workerScripts} />;
break;
}
case Page.Hacknet: {
mainPage = <HacknetRoot player={player} />;
break;
}
case Page.CreateProgram: {
mainPage = <ProgramsRoot />;
break;
}
case Page.Factions: {
mainPage = <FactionsRoot player={player} router={Router} />;
break;
}
case Page.Faction: {
mainPage = <FactionRoot faction={faction} augPage={augPage} />;
break;
}
case Page.Milestones: {
mainPage = <MilestonesRoot player={player} />;
break;
}
case Page.Tutorial: {
mainPage = (
<TutorialRoot
reactivateTutorial={() => {
prestigeAugmentation();
Router.toTerminal();
iTutorialStart();
}}
/>
);
break;
}
case Page.DevMenu: {
mainPage = <DevMenuRoot player={player} engine={engine} router={Router} />;
break;
}
case Page.Gang: {
mainPage = <GangRoot />;
break;
}
case Page.Corporation: {
mainPage = <CorporationRoot />;
break;
}
case Page.Bladeburner: {
mainPage = <BladeburnerRoot />;
break;
}
2022-03-19 05:22:21 +01:00
case Page.Grafting: {
mainPage = <GraftingRoot />;
break;
}
case Page.Travel: {
mainPage = <TravelAgencyRoot p={player} router={Router} />;
break;
}
case Page.StockMarket: {
mainPage = (
<StockMarketRoot
buyStockLong={buyStock}
buyStockShort={shortStock}
cancelOrder={cancelOrder}
eventEmitterForReset={eventEmitterForUiReset}
initStockMarket={initStockMarketFnForReact}
p={player}
placeOrder={placeOrder}
sellStockLong={sellStock}
sellStockShort={sellShort}
stockMarket={StockMarket}
/>
);
break;
}
case Page.City: {
mainPage = <LocationCity />;
break;
}
case Page.Job:
case Page.Location: {
mainPage = <GenericLocation loc={location} />;
break;
}
case Page.Options: {
mainPage = (
<GameOptionsRoot
player={player}
router={Router}
save={() => saveObject.saveGame()}
export={() => {
// Apply the export bonus before saving the game
onExport(player);
saveObject.exportGame();
}}
forceKill={killAllScripts}
softReset={softReset}
/>
);
break;
}
case Page.Augmentations: {
mainPage = (
<AugmentationsRoot
exportGameFn={() => {
// Apply the export bonus before saving the game
onExport(player);
saveObject.exportGame();
}}
installAugmentationsFn={() => {
installAugmentations();
Router.toTerminal();
}}
/>
);
break;
}
case Page.Achievements: {
mainPage = <AchievementsRoot />;
break;
}
case Page.ThemeBrowser: {
mainPage = <ThemeBrowser router={Router} />;
break;
}
case Page.ImportSave: {
2022-03-11 21:19:10 +01:00
mainPage = <ImportSaveRoot importString={importString} automatic={importAutomatic} router={Router} />;
withSidebar = false;
withPopups = false;
bypassGame = true;
}
}
2021-09-17 08:04:44 +02:00
return (
2021-09-18 01:43:08 +02:00
<Context.Player.Provider value={player}>
2022-01-16 00:13:35 +01:00
<Context.Router.Provider value={Router}>
<ErrorBoundary key={errorBoundaryKey} router={Router} softReset={softReset}>
<BypassWrapper content={bypassGame ? mainPage : null}>
<SnackbarProvider>
<Overview mode={ITutorial.isRunning ? "tutorial" : "overview"}>
{!ITutorial.isRunning ? (
<CharacterOverview save={() => saveObject.saveGame()} killScripts={killAllScripts} />
) : (
<InteractiveTutorialRoot />
)}
</Overview>
{withSidebar ? (
<Box display="flex" flexDirection="row" width="100%">
<SidebarRoot
player={player}
router={Router}
page={page}
opened={sidebarOpened}
onToggled={(isOpened) => {
setSideBarOpened(isOpened);
Settings.IsSidebarOpened = isOpened;
}}
/>
<Box className={classes.root}>{mainPage}</Box>
</Box>
2022-01-16 00:13:35 +01:00
) : (
<Box className={classes.root}>{mainPage}</Box>
)}
<Unclickable />
{withPopups && (
<>
<LogBoxManager />
<AlertManager />
<PromptManager />
<InvitationModal />
<Snackbar />
</>
)}
2022-03-31 18:04:06 +02:00
<Apr1 />
</SnackbarProvider>
</BypassWrapper>
</ErrorBoundary>
2021-09-18 01:43:08 +02:00
</Context.Router.Provider>
</Context.Player.Provider>
2021-09-17 08:04:44 +02:00
);
}