diff --git a/src/Augmentation/AugmentationHelpers.tsx b/src/Augmentation/AugmentationHelpers.tsx index 80f709a1f..2959dfd78 100644 --- a/src/Augmentation/AugmentationHelpers.tsx +++ b/src/Augmentation/AugmentationHelpers.tsx @@ -13,6 +13,7 @@ import { SourceFileFlags } from "../SourceFile/SourceFileFlags"; import { dialogBoxCreate } from "../ui/React/DialogBox"; import { clearObject } from "../utils/helpers/clearObject"; +import { Router } from "../ui/GameRoot"; import { WHRNG } from "../Casino/RNG"; @@ -2582,6 +2583,8 @@ function installAugmentations(): boolean { augmentationList += aug.name + level + "
"; } Player.queuedAugmentations = []; + Router.clearHistory(); + dialogBoxCreate( "You slowly drift to sleep as scientists put you under in order " + "to install the following Augmentations:
" + diff --git a/src/Prestige.ts b/src/Prestige.ts index 971ad08c7..505f2c7df 100755 --- a/src/Prestige.ts +++ b/src/Prestige.ts @@ -306,5 +306,7 @@ export function prestigeSourceFile(flume: boolean): void { // Gain int exp if (SourceFileFlags[5] !== 0 && !flume) Player.gainIntelligenceExp(300); + Router.clearHistory(); + resetPidCounter(); } diff --git a/src/ui/GameRoot.tsx b/src/ui/GameRoot.tsx index aa9f3cb77..bb9354274 100644 --- a/src/ui/GameRoot.tsx +++ b/src/ui/GameRoot.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from "react"; - +import { cloneDeep } from "lodash"; import { IPlayer } from "../PersonObjects/IPlayer"; import { IEngine } from "../IEngine"; import { ITerminal } from "../Terminal/ITerminal"; @@ -86,6 +86,11 @@ interface IProps { engine: IEngine; } +interface PageHistoryEntry { + page: Page; + args: any[]; +} + const useStyles = makeStyles((theme: Theme) => createStyles({ root: { @@ -104,6 +109,15 @@ export let Router: IRouter = { page: () => { throw new Error("Router called before initialization"); }, + previousPage: () => { + throw new Error("Router called before initialization"); + }, + clearHistory: () => { + throw new Error("Router called before initialization"); + }, + toPreviousPage: (): boolean => { + throw new Error("Router called before initialization"); + }, toActiveScripts: () => { throw new Error("Router called before initialization"); }, @@ -202,7 +216,9 @@ function determineStartPage(player: IPlayer): Page { 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)); + const startPage = determineStartPage(player); + const [page, setPage] = useState(startPage); + const [pageHistory, setPageHistory] = useState([{ page: startPage, args: [] }]); const setRerender = useState(0)[1]; const [faction, setFaction] = useState( player.currentWorkFactionName ? Factions[player.currentWorkFactionName] : (undefined as unknown as Faction), @@ -233,70 +249,131 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme setTimeout(() => htmlLocation.reload(), 2000); } + function setCurrentPage(page: Page, ...args: any): void { + const history = [ + { page, args: cloneDeep(args) }, + ...pageHistory + ].slice(0, 20); + setPageHistory(history) + setPage(page) + } + + function goBack(fallback: (...args: any[]) => void): void { + const [ , previousPage ] = pageHistory; + if (previousPage) { + const handler = pageToRouterMap[previousPage?.page]; + handler(...previousPage.args); + } else { + if (fallback) fallback(); + } + const [ , ...history] = pageHistory; + setPageHistory(cloneDeep(history)); + } + + const pageToRouterMap: { [key: number] : (...args: any[]) => void } = { + [Page.ActiveScripts]: Router.toActiveScripts, + [Page.Augmentations]: Router.toAugmentations, + [Page.Bladeburner]: Router.toBladeburner, + [Page.Stats]: Router.toStats, + [Page.Corporation]: Router.toCorporation, + [Page.CreateProgram]: Router.toCreateProgram, + [Page.DevMenu]: Router.toDevMenu, + [Page.Faction]: Router.toFaction, + [Page.Factions]: Router.toFactions, + [Page.Options]: Router.toGameOptions, + [Page.Gang]: Router.toGang, + [Page.Hacknet]: Router.toHacknetNodes, + [Page.Milestones]: Router.toMilestones, + [Page.Resleeves]: Router.toResleeves, + [Page.ScriptEditor]: Router.toScriptEditor, + [Page.Sleeves]: Router.toSleeves, + [Page.StockMarket]: Router.toStockMarket, + [Page.Terminal]: Router.toTerminal, + [Page.Tutorial]: Router.toTutorial, + [Page.Job]: Router.toJob, + [Page.City]: Router.toCity, + [Page.Travel]: Router.toTravel, + [Page.BitVerse]: Router.toBitVerse, + [Page.Infiltration]: Router.toInfiltration, + [Page.Work]: Router.toWork, + [Page.BladeburnerCinematic]: Router.toBladeburnerCinematic, + [Page.Location]: Router.toLocation, + [Page.StaneksGift]: Router.toStaneksGift, + [Page.Achievements]: Router.toAchievements, + } + Router = { page: () => page, - 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), + previousPage: () => { + const [ , previousPage] = pageHistory; + return previousPage?.page ?? -1; + }, + clearHistory: () => setPageHistory([]), + toPreviousPage: goBack, + toActiveScripts: () => setCurrentPage(Page.ActiveScripts), + toAugmentations: () => setCurrentPage(Page.Augmentations), + toBladeburner: () => setCurrentPage(Page.Bladeburner), + toStats: () => setCurrentPage(Page.Stats), + toCorporation: () => setCurrentPage(Page.Corporation), + toCreateProgram: () => setCurrentPage(Page.CreateProgram), + toDevMenu: () => setCurrentPage(Page.DevMenu), toFaction: (faction?: Faction) => { - setPage(Page.Faction); + setCurrentPage(Page.Faction, faction); if (faction) setFaction(faction); }, - toFactions: () => setPage(Page.Factions), - toGameOptions: () => setPage(Page.Options), - toGang: () => setPage(Page.Gang), - toHacknetNodes: () => setPage(Page.Hacknet), - toMilestones: () => setPage(Page.Milestones), - toResleeves: () => setPage(Page.Resleeves), + toFactions: () => setCurrentPage(Page.Factions), + toGameOptions: () => setCurrentPage(Page.Options), + toGang: () => setCurrentPage(Page.Gang), + toHacknetNodes: () => setCurrentPage(Page.Hacknet), + toMilestones: () => setCurrentPage(Page.Milestones), + toResleeves: () => setCurrentPage(Page.Resleeves), toScriptEditor: (files: Record, options?: ScriptEditorRouteOptions) => { setEditorOptions({ files, vim: !!options?.vim, }); - setPage(Page.ScriptEditor); + setCurrentPage(Page.ScriptEditor, files, options); }, - toSleeves: () => setPage(Page.Sleeves), - toStockMarket: () => setPage(Page.StockMarket), - toTerminal: () => setPage(Page.Terminal), - toTutorial: () => setPage(Page.Tutorial), + toSleeves: () => setCurrentPage(Page.Sleeves), + toStockMarket: () => setCurrentPage(Page.StockMarket), + toTerminal: () => setCurrentPage(Page.Terminal), + toTutorial: () => setCurrentPage(Page.Tutorial), toJob: () => { setLocation(Locations[player.companyName]); - setPage(Page.Job); + setCurrentPage(Page.Job); }, toCity: () => { - setPage(Page.City); + setCurrentPage(Page.City); }, toTravel: () => { player.gotoLocation(LocationName.TravelAgency); - setPage(Page.Travel); + setCurrentPage(Page.Travel); }, toBitVerse: (flume: boolean, quick: boolean) => { setFlume(flume); setQuick(quick); - setPage(Page.BitVerse); + setCurrentPage(Page.BitVerse, flume, quick); }, toInfiltration: (location: Location) => { setLocation(location); - setPage(Page.Infiltration); + setCurrentPage(Page.Infiltration, location); + }, + toWork: () => { + setCurrentPage(Page.Work); }, - toWork: () => setPage(Page.Work), toBladeburnerCinematic: () => { - setPage(Page.BladeburnerCinematic); + setCurrentPage(Page.BladeburnerCinematic); setCinematicText(cinematicText); }, toLocation: (location: Location) => { setLocation(location); - setPage(Page.Location); + setCurrentPage(Page.Location, location); }, toStaneksGift: () => { - setPage(Page.StaneksGift); + setCurrentPage(Page.StaneksGift); }, toAchievements: () => { - setPage(Page.Achievements); + setCurrentPage(Page.Achievements); }, }; @@ -499,7 +576,11 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme {!ITutorial.isRunning ? ( - saveObject.saveGame()} killScripts={killAllScripts} /> + saveObject.saveGame()} + killScripts={killAllScripts} + router={Router} + allowBackButton={withSidebar} /> ) : ( )} diff --git a/src/ui/React/CharacterOverview.tsx b/src/ui/React/CharacterOverview.tsx index 885157235..d3ea22166 100644 --- a/src/ui/React/CharacterOverview.tsx +++ b/src/ui/React/CharacterOverview.tsx @@ -16,16 +16,21 @@ import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; import IconButton from "@mui/material/IconButton"; import SaveIcon from "@mui/icons-material/Save"; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ClearAllIcon from "@mui/icons-material/ClearAll"; import { Settings } from "../../Settings/Settings"; import { use } from "../Context"; import { StatsProgressOverviewCell } from "./StatsProgressBar"; import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers"; +import { IRouter, Page } from "../Router"; +import { Box, Tooltip } from "@mui/material"; interface IProps { save: () => void; killScripts: () => void; + router: IRouter; + allowBackButton: boolean; } function Intelligence(): React.ReactElement { @@ -205,7 +210,7 @@ const useStyles = makeStyles((theme: Theme) => export { useStyles as characterOverviewStyles }; -export function CharacterOverview({ save, killScripts }: IProps): React.ReactElement { +export function CharacterOverview({ save, killScripts, router, allowBackButton }: IProps): React.ReactElement { const [killOpen, setKillOpen] = useState(false); const player = use.Player(); @@ -244,6 +249,9 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle player.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier, ); + const previousPageName = router.previousPage() < 0 + ? '' : Page[router.previousPage() ?? 0].replace(/([a-z])([A-Z])/g, '$1 $2'); + return ( <> @@ -418,21 +426,33 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle - - - - - - - - - setKillOpen(true)}> - - - -
+ + + + + + + + {allowBackButton && ( + router.toPreviousPage()}> + + + + + )} + + + setKillOpen(true)}> + + + + + + setKillOpen(false)} killScripts={killScripts} /> ); diff --git a/src/ui/Router.ts b/src/ui/Router.ts index 4056a44ac..8bf86f704 100644 --- a/src/ui/Router.ts +++ b/src/ui/Router.ts @@ -53,6 +53,9 @@ export interface IRouter { // toRedPill(): void; // toworkInProgress(): void; page(): Page; + previousPage(): Page; + clearHistory(): void; + toPreviousPage(fallback?: (...args: any[]) => void): void; toActiveScripts(): void; toAugmentations(): void; toBitVerse(flume: boolean, quick: boolean): void; diff --git a/src/ui/WorkInProgressRoot.tsx b/src/ui/WorkInProgressRoot.tsx index d43843c85..535629deb 100644 --- a/src/ui/WorkInProgressRoot.tsx +++ b/src/ui/WorkInProgressRoot.tsx @@ -36,12 +36,12 @@ export function WorkInProgressRoot(): React.ReactElement { const faction = Factions[player.currentWorkFactionName]; if (player.workType == CONSTANTS.WorkTypeFaction) { function cancel(): void { - router.toFaction(faction); player.finishFactionWork(true); + router.toPreviousPage(() => router.toFaction(faction)); } function unfocus(): void { - router.toFaction(faction); player.stopFocusing(); + router.toPreviousPage(() => router.toFaction(faction)); } return ( @@ -120,13 +120,12 @@ export function WorkInProgressRoot(): React.ReactElement { if (player.className !== "") { function cancel(): void { player.finishClass(true); - router.toCity(); + router.toPreviousPage(() => router.toCity()); } function unfocus(): void { - router.toFaction(faction); - router.toCity(); player.stopFocusing(); + router.toPreviousPage(() => router.toCity()); } let stopText = ""; @@ -212,11 +211,11 @@ export function WorkInProgressRoot(): React.ReactElement { function cancel(): void { player.finishWork(true); - router.toJob(); + router.toPreviousPage(() => router.toJob()); } function unfocus(): void { player.stopFocusing(); - router.toJob(); + router.toPreviousPage(() => router.toJob()); } const position = player.jobs[player.companyName]; @@ -304,11 +303,11 @@ export function WorkInProgressRoot(): React.ReactElement { if (player.workType == CONSTANTS.WorkTypeCompanyPartTime) { function cancel(): void { player.finishWorkPartTime(true); - router.toJob(); + router.toPreviousPage(() => router.toJob()); } function unfocus(): void { player.stopFocusing(); - router.toJob(); + router.toPreviousPage(() => router.toJob()); } const comp = Companies[player.companyName]; let companyRep = 0; @@ -440,11 +439,11 @@ export function WorkInProgressRoot(): React.ReactElement { if (player.createProgramName !== "") { function cancel(): void { player.finishCreateProgramWork(true); - router.toTerminal(); + router.toPreviousPage(() => router.toTerminal()); } function unfocus(): void { - router.toTerminal(); player.stopFocusing(); + router.toPreviousPage(() => router.toTerminal()); } return (