From 023f2b830997acc03f4f84e8ac1cfacda759b5fa Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Sun, 19 Sep 2021 00:46:39 -0400 Subject: [PATCH] ITutorial in react --- src/InteractiveTutorial.d.ts | 4 +- src/InteractiveTutorial.js | 357 +------------ src/ScriptEditor/ui/Root.tsx | 19 +- src/Terminal/Terminal.ts | 49 +- src/Terminal/ui/TerminalRoot.tsx | 4 +- src/ui/GameRoot.tsx | 22 +- src/ui/InteractiveTutorial/ITutorialEvents.ts | 2 + .../InteractiveTutorialRoot.tsx | 493 ++++++++++++++++++ src/ui/React/CharacterOverview.tsx | 207 ++++---- src/ui/React/Modal.tsx | 4 +- src/ui/React/Overview.tsx | 34 ++ 11 files changed, 685 insertions(+), 510 deletions(-) create mode 100644 src/ui/InteractiveTutorial/ITutorialEvents.ts create mode 100644 src/ui/InteractiveTutorial/InteractiveTutorialRoot.tsx create mode 100644 src/ui/React/Overview.tsx diff --git a/src/InteractiveTutorial.d.ts b/src/InteractiveTutorial.d.ts index e92fff21c..28b73deb8 100644 --- a/src/InteractiveTutorial.d.ts +++ b/src/InteractiveTutorial.d.ts @@ -1,3 +1,5 @@ export declare function iTutorialNextStep(): void; +export declare function iTutorialPrevStep(): void; +export declare function iTutorialEnd(): void; export declare const ITutorial: { isRunning: boolean; currStep: number }; -export declare const iTutorialSteps: { [key: string]: number }; +export declare const iTutorialSteps: { [key: string]: number | undefined }; diff --git a/src/InteractiveTutorial.js b/src/InteractiveTutorial.js index 9925b6e77..bbead93e9 100644 --- a/src/InteractiveTutorial.js +++ b/src/InteractiveTutorial.js @@ -4,6 +4,8 @@ import { Settings } from "./Settings/Settings"; import { LiteratureNames } from "./Literature/data/LiteratureNames"; +import { ITutorialEvents } from "./ui/InteractiveTutorial/ITutorialEvents"; + import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners"; import { createElement } from "../utils/uiHelpers/createElement"; import { createPopup } from "../utils/uiHelpers/createPopup"; @@ -67,328 +69,6 @@ function iTutorialStart() { Engine.Counters.autoSaveCounter = Infinity; ITutorial.currStep = 0; ITutorial.isRunning = true; - - document.getElementById("interactive-tutorial-container").style.display = "block"; - - // Exit tutorial button - const exitButton = clearEventListeners("interactive-tutorial-exit"); - exitButton.addEventListener("click", function () { - iTutorialEnd(); - return false; - }); - - // Back button - const backButton = clearEventListeners("interactive-tutorial-back"); - backButton.addEventListener("click", function () { - iTutorialPrevStep(); - return false; - }); - - // Next button - const nextButton = clearEventListeners("interactive-tutorial-next"); - nextButton.addEventListener("click", function () { - iTutorialNextStep(); - return false; - }); - - iTutorialEvaluateStep(); -} - -function iTutorialEvaluateStep() { - if (!ITutorial.isRunning) { - return; - } - - // Disable and clear main menu - // const terminalMainMenu = clearEventListeners("terminal-menu-link"); - // const statsMainMenu = clearEventListeners("stats-menu-link"); - // const activeScriptsMainMenu = clearEventListeners("active-scripts-menu-link"); - // const hacknetMainMenu = clearEventListeners("hacknet-nodes-menu-link"); - // const cityMainMenu = clearEventListeners("city-menu-link"); - // const tutorialMainMenu = clearEventListeners("tutorial-menu-link"); - // terminalMainMenu.removeAttribute("class"); - // statsMainMenu.removeAttribute("class"); - // activeScriptsMainMenu.removeAttribute("class"); - // hacknetMainMenu.removeAttribute("class"); - // cityMainMenu.removeAttribute("class"); - // tutorialMainMenu.removeAttribute("class"); - - // Interactive Tutorial Next button - const nextBtn = document.getElementById("interactive-tutorial-next"); - - switch (ITutorial.currStep) { - case iTutorialSteps.Start: - iTutorialSetText( - "Welcome to Bitburner, a cyberpunk-themed incremental RPG! " + - "The game takes place in a dark, dystopian future... The year is 2077...

