ITutorial in react

This commit is contained in:
Olivier Gagnon 2021-09-19 00:46:39 -04:00
parent 61e3959a25
commit 023f2b8309
11 changed files with 685 additions and 510 deletions

@ -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 };

@ -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...<br><br>" +
"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 <code class='interactive-tutorial-tab flashing-button'>Stats</code> tab on " +
"the main navigation menu (left-hand side of the screen)",
);
nextBtn.style.display = "none";
break;
case iTutorialSteps.CharacterPage:
iTutorialSetText(
"The <code class='interactive-tutorial-tab'>Stats</code> 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 <code class='interactive-tutorial-tab flashing-button'>Terminal</code> tab on the " +
"main navigation menu.",
);
nextBtn.style.display = "none";
break;
case iTutorialSteps.TerminalIntro:
iTutorialSetText(
"The <code class='interactive-tutorial-tab'>Terminal</code> 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 <code class='interactive-tutorial-command'>help</code> command into the <code class='interactive-tutorial-tab'>Terminal</code> " +
"(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 <code class='interactive-tutorial-command'>help</code> command displays a list of all available <code class='interactive-tutorial-tab'>Terminal</code> commands, how to use them, " +
"and a description of what they do. <br><br>Let's try another command. Enter the <code class='interactive-tutorial-command'>ls</code> command.",
);
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalScan:
iTutorialSetText(
" <code class='interactive-tutorial-command'>ls</code> is a basic command that shows files " +
"on the computer. Right now, it shows that you have a program called <code class='interactive-tutorial-command'>NUKE.exe</code> on your computer. " +
"We'll get to what this does later. <br><br>Using your home computer's terminal, you can connect " +
"to other machines throughout the world. Let's do that now by first entering " +
"the <code class='interactive-tutorial-command'>scan</code> command.",
);
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalScanAnalyze1:
iTutorialSetText(
"The <code class='interactive-tutorial-command'>scan</code> 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. <br><br> " +
"That's great and all, but there's so many servers. Which one should you go to? " +
"The <code class='interactive-tutorial-command'>scan-analyze</code> 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 <code class='interactive-tutorial-command'>scan-analyze</code> 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). <br><br> It is also possible to run <code class='interactive-tutorial-command'>scan-analyze</code> with " +
"a higher depth. Let's try a depth of two with the following command: <code class='interactive-tutorial-command'>scan-analyze 2</code>.",
);
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 <code class='interactive-tutorial-command'>connect [hostname]</code> command.<br><br>" +
"From the results of the <code class='interactive-tutorial-command'>scan-analyze</code> command, we can see that the <code class='interactive-tutorial-command'>n00dles</code> server is " +
"only one node away. Let's connect so it now using: <code class='interactive-tutorial-command'>connect n00dles</code>",
);
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!<br><br> 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. <br><br> " +
"Before you try to hack a server, you should run diagnostics using the <code class='interactive-tutorial-command'>analyze</code> command.",
);
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalNuke:
iTutorialSetText(
"When the <code class='interactive-tutorial-command'>analyze</code> command finishes running it will show useful information " +
"about hacking the server. <br><br> For this server, the required hacking skill is only <span class='character-hack-cell'>1</span>, " +
"which means you can hack it right now. However, in order to hack a server " +
"you must first gain root access. The <code class='interactive-tutorial-command'>NUKE.exe</code> 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.<br><br> The <code class='interactive-tutorial-command'>analyze</code> 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 " +
"<code class='interactive-tutorial-command'>run NUKE.exe</code> 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 <code class='interactive-tutorial-command'>hack</code> 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.<br><br>" +
"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.<br><br>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!<br><br>To create a new script or edit an existing one, you can use the <code class='interactive-tutorial-command'>nano</code> " +
"command. Scripts must end with the <code class='interactive-tutorial-command'>.script</code> extension. Let's make a script now by " +
"entering <code class='interactive-tutorial-command'>nano n00dles.script</code> 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: <br><br>" +
"<pre class='interactive-tutorial-code'>" +
"while(true) {\n" +
" hack('n00dles');\n" +
"}</pre>" +
"For anyone with basic programming experience, this code should be straightforward. " +
"This script will continuously hack the <code class='interactive-tutorial-command'>n00dles</code> server.<br><br>" +
"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.<br><br>To check how much " +
"RAM is available on this machine, enter the <code class='interactive-tutorial-command'>free</code> 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 <code class='interactive-tutorial-command'>run n00dles.script</code>.",
);
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 <code class='interactive-tutorial-command'>n00dles.script</code> will never complete because it " +
"runs an infinite loop). <br><br>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. <br><br> " +
"Let's check out some statistics for our running scripts by clicking the " +
"<code class='interactive-tutorial-tab flashing-button'>Active Scripts</code> 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 <code class='interactive-tutorial-tab flashing-button'>Terminal</code>",
);
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 <code class='interactive-tutorial-command'>tail</code> command. Do that " +
"now for the script we just ran by typing <code class='interactive-tutorial-command'>tail n00dles.script</code>",
);
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! <br><br>" +
"This covers the basics of hacking. To learn more about writing " +
"scripts, select the <code class='interactive-tutorial-tab'>Tutorial</code> link in the " +
"main navigation menu to look at the documentation. " +
"<strong style='background-color:#444;'>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.</strong><br><br>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 <code class='interactive-tutorial-tab flashing-button'>Hacknet</code> 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.<br><br>" +
"Let's go to the <code class='interactive-tutorial-tab flashing-button'>City</code> 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!<br><br>" +
"Lastly, click on the <code class='interactive-tutorial-tab flashing-button'>Tutorial</code> 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. <strong style='background-color:#444;'> I know it's a lot, but I highly suggest you read " +
"(or at least skim) through this before you start playing</strong>. 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 };