" + - "This tutorial will show you the basics of the game. " + - "You may skip the tutorial at any time.", - ); - nextBtn.style.display = "inline-block"; - break; - case iTutorialSteps.GoToCharacterPage: - iTutorialSetText( - "Let's start by heading to the Stats page. Click the Stats tab on " + - "the main navigation menu (left-hand side of the screen)", - ); - nextBtn.style.display = "none"; - break; - case iTutorialSteps.CharacterPage: - iTutorialSetText( - "The Stats page shows a lot of important information about your progress, " + - "such as your skills, money, and bonuses. ", - ); - nextBtn.style.display = "inline-block"; - break; - case iTutorialSteps.CharacterGoToTerminalPage: - iTutorialSetText( - "Let's head to your computer's terminal by clicking the Terminal tab on the " + - "main navigation menu.", - ); - nextBtn.style.display = "none"; - break; - case iTutorialSteps.TerminalIntro: - iTutorialSetText( - "The Terminal is used to interface with your home computer as well as " + - "all of the other machines around the world.", - ); - nextBtn.style.display = "inline-block"; - break; - case iTutorialSteps.TerminalHelp: - iTutorialSetText( - "Let's try it out. Start by entering the help command into the Terminal " + - "(Don't forget to press Enter after typing the command)", - ); - nextBtn.style.display = "none"; // next step triggered by terminal command - break; - case iTutorialSteps.TerminalLs: - iTutorialSetText( - "The help command displays a list of all available Terminal commands, how to use them, " + - "and a description of what they do.

Let's try another command. Enter the ls command.", - ); - nextBtn.style.display = "none"; // next step triggered by terminal command - break; - case iTutorialSteps.TerminalScan: - iTutorialSetText( - " ls is a basic command that shows files " + - "on the computer. Right now, it shows that you have a program called NUKE.exe on your computer. " + - "We'll get to what this does later.

Using your home computer's terminal, you can connect " + - "to other machines throughout the world. Let's do that now by first entering " + - "the scan command.", - ); - nextBtn.style.display = "none"; // next step triggered by terminal command - break; - case iTutorialSteps.TerminalScanAnalyze1: - iTutorialSetText( - "The scan command shows all available network connections. In other words, " + - "it displays a list of all servers that can be connected to from your " + - "current machine. A server is identified by its hostname.

" + - "That's great and all, but there's so many servers. Which one should you go to? " + - "The scan-analyze command gives some more detailed information about servers on the " + - "network. Try it now!", - ); - nextBtn.style.display = "none"; // next step triggered by terminal command - break; - case iTutorialSteps.TerminalScanAnalyze2: - iTutorialSetText( - "You just ran scan-analyze with a depth of one. This command shows more detailed " + - "information about each server that you can connect to (servers that are a distance of " + - "one node away).

It is also possible to run scan-analyze with " + - "a higher depth. Let's try a depth of two with the following command: scan-analyze 2.", - ); - nextBtn.style.display = "none"; // next step triggered by terminal command - break; - case iTutorialSteps.TerminalConnect: - iTutorialSetText( - "Now you can see information about all servers that are up to two nodes away, as well " + - "as figure out how to navigate to those servers through the network. You can only connect to " + - "a server that is one node away. To connect to a machine, use the connect [hostname] command.

" + - "From the results of the scan-analyze command, we can see that the n00dles server is " + - "only one node away. Let's connect so it now using: connect n00dles", - ); - nextBtn.style.display = "none"; // next step triggered by terminal command - break; - case iTutorialSteps.TerminalAnalyze: - iTutorialSetText( - "You are now connected to another machine! What can you do now? You can hack it!

In the year 2077, currency has " + - "become digital and decentralized. People and corporations store their money " + - "on servers and computers. Using your hacking abilities, you can hack servers " + - "to steal money and gain experience.

" + - "Before you try to hack a server, you should run diagnostics using the analyze command.", - ); - nextBtn.style.display = "none"; // next step triggered by terminal command - break; - case iTutorialSteps.TerminalNuke: - iTutorialSetText( - "When the analyze command finishes running it will show useful information " + - "about hacking the server.

For this server, the required hacking skill is only 1, " + - "which means you can hack it right now. However, in order to hack a server " + - "you must first gain root access. The NUKE.exe program that we saw earlier on your " + - "home computer is a virus that will grant you root access to a machine if there are enough " + - "open ports.

The analyze results shows that there do not need to be any open ports " + - "on this machine for the NUKE virus to work, so go ahead and run the virus using the " + - "run NUKE.exe command.", - ); - nextBtn.style.display = "none"; // next step triggered by terminal command - break; - case iTutorialSteps.TerminalManualHack: - iTutorialSetText( - "You now have root access! You can hack the server using the hack command. " + - "Try doing that now.", - ); - nextBtn.style.display = "none"; // next step triggered by terminal command - break; - case iTutorialSteps.TerminalHackingMechanics: - iTutorialSetText( - "You are now attempting to hack the server. Performing a hack takes time and " + - "only has a certain percentage chance " + - "of success. This time and success chance is determined by a variety of factors, including " + - "your hacking skill and the server's security level.

" + - "If your attempt to hack the server is successful, you will steal a certain percentage " + - "of the server's total money. This percentage is affected by your hacking skill and " + - "the server's security level.

The amount of money on a server is not limitless. So, if " + - "you constantly hack a server and deplete its money, then you will encounter " + - "diminishing returns in your hacking.", - ); - nextBtn.style.display = "inline-block"; - break; - case iTutorialSteps.TerminalCreateScript: - iTutorialSetText( - "Hacking is the core mechanic of the game and is necessary for progressing. However, " + - "you don't want to be hacking manually the entire time. You can automate your hacking " + - "by writing scripts!

To create a new script or edit an existing one, you can use the nano " + - "command. Scripts must end with the .script extension. Let's make a script now by " + - "entering nano n00dles.script after the hack command finishes running (Sidenote: Pressing ctrl + c" + - " will end a command like hack early)", - ); - nextBtn.style.display = "none"; // next step triggered by terminal command - break; - case iTutorialSteps.TerminalTypeScript: - iTutorialSetText( - "This is the script editor. You can use it to program your scripts. Scripts are " + - "written in a simplified version of javascript. Copy and paste the following code into the script editor:

" + - "
" +
-          "while(true) {\n" +
-          "  hack('n00dles');\n" +
-          "}
" + - "For anyone with basic programming experience, this code should be straightforward. " + - "This script will continuously hack the n00dles server.

" + - "To save and close the script editor, press the button in the bottom left, or press ctrl + b.", - ); - nextBtn.style.display = "none"; // next step triggered in saveAndCloseScriptEditor() (Script.js) - break; - case iTutorialSteps.TerminalFree: - iTutorialSetText( - "Now we'll run the script. Scripts require a certain amount of RAM to run, and can be " + - "run on any machine which you have root access to. Different servers have different " + - "amounts of RAM. You can also purchase more RAM for your home server.

To check how much " + - "RAM is available on this machine, enter the free command.", - ); - nextBtn.style.display = "none"; // next step triggered by terminal commmand - break; - case iTutorialSteps.TerminalRunScript: - iTutorialSetText( - "We have 4GB of free RAM on this machine, which is enough to run our " + - "script. Let's run our script using run n00dles.script.", - ); - nextBtn.style.display = "none"; // next step triggered by terminal commmand - break; - case iTutorialSteps.TerminalGoToActiveScriptsPage: - iTutorialSetText( - "Your script is now running! " + - "It will continuously run in the background and will automatically stop if " + - "the code ever completes (the n00dles.script will never complete because it " + - "runs an infinite loop).

These scripts can passively earn you income and hacking experience. " + - "Your scripts will also earn money and experience while you are offline, although at a " + - "slightly slower rate.

" + - "Let's check out some statistics for our running scripts by clicking the " + - "Active Scripts link in the main navigation menu.", - ); - nextBtn.style.display = "none"; - break; - case iTutorialSteps.ActiveScriptsPage: - iTutorialSetText( - "This page displays information about all of your scripts that are " + - "running across every server. You can use this to gauge how well " + - "your scripts are doing. Let's go back to the Terminal", - ); - nextBtn.style.display = "none"; - break; - case iTutorialSteps.ActiveScriptsToTerminal: - iTutorialSetText( - "One last thing about scripts, each active script contains logs that detail " + - "what it's doing. We can check these logs using the tail command. Do that " + - "now for the script we just ran by typing tail n00dles.script", - ); - nextBtn.style.display = "none"; // next step triggered by terminal command - break; - case iTutorialSteps.TerminalTailScript: - iTutorialSetText( - "The log for this script won't show much right now (it might show nothing at all) because it " + - "just started running...but check back again in a few minutes!

" + - "This covers the basics of hacking. To learn more about writing " + - "scripts, select the Tutorial link in the " + - "main navigation menu to look at the documentation. " + - "If you are an experienced JavaScript " + - "developer, I would highly suggest you check out the section on " + - "NetscriptJS/Netscript 2.0, it's faster and more powerful.

For now, let's move on to something else!", - ); - nextBtn.style.display = "inline-block"; - break; - case iTutorialSteps.GoToHacknetNodesPage: - iTutorialSetText( - "Hacking is not the only way to earn money. One other way to passively " + - "earn money is by purchasing and upgrading Hacknet Nodes. Let's go to " + - "the Hacknet page through the main navigation menu now.", - ); - nextBtn.style.display = "none"; - break; - case iTutorialSteps.HacknetNodesIntroduction: - iTutorialSetText( - "here you can purchase new Hacknet Nodes and upgrade your " + "existing ones. Let's purchase a new one now.", - ); - nextBtn.style.display = "none"; // Next step triggered by purchaseHacknet() (HacknetNode.js) - break; - case iTutorialSteps.HacknetNodesGoToWorldPage: - iTutorialSetText( - "You just purchased a Hacknet Node! This Hacknet Node will passively " + - "earn you money over time, both online and offline. When you get enough " + - " money, you can upgrade " + - "your newly-purchased Hacknet Node below.

" + - "Let's go to the City page through the main navigation menu.", - ); - nextBtn.style.display = "none"; - break; - case iTutorialSteps.WorldDescription: - iTutorialSetText( - "This page lists all of the different locations you can currently " + - "travel to. Each location has something that you can do. " + - "There's a lot of content out in the world, make sure " + - "you explore and discover!

" + - "Lastly, click on the Tutorial link in the main navigation menu.", - ); - nextBtn.style.display = "none"; - break; - case iTutorialSteps.TutorialPageInfo: - iTutorialSetText( - "This page contains a lot of different documentation about the game's " + - "content and mechanics. I know it's a lot, but I highly suggest you read " + - "(or at least skim) through this before you start playing. That's the end of the tutorial. " + - "Hope you enjoy the game!", - ); - nextBtn.style.display = "inline-block"; - nextBtn.innerHTML = "Finish Tutorial"; - break; - case iTutorialSteps.End: - iTutorialEnd(); - break; - default: - throw new Error("Invalid tutorial step"); - } - - if (ITutorial.stepIsDone[ITutorial.currStep] === true) { - nextBtn.style.display = "inline-block"; - } } // Go to the next step and evaluate it @@ -397,7 +77,8 @@ function iTutorialNextStep() { if (ITutorial.currStep < iTutorialSteps.End) { ITutorial.currStep += 1; } - iTutorialEvaluateStep(); + if (ITutorial.currStep === iTutorialSteps.End) iTutorialEnd(); + ITutorialEvents.emit(); } // Go to previous step and evaluate @@ -405,22 +86,11 @@ function iTutorialPrevStep() { if (ITutorial.currStep > iTutorialSteps.Start) { ITutorial.currStep -= 1; } - iTutorialEvaluateStep(); + ITutorialEvents.emit(); } function iTutorialEnd() { - // Re-enable auto save - if (Settings.AutosaveInterval === 0) { - Engine.Counters.autoSaveCounter = Infinity; - } else { - Engine.Counters.autoSaveCounter = Settings.AutosaveInterval * 5; - } - - Engine.init(); - - ITutorial.currStep = iTutorialSteps.End; ITutorial.isRunning = false; - document.getElementById("interactive-tutorial-container").style.display = "none"; // Create a popup with final introductory stuff const popupId = "interactive-tutorial-ending-popup"; @@ -445,20 +115,7 @@ function iTutorialEnd() { createPopup(popupId, [txt, gotitBtn]); Player.getHomeComputer().messages.push(LiteratureNames.HackersStartingHandbook); + ITutorialEvents.emit(); } -let textBox = null; -(function () { - function set() { - textBox = document.getElementById("interactive-tutorial-text"); - document.removeEventListener("DOMContentLoaded", set); - } - document.addEventListener("DOMContentLoaded", set); -})(); - -function iTutorialSetText(txt) { - textBox.innerHTML = txt; - textBox.parentElement.scrollTop = 0; // this resets scroll position -} - -export { iTutorialSteps, iTutorialEnd, iTutorialStart, iTutorialNextStep, ITutorial }; +export { iTutorialSteps, iTutorialEnd, iTutorialStart, iTutorialNextStep, ITutorial, iTutorialPrevStep }; diff --git a/src/ScriptEditor/ui/Root.tsx b/src/ScriptEditor/ui/Root.tsx index 6fafaef07..5b4912453 100644 --- a/src/ScriptEditor/ui/Root.tsx +++ b/src/ScriptEditor/ui/Root.tsx @@ -103,6 +103,7 @@ export function Root(props: IProps): React.ReactElement { } lastPosition = null; + // this is duplicate code with saving later. if (ITutorial.isRunning && ITutorial.currStep === iTutorialSteps.TerminalTypeScript) { //Make sure filename + code properly follow tutorial if (filename !== "n00dles.script") { @@ -117,20 +118,24 @@ export function Root(props: IProps): React.ReactElement { //Save the script const server = props.player.getCurrentServer(); if (server === null) throw new Error("Server should not be null but it is."); + let found = false; for (let i = 0; i < server.scripts.length; i++) { if (filename == server.scripts[i].filename) { server.scripts[i].saveScript(code, props.player.currentServer, server.scripts); - props.router.toTerminal(); - return iTutorialNextStep(); + found = true; } } - // If the current script does NOT exist, create a new one - const script = new Script(); - script.saveScript(code, props.player.currentServer, server.scripts); - server.scripts.push(script); + if (!found) { + const script = new Script(); + script.saveScript(code, props.player.currentServer, server.scripts); + server.scripts.push(script); + } - return iTutorialNextStep(); + iTutorialNextStep(); + + props.router.toTerminal(); + return; } if (filename == "") { diff --git a/src/Terminal/Terminal.ts b/src/Terminal/Terminal.ts index 5f2890b1f..cd6929ad1 100644 --- a/src/Terminal/Terminal.ts +++ b/src/Terminal/Terminal.ts @@ -88,8 +88,8 @@ export class Terminal implements ITerminal { process(router: IRouter, player: IPlayer, cycles: number): void { if (this.action === null) return; this.action.timeLeft -= (CONSTANTS._idleSpeed * cycles) / 1000; - TerminalEvents.emit(); if (this.action.timeLeft < 0) this.finishAction(router, player, false); + TerminalEvents.emit(); } append(item: Output | Link): void { @@ -97,16 +97,15 @@ export class Terminal implements ITerminal { if (this.outputHistory.length > Settings.MaxTerminalCapacity) { this.outputHistory.slice(this.outputHistory.length - Settings.MaxTerminalCapacity); } + TerminalEvents.emit(); } print(s: string): void { this.append(new Output(s, "primary")); - TerminalEvents.emit(); } error(s: string): void { this.append(new Output(s, "error")); - TerminalEvents.emit(); } startHack(player: IPlayer): void { @@ -246,6 +245,7 @@ export class Terminal implements ITerminal { this.print("Cancelled"); } this.action = null; + TerminalEvents.emit(); } getFile(player: IPlayer, filename: string): Script | TextFile | string | null { @@ -515,51 +515,47 @@ export class Terminal implements ITerminal { switch (ITutorial.currStep) { case iTutorialSteps.TerminalHelp: if (commandArray.length === 1 && commandArray[0] == "help") { - TerminalHelpText.forEach((line) => this.print(line)); iTutorialNextStep(); } else { this.print("Bad command. Please follow the tutorial"); + return; } break; case iTutorialSteps.TerminalLs: if (commandArray.length === 1 && commandArray[0] == "ls") { - ls(this, router, player, s, commandArray.slice(1)); iTutorialNextStep(); } else { this.print("Bad command. Please follow the tutorial"); + return; } break; case iTutorialSteps.TerminalScan: if (commandArray.length === 1 && commandArray[0] == "scan") { - scan(this, router, player, s, commandArray.slice(1)); iTutorialNextStep(); } else { this.print("Bad command. Please follow the tutorial"); + return; } break; case iTutorialSteps.TerminalScanAnalyze1: if (commandArray.length == 1 && commandArray[0] == "scan-analyze") { - this.executeScanAnalyzeCommand(player, 1); iTutorialNextStep(); } else { this.print("Bad command. Please follow the tutorial"); + return; } break; case iTutorialSteps.TerminalScanAnalyze2: if (commandArray.length == 2 && commandArray[0] == "scan-analyze" && commandArray[1] === 2) { - this.executeScanAnalyzeCommand(player, 2); iTutorialNextStep(); } else { this.print("Bad command. Please follow the tutorial"); + return; } break; case iTutorialSteps.TerminalConnect: if (commandArray.length == 2) { if (commandArray[0] == "connect" && (commandArray[1] == "n00dles" || commandArray[1] == n00dlesServ.ip)) { - player.getCurrentServer().isConnectedTo = false; - player.currentServer = n00dlesServ.ip; - player.getCurrentServer().isConnectedTo = true; - this.print("Connected to n00dles"); iTutorialNextStep(); } else { this.print("Wrong command! Try again!"); @@ -567,80 +563,69 @@ export class Terminal implements ITerminal { } } else { this.print("Bad command. Please follow the tutorial"); + return; } break; case iTutorialSteps.TerminalAnalyze: if (commandArray.length === 1 && commandArray[0] === "analyze") { - if (commandArray.length !== 1) { - this.print("Incorrect usage of analyze command. Usage: analyze"); - return; - } - this.startAnalyze(); iTutorialNextStep(); } else { this.print("Bad command. Please follow the tutorial"); + return; } break; case iTutorialSteps.TerminalNuke: if (commandArray.length == 2 && commandArray[0] == "run" && commandArray[1] == "NUKE.exe") { - n00dlesServ.hasAdminRights = true; - this.print("NUKE successful! Gained root access to n00dles"); iTutorialNextStep(); } else { this.print("Bad command. Please follow the tutorial"); + return; } break; case iTutorialSteps.TerminalManualHack: if (commandArray.length == 1 && commandArray[0] == "hack") { - this.startHack(player); iTutorialNextStep(); } else { this.print("Bad command. Please follow the tutorial"); + return; } break; case iTutorialSteps.TerminalCreateScript: if (commandArray.length == 2 && commandArray[0] == "nano" && commandArray[1] == "n00dles.script") { - router.toScriptEditor("n00dles.script", ""); iTutorialNextStep(); } else { this.print("Bad command. Please follow the tutorial"); + return; } break; case iTutorialSteps.TerminalFree: if (commandArray.length == 1 && commandArray[0] == "free") { - free(this, router, player, s, commandArray.slice(1)); iTutorialNextStep(); } else { this.print("Bad command. Please follow the tutorial"); + return; } break; case iTutorialSteps.TerminalRunScript: if (commandArray.length == 2 && commandArray[0] == "run" && commandArray[1] == "n00dles.script") { - run(this, router, player, s, commandArray.slice(1)); iTutorialNextStep(); } else { this.print("Bad command. Please follow the tutorial"); + return; } break; case iTutorialSteps.ActiveScriptsToTerminal: if (commandArray.length == 2 && commandArray[0] == "tail" && commandArray[1] == "n00dles.script") { - // Check that the script exists on this machine - const runningScript = findRunningScript("n00dles.script", [], player.getCurrentServer()); - if (runningScript == null) { - this.print("Error: No such script exists"); - return; - } - logBoxCreate(runningScript); iTutorialNextStep(); } else { this.print("Bad command. Please follow the tutorial"); + return; } break; default: - this.print("Please follow the tutorial, or click 'Exit Tutorial' if you'd like to skip it"); + this.print("Please follow the tutorial, or click 'EXIT' if you'd like to skip it"); return; } - return; } /****************** END INTERACTIVE TUTORIAL ******************/ /* Command parser */ diff --git a/src/Terminal/ui/TerminalRoot.tsx b/src/Terminal/ui/TerminalRoot.tsx index ce9005172..854353a8c 100644 --- a/src/Terminal/ui/TerminalRoot.tsx +++ b/src/Terminal/ui/TerminalRoot.tsx @@ -50,9 +50,9 @@ interface IProps { export function TerminalRoot({ terminal, router, player }: IProps): React.ReactElement { const scrollHook = useRef(null); - const setRerender = useState(false)[1]; + const setRerender = useState(0)[1]; function rerender(): void { - setRerender((old) => !old); + setRerender((old) => old + 1); } useEffect(() => { diff --git a/src/ui/GameRoot.tsx b/src/ui/GameRoot.tsx index bc28b2a88..535a028f1 100644 --- a/src/ui/GameRoot.tsx +++ b/src/ui/GameRoot.tsx @@ -9,6 +9,9 @@ import { onExport } from "../ExportBonus"; import { LocationName } from "../Locations/data/LocationNames"; import { Location } from "../Locations/Location"; import { Locations } from "../Locations/Locations"; +import { ITutorial } from "../InteractiveTutorial"; +import { InteractiveTutorialRoot } from "./InteractiveTutorial/InteractiveTutorialRoot"; +import { ITutorialEvents } from "./InteractiveTutorial/ITutorialEvents"; import { Faction } from "../Faction/Faction"; import { prestigeAugmentation } from "../Prestige"; @@ -31,6 +34,7 @@ import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import { Page, IRouter } from "./Router"; +import { Overview } from "./React/Overview"; import { SidebarRoot } from "../Sidebar/ui/SidebarRoot"; import { AugmentationsRoot } from "../Augmentation/ui/AugmentationsRoot"; import { DevMenuRoot } from "../DevMenu"; @@ -179,6 +183,7 @@ function determineStartPage(player: IPlayer): Page { export function GameRoot({ player, engine, terminal }: IProps): React.ReactElement { const classes = useStyles(); const [page, setPage] = useState(determineStartPage(player)); + const setRerender = useState(0)[1]; const [faction, setFaction] = useState( player.currentWorkFactionName ? Factions[player.currentWorkFactionName] : (undefined as unknown as Faction), ); @@ -193,6 +198,13 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme const [cinematicText, setCinematicText] = useState(""); + function rerender(): void { + setRerender((old) => old + 1); + } + useEffect(() => { + return ITutorialEvents.subscribe(rerender); + }, []); + Router = { page: () => page, toActiveScripts: () => setPage(Page.ActiveScripts), @@ -255,13 +267,19 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme useEffect(() => { filename = ""; code = ""; - window.scrollTo(0, 0); + if (page !== Page.Terminal) window.scrollTo(0, 0); }); return ( - saveObject.saveGame(engine.indexedDb)} /> + + {!ITutorial.isRunning ? ( + saveObject.saveGame(engine.indexedDb)} /> + ) : ( + + )} + {page === Page.BitVerse ? ( ) : page === Page.Infiltration ? ( diff --git a/src/ui/InteractiveTutorial/ITutorialEvents.ts b/src/ui/InteractiveTutorial/ITutorialEvents.ts new file mode 100644 index 000000000..fbd1ae944 --- /dev/null +++ b/src/ui/InteractiveTutorial/ITutorialEvents.ts @@ -0,0 +1,2 @@ +import { EventEmitter } from "../../utils/EventEmitter"; +export const ITutorialEvents = new EventEmitter<[]>(); diff --git a/src/ui/InteractiveTutorial/InteractiveTutorialRoot.tsx b/src/ui/InteractiveTutorial/InteractiveTutorialRoot.tsx new file mode 100644 index 000000000..dc5d86fe2 --- /dev/null +++ b/src/ui/InteractiveTutorial/InteractiveTutorialRoot.tsx @@ -0,0 +1,493 @@ +import React, { useState, useEffect } from "react"; + +import Paper from "@mui/material/Paper"; +import Typography from "@mui/material/Typography"; +import IconButton from "@mui/material/IconButton"; +import Button from "@mui/material/Button"; +import ArrowForwardIos from "@mui/icons-material/ArrowForwardIos"; +import ArrowBackIos from "@mui/icons-material/ArrowBackIos"; +import { ITutorialEvents } from "./ITutorialEvents"; +import { use } from "../Context"; + +import ListItem from "@mui/material/ListItem"; +import EqualizerIcon from "@mui/icons-material/Equalizer"; +import LastPageIcon from "@mui/icons-material/LastPage"; +import HelpIcon from "@mui/icons-material/Help"; +import AccountTreeIcon from "@mui/icons-material/AccountTree"; +import StorageIcon from "@mui/icons-material/Storage"; +import LocationCityIcon from "@mui/icons-material/LocationCity"; + +import { + iTutorialPrevStep, + iTutorialNextStep, + ITutorial, + iTutorialSteps, + iTutorialEnd, +} from "../../InteractiveTutorial"; + +interface IContent { + content: React.ReactElement; + canNext: boolean; +} + +const contents: { [number: string]: IContent | undefined } = { + [iTutorialSteps.Start as number]: { + content: ( + + Welcome to Bitburner, a cyberpunk-themed incremental RPG! The game takes place in a dark, dystopian future... + The year is 2077... +
+
+ This tutorial will show you the basics of the game. You may skip the tutorial at any time. +
+ ), + canNext: true, + }, + [iTutorialSteps.GoToCharacterPage as number]: { + content: ( + <> + Let's start by heading to the Stats page. Click + + + Stats + + + on the main navigation menu (left-hand side of the screen) + + ), + canNext: false, + }, + [iTutorialSteps.CharacterPage as number]: { + content: ( + <> + + + Stats + + + shows a lot of important information about your progress, such as your skills, money, and bonuses. + + + ), + canNext: true, + }, + [iTutorialSteps.CharacterGoToTerminalPage as number]: { + content: ( + <> + Let's head to your computer's terminal by clicking + + + Terminal + + on the main navigation menu. + + ), + canNext: false, + }, + [iTutorialSteps.TerminalIntro as number]: { + content: ( + <> + + + Terminal + + + is used to interface with your home computer as well as all of the other machines around the world. + + + ), + canNext: true, + }, + [iTutorialSteps.TerminalHelp as number]: { + content: ( + <> + + Let's try it out. Start by entering the help command + into the + + + + Terminal + + (Don't forget to press Enter after typing the command) + + ), + canNext: false, + }, + [iTutorialSteps.TerminalLs as number]: { + content: ( + <> + + The help command displays a list of all available + + + + Terminal + + + commands, how to use them, and a description of what they do.
+
+ Let's try another command. Enter the ls command. +
+ + ), + canNext: false, + }, + [iTutorialSteps.TerminalScan as number]: { + content: ( + + ls is a basic command that shows files on the computer. + Right now, it shows that you have a program called{" "} + NUKE.exe on your computer. We'll get to what this does + later.
+
+ Using your home computer's terminal, you can connect to other machines throughout the world. Let's do that now + by first entering the scan command. +
+ ), + canNext: false, + }, + [iTutorialSteps.TerminalScanAnalyze1 as number]: { + content: ( + + The scan command shows all available network connections. + In other words, it displays a list of all servers that can be connected to from your current machine. A server + is identified by its hostname.
+
+ That's great and all, but there's so many servers. Which one should you go to? The{" "} + scan-analyze command gives some more detailed information + about servers on the network. Try it now! +
+ ), + canNext: false, + }, + [iTutorialSteps.TerminalScanAnalyze2 as number]: { + content: ( + + You just ran scan-analyze with a depth of one. This + command shows more detailed information about each server that you can connect to (servers that are a distance + of one node away).
+
It is also possible to run scan-analyze with a + higher depth. Let's try a depth of two with the following command:{" "} + scan-analyze 2. +
+ ), + canNext: false, + }, + [iTutorialSteps.TerminalConnect as number]: { + content: ( + + Now you can see information about all servers that are up to two nodes away, as well as figure out how to + navigate to those servers through the network. You can only connect to a server that is one node away. To + connect to a machine, use the connect [hostname] command. +
+
+ From the results of the scan-analyze command, we can see + that the n00dles server is only one node away. Let's + connect so it now using: connect n00dles +
+ ), + canNext: false, + }, + [iTutorialSteps.TerminalAnalyze as number]: { + content: ( + + You are now connected to another machine! What can you do now? You can hack it! +
+
In the year 2077, currency has become digital and decentralized. People and corporations store their + money on servers and computers. Using your hacking abilities, you can hack servers to steal money and gain + experience.
+
+ Before you try to hack a server, you should run diagnostics using the{" "} + analyze command. +
+ ), + canNext: false, + }, + [iTutorialSteps.TerminalNuke as number]: { + content: ( + + When the analyze command finishes running it will show + useful information about hacking the server.
+
For this server, the required hacking skill is only 1, which + means you can hack it right now. However, in order to hack a server you must first gain root access. The{" "} + NUKE.exe program that we saw earlier on your home computer + is a virus that will grant you root access to a machine if there are enough open ports. +
+
The analyze results shows that there do not need to + be any open ports on this machine for the NUKE virus to work, so go ahead and run the virus using the{" "} + run NUKE.exe command. +
+ ), + canNext: true, + }, + [iTutorialSteps.TerminalManualHack as number]: { + content: ( + + You now have root access! You can hack the server using the{" "} + hack command. Try doing that now. + + ), + canNext: true, + }, + [iTutorialSteps.TerminalHackingMechanics as number]: { + content: ( + + You are now attempting to hack the server. Performing a hack takes time and only has a certain percentage chance + of success. This time and success chance is determined by a variety of factors, including your hacking skill and + the server's security level. +
+
+ If your attempt to hack the server is successful, you will steal a certain percentage of the server's total + money. This percentage is affected by your hacking skill and the server's security level. +
+
+ The amount of money on a server is not limitless. So, if you constantly hack a server and deplete its money, + then you will encounter diminishing returns in your hacking. +
+ ), + canNext: true, + }, + [iTutorialSteps.TerminalCreateScript as number]: { + content: ( + + Hacking is the core mechanic of the game and is necessary for progressing. However, you don't want to be hacking + manually the entire time. You can automate your hacking by writing scripts! +
+
+ To create a new script or edit an existing one, you can use the{" "} + nano + command. Scripts must end with the .script extension. + Let's make a script now by entering nano n00dles.script{" "} + after the hack command finishes running (Sidenote: Pressing ctrl + c will end a command like hack early) +
+ ), + canNext: false, + }, + [iTutorialSteps.TerminalTypeScript as number]: { + content: ( + <> + + This is the script editor. You can use it to program your scripts. Scripts are written in a simplified version + of javascript. Copy and paste the following code into the script editor:
+
+
+          while(true) {"{"}
+          hack('n00dles');
+          {"}"}
+        
+ + For anyone with basic programming experience, this code should be straightforward. This script will + continuously hack the n00dles server. +
+
+ To save and close the script editor, press the button in the bottom left, or press ctrl + b. +
+ + ), + canNext: false, + }, + [iTutorialSteps.TerminalFree as number]: { + content: ( + + Now we'll run the script. Scripts require a certain amount of RAM to run, and can be run on any machine which + you have root access to. Different servers have different amounts of RAM. You can also purchase more RAM for + your home server. +
+
+ To check how much RAM is available on this machine, enter the{" "} + free command. +
+ ), + canNext: false, + }, + [iTutorialSteps.TerminalRunScript as number]: { + content: ( + + We have 4GB of free RAM on this machine, which is enough to run our script. Let's run our script using{" "} + run n00dles.script. + + ), + canNext: false, + }, + [iTutorialSteps.TerminalGoToActiveScriptsPage as number]: { + content: ( + <> + + Your script is now running! It will continuously run in the background and will automatically stop if the code + ever completes (the n00dles.script will never complete + because it runs an infinite loop).
+
+ These scripts can passively earn you income and hacking experience. Your scripts will also earn money and + experience while you are offline, although at a slightly slower rate.
+
+ Let's check out some statistics for our running scripts by clicking{" "} +
+ + + Active Scripts + + + ), + canNext: false, + }, + [iTutorialSteps.ActiveScriptsPage as number]: { + content: ( + <> + + This page displays information about all of your scripts that are running across every server. You can use + this to gauge how well your scripts are doing. Let's go back to + + + + Terminal + + + ), + canNext: false, + }, + [iTutorialSteps.ActiveScriptsToTerminal as number]: { + content: ( + + One last thing about scripts, each active script contains logs that detail what it's doing. We can check these + logs using the tail command. Do that now for the script we + just ran by typing tail n00dles.script + + ), + canNext: false, + }, + [iTutorialSteps.TerminalTailScript as number]: { + content: ( + <> + + The log for this script won't show much right now (it might show nothing at all) because it just started + running...but check back again in a few minutes!
+
+ This covers the basics of hacking. To learn more about writing scripts, select +
+ + + Tutorial + + + in the main navigation menu to look at the documentation. + + If you are an experienced JavaScript developer, I would highly suggest you check out the section on + NetscriptJS/Netscript 2.0, it's faster and more powerful. + +
+
+ For now, let's move on to something else! +
+ + ), + canNext: true, + }, + [iTutorialSteps.GoToHacknetNodesPage as number]: { + content: ( + <> + + Hacking is not the only way to earn money. One other way to passively earn money is by purchasing and + upgrading Hacknet Nodes. Let's go to + + + + Hacknet + + through the main navigation menu now. + + ), + canNext: true, + }, + [iTutorialSteps.HacknetNodesIntroduction as number]: { + content: ( + + here you can purchase new Hacknet Nodes and upgrade your existing ones. Let's purchase a new one now. + + ), + canNext: true, + }, + [iTutorialSteps.HacknetNodesGoToWorldPage as number]: { + content: ( + <> + + You just purchased a Hacknet Node! This Hacknet Node will passively earn you money over time, both online and + offline. When you get enough money, you can upgrade your newly-purchased Hacknet Node below. +
+
+ Let's go to +
+ + + City + + + ), + canNext: true, + }, + [iTutorialSteps.WorldDescription as number]: { + content: ( + <> + + This page lists all of the different locations you can currently travel to. Each location has something that + you can do. There's a lot of content out in the world, make sure you explore and discover! +
+
+ Lastly, click on +
+ + + Tutorial + + + ), + canNext: true, + }, + [iTutorialSteps.TutorialPageInfo as number]: { + content: ( + + This page contains a lot of different documentation about the game's content and mechanics.{" "} + + {" "} + I know it's a lot, but I highly suggest you read (or at least skim) through this before you start playing + + . That's the end of the tutorial. Hope you enjoy the game! + + ), + canNext: true, + }, + [iTutorialSteps.End as number]: { + content: , + canNext: true, + }, +}; + +export function InteractiveTutorialRoot(): React.ReactElement { + const setRerender = useState(false)[1]; + function rerender(): void { + setRerender((old) => !old); + } + + useEffect(() => { + return ITutorialEvents.subscribe(rerender); + }, []); + const step = ITutorial.currStep; + const content = contents[step]; + if (content === undefined) throw new Error("error in the tutorial"); + return ( + + {content.content} + + + + {content.canNext && ( + + + + )} +
+
+ +
+ ); +} diff --git a/src/ui/React/CharacterOverview.tsx b/src/ui/React/CharacterOverview.tsx index db3a7c3c0..1295576bb 100644 --- a/src/ui/React/CharacterOverview.tsx +++ b/src/ui/React/CharacterOverview.tsx @@ -21,8 +21,9 @@ import SaveAltIcon from "@mui/icons-material/SaveAlt"; import { colors } from "./Theme"; import { Settings } from "../../Settings/Settings"; -import { use } from "../../ui/Context"; -import { Page } from "../../ui/Router"; +import { use } from "../Context"; +import { Page } from "../Router"; +import { Overview } from "./Overview"; interface IProps { save: () => void; @@ -105,9 +106,6 @@ const useStyles = makeStyles({ int: { color: colors.int, }, - nobackground: { - backgroundColor: "#0000", - }, }); export function CharacterOverview({ save }: IProps): React.ReactElement { @@ -115,9 +113,7 @@ export function CharacterOverview({ save }: IProps): React.ReactElement { const router = use.Router(); if (router.page() === Page.BitVerse) return <>; - const setRerender = useState(false)[1]; - const [open, setOpen] = useState(true); useEffect(() => { const id = setInterval(() => setRerender((old) => !old), 600); @@ -126,120 +122,103 @@ export function CharacterOverview({ save }: IProps): React.ReactElement { const classes = useStyles(); return ( -
- - - - - - - - - HP  - - - - {numeralWrapper.formatHp(player.hp)} / {numeralWrapper.formatHp(player.max_hp)} - - - + + +
+ + + + HP  + + + + {numeralWrapper.formatHp(player.hp)} / {numeralWrapper.formatHp(player.max_hp)} + + + - - - Money  - - - - {numeralWrapper.formatMoney(player.money.toNumber())} - - - + + + Money  + + + + {numeralWrapper.formatMoney(player.money.toNumber())} + + + - - - Hack  - - - - {numeralWrapper.formatSkill(player.hacking_skill)} - - - + + + Hack  + + + + {numeralWrapper.formatSkill(player.hacking_skill)} + + + - - - Str  - - - - {numeralWrapper.formatSkill(player.strength)} - - - + + + Str  + + + + {numeralWrapper.formatSkill(player.strength)} + + + - - - Def  - - - - {numeralWrapper.formatSkill(player.defense)} - - - + + + Def  + + + {numeralWrapper.formatSkill(player.defense)} + + - - - Dex  - - - - {numeralWrapper.formatSkill(player.dexterity)} - - - - - - Agi  - - - - {numeralWrapper.formatSkill(player.agility)} - - - + + + Dex  + + + + {numeralWrapper.formatSkill(player.dexterity)} + + + + + + Agi  + + + {numeralWrapper.formatSkill(player.agility)} + + - - - Cha  - - - - {numeralWrapper.formatSkill(player.charisma)} - - - - - + + + Cha  + + + {numeralWrapper.formatSkill(player.charisma)} + + + + - - - - - - - - -
-
-
-
- - setOpen((old) => !old)}> - - - + + + + + + + + +
-
+ ); } diff --git a/src/ui/React/Modal.tsx b/src/ui/React/Modal.tsx index 14073344d..95f17734b 100644 --- a/src/ui/React/Modal.tsx +++ b/src/ui/React/Modal.tsx @@ -14,9 +14,9 @@ const useStyles = makeStyles((theme: Theme) => justifyContent: "center", }, paper: { - backgroundColor: theme.palette.background.paper, + backgroundColor: theme.palette.background.default, border: "2px solid " + theme.palette.primary.main, - boxShadow: theme.shadows[5], + boxShadow: `0px 3px 5px -1px ${theme.palette.primary.dark},0px 5px 8px 0px ${theme.palette.primary.dark},0px 1px 14px 0px ${theme.palette.primary.dark}`, padding: 2, maxWidth: "80%", maxHeight: "80%", diff --git a/src/ui/React/Overview.tsx b/src/ui/React/Overview.tsx new file mode 100644 index 000000000..fdbcc3f3c --- /dev/null +++ b/src/ui/React/Overview.tsx @@ -0,0 +1,34 @@ +import React, { useState } from "react"; + +import makeStyles from "@mui/styles/makeStyles"; +import Box from "@mui/material/Box"; +import Collapse from "@mui/material/Collapse"; +import Fab from "@mui/material/Fab"; +import VisibilityOffIcon from "@mui/icons-material/VisibilityOff"; + +const useStyles = makeStyles({ + nobackground: { + backgroundColor: "#0000", + }, +}); + +interface IProps { + children: JSX.Element[] | JSX.Element | React.ReactElement[] | React.ReactElement; +} + +export function Overview({ children }: IProps): React.ReactElement { + const [open, setOpen] = useState(true); + const classes = useStyles(); + return ( +
+ + {children} + + setOpen((old) => !old)}> + + + + +
+ ); +}