@ -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 == "") {

@ -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 */

@ -50,9 +50,9 @@ interface IProps {
export function TerminalRoot({ terminal, router, player }: IProps): React.ReactElement {
const scrollHook = useRef<HTMLDivElement>(null);
const setRerender = useState(false)[1];
const setRerender = useState(0)[1];
function rerender(): void {
setRerender((old) => !old);
setRerender((old) => old + 1);
}
useEffect(() => {

@ -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<Faction>(
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 (
<Context.Player.Provider value={player}>
<Context.Router.Provider value={Router}>
<CharacterOverview save={() => saveObject.saveGame(engine.indexedDb)} />
<Overview>
{!ITutorial.isRunning ? (
<CharacterOverview save={() => saveObject.saveGame(engine.indexedDb)} />
) : (
<InteractiveTutorialRoot />
)}
</Overview>
{page === Page.BitVerse ? (
<BitverseRoot flume={flume} enter={enterBitNode} quick={quick} />
) : page === Page.Infiltration ? (

@ -0,0 +1,2 @@
import { EventEmitter } from "../../utils/EventEmitter";
export const ITutorialEvents = new EventEmitter<[]>();

@ -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: (
<Typography>
Welcome to Bitburner, a cyberpunk-themed incremental RPG! The game takes place in a dark, dystopian future...
The year is 2077...
<br />
<br />
This tutorial will show you the basics of the game. You may skip the tutorial at any time.
</Typography>
),
canNext: true,
},
[iTutorialSteps.GoToCharacterPage as number]: {
content: (
<>
<Typography>Let's start by heading to the Stats page. Click</Typography>
<ListItem>
<EqualizerIcon color={"error"} />
<Typography color={"error"}>Stats</Typography>
</ListItem>
<Typography>on the main navigation menu (left-hand side of the screen)</Typography>
</>
),
canNext: false,
},
[iTutorialSteps.CharacterPage as number]: {
content: (
<>
<ListItem>
<EqualizerIcon color={"primary"} />
<Typography color={"primary"}>Stats</Typography>
</ListItem>
<Typography>
shows a lot of important information about your progress, such as your skills, money, and bonuses.
</Typography>
</>
),
canNext: true,
},
[iTutorialSteps.CharacterGoToTerminalPage as number]: {
content: (
<>
<Typography>Let's head to your computer's terminal by clicking</Typography>
<ListItem>
<EqualizerIcon color={"error"} />
<Typography color={"error"}>Terminal</Typography>
</ListItem>
<Typography>on the main navigation menu.</Typography>
</>
),
canNext: false,
},
[iTutorialSteps.TerminalIntro as number]: {
content: (
<>
<ListItem>
<EqualizerIcon color={"primary"} />
<Typography color={"primary"}>Terminal</Typography>
</ListItem>
<Typography>
is used to interface with your home computer as well as all of the other machines around the world.
</Typography>
</>
),
canNext: true,
},
[iTutorialSteps.TerminalHelp as number]: {
content: (
<>
<Typography>
Let's try it out. Start by entering the <code className="interactive-tutorial-command">help</code> command
into the
</Typography>
<ListItem>
<EqualizerIcon color={"primary"} />
<Typography color={"primary"}>Terminal</Typography>
</ListItem>
<Typography>(Don't forget to press Enter after typing the command)</Typography>
</>
),
canNext: false,
},
[iTutorialSteps.TerminalLs as number]: {
content: (
<>
<Typography>
The <code className="interactive-tutorial-command">help</code> command displays a list of all available
</Typography>
<ListItem>
<EqualizerIcon color={"primary"} />
<Typography color={"primary"}>Terminal</Typography>
</ListItem>
<Typography>
commands, how to use them, and a description of what they do. <br />
<br />
Let's try another command. Enter the <code className="interactive-tutorial-command">ls</code> command.
</Typography>
</>
),
canNext: false,
},
[iTutorialSteps.TerminalScan as number]: {
content: (
<Typography>
<code className="interactive-tutorial-command">ls</code> is a basic command that shows files on the computer.
Right now, it shows that you have a program called{" "}
<code className="interactive-tutorial-command">NUKE.exe</code> on your computer. We'll get to what this does
later. <br />
<br />
Using your home computer's terminal, you can connect to other machines throughout the world. Let's do that now
by first entering the <code className="interactive-tutorial-command">scan</code> command.
</Typography>
),
canNext: false,
},
[iTutorialSteps.TerminalScanAnalyze1 as number]: {
content: (
<Typography>
The <code className="interactive-tutorial-command">scan</code> 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. <br />
<br />
That's great and all, but there's so many servers. Which one should you go to? The{" "}
<code className="interactive-tutorial-command">scan-analyze</code> command gives some more detailed information
about servers on the network. Try it now!
</Typography>
),
canNext: false,
},
[iTutorialSteps.TerminalScanAnalyze2 as number]: {
content: (
<Typography>
You just ran <code className="interactive-tutorial-command">scan-analyze</code> 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). <br />
<br /> It is also possible to run <code className="interactive-tutorial-command">scan-analyze</code> with a
higher depth. Let's try a depth of two with the following command:{" "}
<code className="interactive-tutorial-command">scan-analyze 2</code>.
</Typography>
),
canNext: false,
},
[iTutorialSteps.TerminalConnect as number]: {
content: (
<Typography>
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 <code className="interactive-tutorial-command">connect [hostname]</code> command.
<br />
<br />
From the results of the <code className="interactive-tutorial-command">scan-analyze</code> command, we can see
that the <code className="interactive-tutorial-command">n00dles</code> server is only one node away. Let's
connect so it now using: <code className="interactive-tutorial-command">connect n00dles</code>
</Typography>
),
canNext: false,
},
[iTutorialSteps.TerminalAnalyze as number]: {
content: (
<Typography>
You are now connected to another machine! What can you do now? You can hack it!
<br />
<br /> 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. <br />
<br />
Before you try to hack a server, you should run diagnostics using the{" "}
<code className="interactive-tutorial-command">analyze</code> command.
</Typography>
),
canNext: false,
},
[iTutorialSteps.TerminalNuke as number]: {
content: (
<Typography>
When the <code className="interactive-tutorial-command">analyze</code> command finishes running it will show
useful information about hacking the server. <br />
<br /> For this server, the required hacking skill is only <span className="character-hack-cell">1</span>, which
means you can hack it right now. However, in order to hack a server you must first gain root access. The{" "}
<code className="interactive-tutorial-command">NUKE.exe</code> 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.
<br />
<br /> The <code className="interactive-tutorial-command">analyze</code> 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{" "}
<code className="interactive-tutorial-command">run NUKE.exe</code> command.
</Typography>
),
canNext: true,
},
[iTutorialSteps.TerminalManualHack as number]: {
content: (
<Typography>
You now have root access! You can hack the server using the{" "}
<code className="interactive-tutorial-command">hack</code> command. Try doing that now.
</Typography>
),
canNext: true,
},
[iTutorialSteps.TerminalHackingMechanics as number]: {
content: (
<Typography>
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.
<br />
<br />
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.
<br />
<br />
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.
</Typography>
),
canNext: true,
},
[iTutorialSteps.TerminalCreateScript as number]: {
content: (
<Typography>
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!
<br />
<br />
To create a new script or edit an existing one, you can use the{" "}
<code className="interactive-tutorial-command">nano</code>
command. Scripts must end with the <code className="interactive-tutorial-command">.script</code> extension.
Let's make a script now by entering <code className="interactive-tutorial-command">nano n00dles.script</code>{" "}
after the hack command finishes running (Sidenote: Pressing ctrl + c will end a command like hack early)
</Typography>
),
canNext: false,
},
[iTutorialSteps.TerminalTypeScript as number]: {
content: (
<>
<Typography>
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: <br />
</Typography>
<pre className="interactive-tutorial-code">
while(true) {"{"}
hack('n00dles');
{"}"}
</pre>
<Typography>
For anyone with basic programming experience, this code should be straightforward. This script will
continuously hack the <code className="interactive-tutorial-command">n00dles</code> server.
<br />
<br />
To save and close the script editor, press the button in the bottom left, or press ctrl + b.
</Typography>
</>
),
canNext: false,
},
[iTutorialSteps.TerminalFree as number]: {
content: (
<Typography>
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.
<br />
<br />
To check how much RAM is available on this machine, enter the{" "}
<code className="interactive-tutorial-command">free</code> command.
</Typography>
),
canNext: false,
},
[iTutorialSteps.TerminalRunScript as number]: {
content: (
<Typography>
We have 4GB of free RAM on this machine, which is enough to run our script. Let's run our script using{" "}
<code className="interactive-tutorial-command">run n00dles.script</code>.
</Typography>
),
canNext: false,
},
[iTutorialSteps.TerminalGoToActiveScriptsPage as number]: {
content: (
<>
<Typography>
Your script is now running! It will continuously run in the background and will automatically stop if the code
ever completes (the <code className="interactive-tutorial-command">n00dles.script</code> will never complete
because it runs an infinite loop). <br />
<br />
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. <br />
<br />
Let's check out some statistics for our running scripts by clicking{" "}
</Typography>
<ListItem>
<StorageIcon color={"error"} />
<Typography color={"error"}>Active Scripts</Typography>
</ListItem>
</>
),
canNext: false,
},
[iTutorialSteps.ActiveScriptsPage as number]: {
content: (
<>
<Typography>
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
</Typography>
<ListItem>
<EqualizerIcon color={"error"} />
<Typography color={"error"}>Terminal</Typography>
</ListItem>
</>
),
canNext: false,
},
[iTutorialSteps.ActiveScriptsToTerminal as number]: {
content: (
<Typography>
One last thing about scripts, each active script contains logs that detail what it's doing. We can check these
logs using the <code className="interactive-tutorial-command">tail</code> command. Do that now for the script we
just ran by typing <code className="interactive-tutorial-command">tail n00dles.script</code>
</Typography>
),
canNext: false,
},
[iTutorialSteps.TerminalTailScript as number]: {
content: (
<>
<Typography>
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! <br />
<br />
This covers the basics of hacking. To learn more about writing scripts, select
</Typography>
<ListItem>
<HelpIcon color={"primary"} />
<Typography color={"primary"}>Tutorial</Typography>
</ListItem>
<Typography>
in the main navigation menu to look at the documentation.
<strong style={{ backgroundColor: "#444" }}>
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.
</strong>
<br />
<br />
For now, let's move on to something else!
</Typography>
</>
),
canNext: true,
},
[iTutorialSteps.GoToHacknetNodesPage as number]: {
content: (
<>
<Typography>
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
</Typography>
<ListItem>
<AccountTreeIcon color={"error"} />
<Typography color={"error"}>Hacknet</Typography>
</ListItem>
<Typography>through the main navigation menu now.</Typography>
</>
),
canNext: true,
},
[iTutorialSteps.HacknetNodesIntroduction as number]: {
content: (
<Typography>
here you can purchase new Hacknet Nodes and upgrade your existing ones. Let's purchase a new one now.
</Typography>
),
canNext: true,
},
[iTutorialSteps.HacknetNodesGoToWorldPage as number]: {
content: (
<>
<Typography>
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.
<br />
<br />
Let's go to
</Typography>
<ListItem>
<LocationCityIcon color={"error"} />
<Typography color={"error"}>City</Typography>
</ListItem>
</>
),
canNext: true,
},
[iTutorialSteps.WorldDescription as number]: {
content: (
<>
<Typography>
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!
<br />
<br />
Lastly, click on
</Typography>
<ListItem>
<HelpIcon color={"error"} />
<Typography color={"error"}>Tutorial</Typography>
</ListItem>
</>
),
canNext: true,
},
[iTutorialSteps.TutorialPageInfo as number]: {
content: (
<Typography>
This page contains a lot of different documentation about the game's content and mechanics.{" "}
<strong style={{ backgroundColor: "#444" }}>
{" "}
I know it's a lot, but I highly suggest you read (or at least skim) through this before you start playing
</strong>
. That's the end of the tutorial. Hope you enjoy the game!
</Typography>
),
canNext: true,
},
[iTutorialSteps.End as number]: {
content: <Typography></Typography>,
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 (
<Paper square sx={{ maxWidth: "35vh", p: 2 }}>
{content.content}
<IconButton onClick={iTutorialPrevStep}>
<ArrowBackIos />
</IconButton>
{content.canNext && (
<IconButton onClick={iTutorialNextStep}>
<ArrowForwardIos />
</IconButton>
)}
<br />
<br />
<Button onClick={iTutorialEnd}>EXIT</Button>
</Paper>
);
}

@ -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 (
<div style={{ position: "fixed", top: 0, right: 0, zIndex: 1500 }}>
<Box display="flex" justifyContent="flex-end" flexDirection={"column"}>
<Collapse in={open}>
<Paper square>
<Box m={1}>
<Table size="small">
<TableBody>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.hp }}>HP&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.hp }}>
{numeralWrapper.formatHp(player.hp)}&nbsp;/&nbsp;{numeralWrapper.formatHp(player.max_hp)}
</Typography>
</TableCell>
</TableRow>
<Paper square>
<Box m={1}>
<Table size="small">
<TableBody>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.hp }}>HP&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.hp }}>
{numeralWrapper.formatHp(player.hp)}&nbsp;/&nbsp;{numeralWrapper.formatHp(player.max_hp)}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.money }}>Money&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.money }}>
{numeralWrapper.formatMoney(player.money.toNumber())}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.money }}>Money&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.money }}>
{numeralWrapper.formatMoney(player.money.toNumber())}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cell }}>
<Typography classes={{ root: classes.hack }}>Hack&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cell }}>
<Typography classes={{ root: classes.hack }}>
{numeralWrapper.formatSkill(player.hacking_skill)}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cell }}>
<Typography classes={{ root: classes.hack }}>Hack&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cell }}>
<Typography classes={{ root: classes.hack }}>
{numeralWrapper.formatSkill(player.hacking_skill)}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.combat }}>Str&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.combat }}>
{numeralWrapper.formatSkill(player.strength)}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.combat }}>Str&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.combat }}>
{numeralWrapper.formatSkill(player.strength)}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.combat }}>Def&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.combat }}>
{numeralWrapper.formatSkill(player.defense)}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.combat }}>Def&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.combat }}>{numeralWrapper.formatSkill(player.defense)}</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.combat }}>Dex&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.combat }}>
{numeralWrapper.formatSkill(player.dexterity)}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cell }}>
<Typography classes={{ root: classes.combat }}>Agi&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cell }}>
<Typography classes={{ root: classes.combat }}>
{numeralWrapper.formatSkill(player.agility)}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.combat }}>Dex&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.combat }}>
{numeralWrapper.formatSkill(player.dexterity)}
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cell }}>
<Typography classes={{ root: classes.combat }}>Agi&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cell }}>
<Typography classes={{ root: classes.combat }}>{numeralWrapper.formatSkill(player.agility)}</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.cha }}>Cha&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.cha }}>
{numeralWrapper.formatSkill(player.charisma)}
</Typography>
</TableCell>
</TableRow>
<Intelligence />
<Work />
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.cha }}>Cha&nbsp;</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.cha }}>{numeralWrapper.formatSkill(player.charisma)}</Typography>
</TableCell>
</TableRow>
<Intelligence />
<Work />
<TableRow>
<TableCell align="center" colSpan={2} classes={{ root: classes.cellNone }}>
<IconButton onClick={save}>
<SaveAltIcon color={Settings.AutosaveInterval !== 0 ? "primary" : "error"} />
</IconButton>
</TableCell>
</TableRow>
</TableBody>
</Table>
</Box>
</Paper>
</Collapse>
<Box display="flex" justifyContent="flex-end">
<Fab classes={{ root: classes.nobackground }} onClick={() => setOpen((old) => !old)}>
<VisibilityOffIcon color="primary" />
</Fab>
</Box>
<TableRow>
<TableCell align="center" colSpan={2} classes={{ root: classes.cellNone }}>
<IconButton onClick={save}>
<SaveAltIcon color={Settings.AutosaveInterval !== 0 ? "primary" : "error"} />
</IconButton>
</TableCell>
</TableRow>
</TableBody>
</Table>
</Box>
</div>
</Paper>
);
}

@ -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%",

34
src/ui/React/Overview.tsx Normal file

@ -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 (
<div style={{ position: "fixed", top: 0, right: 0, zIndex: 1500 }}>
<Box display="flex" justifyContent="flex-end" flexDirection={"column"}>
<Collapse in={open}>{children}</Collapse>
<Box display="flex" justifyContent="flex-end">
<Fab classes={{ root: classes.nobackground }} onClick={() => setOpen((old) => !old)}>
<VisibilityOffIcon color="primary" />
</Fab>
</Box>
</Box>
</div>
);
}