diff --git a/src/DevMenu/ui/Augmentations.tsx b/src/DevMenu/ui/Augmentations.tsx index 248dcc385..f4f0ef990 100644 --- a/src/DevMenu/ui/Augmentations.tsx +++ b/src/DevMenu/ui/Augmentations.tsx @@ -12,6 +12,7 @@ import MenuItem from "@material-ui/core/MenuItem"; import IconButton from "@material-ui/core/IconButton"; import ReplyAllIcon from "@material-ui/icons/ReplyAll"; import ReplyIcon from "@material-ui/icons/Reply"; +import ClearIcon from "@material-ui/icons/Clear"; interface IProps { player: IPlayer; @@ -33,6 +34,11 @@ export function Augmentations(props: IProps): React.ReactElement { props.player.queueAugmentation(augName); } } + + function clearAugs(): void { + props.player.augmentations = []; + } + return ( }> @@ -61,6 +67,13 @@ export function Augmentations(props: IProps): React.ReactElement { } + endAdornment={ + <> + + + + + } > {Object.values(AugmentationNames).map((aug) => ( diff --git a/src/Terminal.jsx b/src/Terminal.jsx index da5f973dc..13b6f868c 100644 --- a/src/Terminal.jsx +++ b/src/Terminal.jsx @@ -1,54 +1,5 @@ -import { determineAllPossibilitiesForTabCompletion } from "./Terminal/determineAllPossibilitiesForTabCompletion"; -import { tabCompletion } from "./Terminal/tabCompletion"; - -import { CONSTANTS } from "./Constants"; -import { Engine } from "./engine"; -import { FconfSettings } from "./Fconf/FconfSettings"; -import { Player } from "./Player"; -import { setTimeoutRef } from "./utils/SetTimeoutRef"; -import { Page, routing } from "./ui/navigationTracking"; -import { KEY } from "../utils/helpers/keyCodes"; -import { getTimestamp } from "../utils/helpers/getTimestamp"; - import { Terminal as TTerminal } from "./Terminal/Terminal"; const Terminal = new TTerminal(); -// Defines key commands in terminal -$(document).keydown(function (event) { - // Terminal - - if (event.keyCode === KEY.C && event.ctrlKey) { - if (Engine._actionInProgress) { - // Cancel action - // post("Cancelling..."); - Engine._actionInProgress = false; - Terminal.finishAction(true); - } else if (FconfSettings.ENABLE_BASH_HOTKEYS) { - // Dont prevent default so it still copies - Terminal.clear(); // Clear Terminal - } - } -}); - -$(document).keydown(function (e) { - if (routing.isOn(Page.Terminal)) { - if (e.which == KEY.CTRL) { - terminalCtrlPressed = true; - } else if (e.shiftKey) { - shiftKeyPressed = true; - } else if (terminalCtrlPressed || shiftKeyPressed || Terminal.contractOpen) { - // Don't focus - } else { - var inputTextBox = document.getElementById("terminal-input-text-box"); - if (inputTextBox != null) { - inputTextBox.focus(); - } - - terminalCtrlPressed = false; - shiftKeyPressed = false; - } - } -}); - export { Terminal }; diff --git a/src/Terminal/ITerminal.ts b/src/Terminal/ITerminal.ts index 253f0d36f..d26b25477 100644 --- a/src/Terminal/ITerminal.ts +++ b/src/Terminal/ITerminal.ts @@ -3,23 +3,44 @@ import { Script } from "../Script/Script"; import { IPlayer } from "../PersonObjects/IPlayer"; import { IEngine } from "../IEngine"; -export interface IOutput { +export class Output { text: string; color: "inherit" | "initial" | "primary" | "secondary" | "error" | "textPrimary" | "textSecondary" | undefined; + constructor( + text: string, + color: "inherit" | "initial" | "primary" | "secondary" | "error" | "textPrimary" | "textSecondary" | undefined, + ) { + this.text = text; + this.color = color; + } +} + +export class Link { + hostname: string; + constructor(hostname: string) { + this.hostname = hostname; + } +} + +export class TTimer { + time: number; + timeLeft: number; + action: "h" | "b" | "a"; + + constructor(time: number, action: "h" | "b" | "a") { + this.time = time; + this.timeLeft = time; + this.action = action; + } } export interface ITerminal { - // Flags to determine whether the player is currently running a hack or an analyze - hackFlag: boolean; - backdoorFlag: boolean; - analyzeFlag: boolean; - actionStarted: boolean; - actionTime: number; + action: TTimer | null; commandHistory: string[]; commandHistoryIndex: number; - outputHistory: IOutput[]; + outputHistory: (Output | Link)[]; // True if a Coding Contract prompt is opened contractOpen: boolean; @@ -53,4 +74,7 @@ export interface ITerminal { executeCommands(engine: IEngine, player: IPlayer, commands: string): void; // If there was any changes, will return true, once. pollChanges(): boolean; + process(player: IPlayer, cycles: number): void; + prestige(): void; + getProgressText(): string; } diff --git a/src/Terminal/Terminal.ts b/src/Terminal/Terminal.ts index fdc1eaab9..510c6da1e 100644 --- a/src/Terminal/Terminal.ts +++ b/src/Terminal/Terminal.ts @@ -1,5 +1,5 @@ import { postContent, hackProgressBarPost, hackProgressPost } from "../ui/postToTerminal"; -import { ITerminal, IOutput } from "./ITerminal"; +import { ITerminal, Output, Link, TTimer } from "./ITerminal"; import { IEngine } from "../IEngine"; import { IPlayer } from "../PersonObjects/IPlayer"; import { HacknetServer } from "../Hacknet/HacknetServer"; @@ -8,7 +8,6 @@ import { hackWorldDaemon } from "../RedPill"; import { Programs } from "../Programs/Programs"; import { CodingContractResult } from "../CodingContracts"; -import { Terminal as OldTerminal } from "../Terminal"; import { TextFile } from "../TextFile"; import { Script } from "../Script/Script"; import { isScriptFilename } from "../Script/ScriptHelpersTS"; @@ -24,6 +23,7 @@ import { TerminalHelpText } from "./HelpText"; import { GetServerByHostname, getServer, getServerOnNetwork } from "../Server/ServerHelpers"; import { ParseCommand, ParseCommands } from "./Parser"; import { SpecialServerIps, SpecialServerNames } from "../Server/SpecialServerIps"; +import { createProgressBarText } from "../../utils/helpers/createProgressBarText"; import { calculateHackingChance, calculateHackingExpGain, @@ -72,16 +72,12 @@ import { wget } from "./commands/wget"; export class Terminal implements ITerminal { hasChanges = false; // Flags to determine whether the player is currently running a hack or an analyze - hackFlag = false; - backdoorFlag = false; - analyzeFlag = false; - actionStarted = false; - actionTime = 0; + action: TTimer | null = null; commandHistory: string[] = []; commandHistoryIndex = 0; - outputHistory: IOutput[] = [{ text: `Bitburner v${CONSTANTS.Version}`, color: "primary" }]; + outputHistory: (Output | Link)[] = [{ text: `Bitburner v${CONSTANTS.Version}`, color: "primary" }]; // True if a Coding Contract prompt is opened contractOpen = false; @@ -90,6 +86,13 @@ export class Terminal implements ITerminal { // Excludes the trailing forward slash currDir = "/"; + process(player: IPlayer, cycles: number): void { + if (this.action === null) return; + this.action.timeLeft -= (CONSTANTS._idleSpeed * cycles) / 1000; + this.hasChanges = true; + if (this.action.timeLeft < 0) this.finishAction(player, false); + } + pollChanges(): boolean { if (this.hasChanges) { this.hasChanges = false; @@ -99,105 +102,85 @@ export class Terminal implements ITerminal { } print(s: string, config?: any): void { - this.outputHistory.push({ text: s, color: "primary" }); + this.outputHistory.push(new Output(s, "primary")); this.hasChanges = true; } error(s: string): void { - this.outputHistory.push({ text: s, color: "error" }); + this.outputHistory.push(new Output(s, "error")); this.hasChanges = true; } startHack(player: IPlayer): void { - this.hackFlag = true; - // Hacking through Terminal should be faster than hacking through a script - this.actionTime = calculateHackingTime(player.getCurrentServer(), player) / 4; - this.startAction(); + this.startAction(calculateHackingTime(player.getCurrentServer(), player) / 4, "h"); } startBackdoor(player: IPlayer): void { - this.backdoorFlag = true; - // Backdoor should take the same amount of time as hack - this.actionTime = calculateHackingTime(player.getCurrentServer(), player) / 4; - this.startAction(); + this.startAction(calculateHackingTime(player.getCurrentServer(), player) / 4, "b"); } startAnalyze(): void { - this.analyzeFlag = true; - this.actionTime = 1; this.print("Analyzing system..."); - this.startAction(); + this.startAction(1, "a"); } - startAction(): void { - this.actionStarted = true; - - hackProgressPost("Time left:"); - hackProgressBarPost("["); - - // Disable terminal - const elem = document.getElementById("terminal-input-td"); - if (!elem) throw new Error("terminal-input-td should not be null"); - elem.innerHTML = ''; - $("input[class=terminal-input]").prop("disabled", true); + startAction(n: number, action: "h" | "b" | "a"): void { + this.action = new TTimer(n, action); } // Complete the hack/analyze command finishHack(player: IPlayer, cancelled = false): void { - if (!cancelled) { - const server = player.getCurrentServer(); + if (cancelled) return; + const server = player.getCurrentServer(); - // Calculate whether hack was successful - const hackChance = calculateHackingChance(server, player); - const rand = Math.random(); - const expGainedOnSuccess = calculateHackingExpGain(server, player); - const expGainedOnFailure = expGainedOnSuccess / 4; - if (rand < hackChance) { - // Success! - if ( - SpecialServerIps[SpecialServerNames.WorldDaemon] && - SpecialServerIps[SpecialServerNames.WorldDaemon] == server.ip - ) { - if (player.bitNodeN == null) { - player.bitNodeN = 1; - } - hackWorldDaemon(player.bitNodeN); - this.hackFlag = false; - return; + // Calculate whether hack was successful + const hackChance = calculateHackingChance(server, player); + const rand = Math.random(); + const expGainedOnSuccess = calculateHackingExpGain(server, player); + const expGainedOnFailure = expGainedOnSuccess / 4; + if (rand < hackChance) { + // Success! + if ( + SpecialServerIps[SpecialServerNames.WorldDaemon] && + SpecialServerIps[SpecialServerNames.WorldDaemon] == server.ip + ) { + if (player.bitNodeN == null) { + player.bitNodeN = 1; } - server.backdoorInstalled = true; - let moneyGained = calculatePercentMoneyHacked(server, player); - moneyGained = Math.floor(server.moneyAvailable * moneyGained); - - if (moneyGained <= 0) { - moneyGained = 0; - } // Safety check - - server.moneyAvailable -= moneyGained; - player.gainMoney(moneyGained); - player.recordMoneySource(moneyGained, "hacking"); - player.gainHackingExp(expGainedOnSuccess); - player.gainIntelligenceExp(expGainedOnSuccess / CONSTANTS.IntelligenceTerminalHackBaseExpGain); - - server.fortify(CONSTANTS.ServerFortifyAmount); - - this.print( - `Hack successful! Gained ${numeralWrapper.formatMoney(moneyGained)} and ${numeralWrapper.formatExp( - expGainedOnSuccess, - )} hacking exp`, - ); - } else { - // Failure - // player only gains 25% exp for failure? TODO Can change this later to balance - player.gainHackingExp(expGainedOnFailure); - this.print( - `Failed to hack ${server.hostname}. Gained ${numeralWrapper.formatExp(expGainedOnFailure)} hacking exp`, - ); + hackWorldDaemon(player.bitNodeN); + return; } + server.backdoorInstalled = true; + let moneyGained = calculatePercentMoneyHacked(server, player); + moneyGained = Math.floor(server.moneyAvailable * moneyGained); + + if (moneyGained <= 0) { + moneyGained = 0; + } // Safety check + + server.moneyAvailable -= moneyGained; + player.gainMoney(moneyGained); + player.recordMoneySource(moneyGained, "hacking"); + player.gainHackingExp(expGainedOnSuccess); + player.gainIntelligenceExp(expGainedOnSuccess / CONSTANTS.IntelligenceTerminalHackBaseExpGain); + + server.fortify(CONSTANTS.ServerFortifyAmount); + + this.print( + `Hack successful! Gained ${numeralWrapper.formatMoney(moneyGained)} and ${numeralWrapper.formatExp( + expGainedOnSuccess, + )} hacking exp`, + ); + } else { + // Failure + // player only gains 25% exp for failure? TODO Can change this later to balance + player.gainHackingExp(expGainedOnFailure); + this.print( + `Failed to hack ${server.hostname}. Gained ${numeralWrapper.formatExp(expGainedOnFailure)} hacking exp`, + ); } - this.hackFlag = false; } finishBackdoor(player: IPlayer, cancelled = false): void { @@ -211,13 +194,11 @@ export class Terminal implements ITerminal { player.bitNodeN = 1; } hackWorldDaemon(player.bitNodeN); - this.backdoorFlag = false; return; } server.backdoorInstalled = true; this.print("Backdoor successful!"); } - this.backdoorFlag = false; } finishAnalyze(player: IPlayer, cancelled = false): void { @@ -248,22 +229,25 @@ export class Terminal implements ITerminal { this.print("HTTP port: " + (currServ.httpPortOpen ? "Open" : "Closed")); this.print("SQL port: " + (currServ.sqlPortOpen ? "Open" : "Closed")); } - this.analyzeFlag = false; } finishAction(player: IPlayer, cancelled = false): void { - if (this.hackFlag) { + if (this.action === null) { + if (!cancelled) throw new Error("Finish action called when there was no action"); + return; + } + this.print(this.getProgressText()); + if (this.action.action === "h") { this.finishHack(player, cancelled); - } else if (this.backdoorFlag) { + } else if (this.action.action === "b") { this.finishBackdoor(player, cancelled); - } else if (this.analyzeFlag) { + } else if (this.action.action === "a") { this.finishAnalyze(player, cancelled); } - - // Rename the progress bar so that the next hacks dont trigger it. Re-enable terminal - $("#hack-progress-bar").attr("id", "old-hack-progress-bar"); - $("#hack-progress").attr("id", "old-hack-progress"); - $("input[class=terminal-input]").prop("disabled", false); + if (cancelled) { + this.print("Cancelled"); + } + this.action = null; } getFile(player: IPlayer, filename: string): Script | TextFile | string | null { @@ -427,7 +411,7 @@ export class Terminal implements ITerminal { } // Don't print current server const titleDashes = Array((d - 1) * 4 + 1).join("-"); if (player.hasProgram(Programs.AutoLink.name)) { - this.print(s.hostname); + this.outputHistory.push(new Link(s.hostname)); } else { this.print(s.hostname); } @@ -452,7 +436,7 @@ export class Terminal implements ITerminal { (() => { const hostname = links[i].innerHTML.toString(); links[i].addEventListener("onclick", () => { - if (this.analyzeFlag || this.hackFlag || this.backdoorFlag) { + if (this.action !== null) { return; } this.connectToServer(player, hostname); @@ -498,12 +482,17 @@ export class Terminal implements ITerminal { } clear(): void { - this.outputHistory = [{ text: `Bitburner v${CONSTANTS.Version}`, color: "primary" }]; + this.outputHistory = [new Output(`Bitburner v${CONSTANTS.Version}`, "primary")]; this.hasChanges = true; } + prestige(): void { + this.action = null; + this.clear(); + } + executeCommand(engine: IEngine, player: IPlayer, command: string): void { - if (this.hackFlag || this.backdoorFlag || this.analyzeFlag) { + if (this.action !== null) { this.error(`Cannot execute command (${command}) while an action is in progress`); return; } @@ -718,4 +707,12 @@ export class Terminal implements ITerminal { f(this, engine, player, s, commandArray.slice(1)); } + + getProgressText(): string { + if (this.action === null) throw new Error("trying to get the progress text when there's no action"); + return createProgressBarText({ + progress: (this.action.time - this.action.timeLeft) / this.action.time, + totalTicks: 50, + }); + } } diff --git a/src/Terminal/ui/TerminalRoot.tsx b/src/Terminal/ui/TerminalRoot.tsx index 55db94755..628683635 100644 --- a/src/Terminal/ui/TerminalRoot.tsx +++ b/src/Terminal/ui/TerminalRoot.tsx @@ -2,16 +2,30 @@ import React, { useState, useEffect, useRef } from "react"; import Typography from "@material-ui/core/Typography"; import List from "@material-ui/core/List"; import ListItem from "@material-ui/core/ListItem"; +import { Link as MuiLink } from "@material-ui/core"; import { makeStyles, createStyles, Theme } from "@material-ui/core/styles"; import TextField from "@material-ui/core/TextField"; import Box from "@material-ui/core/Box"; import { KEY } from "../../../utils/helpers/keyCodes"; -import { ITerminal } from "../ITerminal"; +import { ITerminal, Output, Link, TTimer } from "../ITerminal"; import { IEngine } from "../../IEngine"; import { IPlayer } from "../../PersonObjects/IPlayer"; import { determineAllPossibilitiesForTabCompletion } from "../determineAllPossibilitiesForTabCompletion"; import { tabCompletion } from "../tabCompletion"; import { FconfSettings } from "../../Fconf/FconfSettings"; +import { createProgressBarText } from "../../../utils/helpers/createProgressBarText"; + +interface IActionTimerProps { + terminal: ITerminal; +} + +function ActionTimer({ terminal }: IActionTimerProps): React.ReactElement { + return ( + + {terminal.getProgressText()} + + ); +} const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -150,6 +164,11 @@ export function TerminalRoot({ terminal, engine, player }: IProps): React.ReactE if (terminal.contractOpen) return; const ref = terminalInput.current; if (ref) ref.focus(); + + // Cancel action + if (event.keyCode === KEY.C && event.ctrlKey) { + terminal.finishAction(player, true); + } } document.addEventListener("keydown", keyDown); return () => document.removeEventListener("keydown", keyDown); @@ -328,15 +347,31 @@ export function TerminalRoot({ terminal, engine, player }: IProps): React.ReactE } return ( - + - {terminal.outputHistory.map((output, i) => ( - - - {output.text} - - - ))} + {terminal.outputHistory.map((item, i) => { + if (item instanceof Output) + return ( + + + {item.text} + + + ); + if (item instanceof Link) + return ( + + terminal.connectToServer(player, item.hostname)} + > + > {item.hostname} + + + ); + })} {possibilities.length > 0 && ( <> @@ -348,17 +383,23 @@ export function TerminalRoot({ terminal, engine, player }: IProps): React.ReactE )} + {terminal.action !== null && } - + [{player.getCurrentServer().hostname} ~{terminal.cwd()}]>  diff --git a/src/engine.jsx b/src/engine.jsx index a2a9f72a7..8e2bebad4 100644 --- a/src/engine.jsx +++ b/src/engine.jsx @@ -444,16 +444,7 @@ const Engine = { Player.playtimeSinceLastAug += time; Player.playtimeSinceLastBitnode += time; - // Start Manual hack - if (Terminal.actionStarted === true) { - Engine._totalActionTime = Terminal.actionTime; - Engine._actionTimeLeft = Terminal.actionTime; - Engine._actionInProgress = true; - Engine._actionProgressBarCount = 1; - Engine._actionProgressStr = "[ ]"; - Engine._actionTimeStr = "Time left: "; - Terminal.actionStarted = false; - } + Terminal.process(Player, numCycles); // Working if (Player.isWorking) { @@ -519,11 +510,6 @@ const Engine = { Engine.decrementAllCounters(numCycles); Engine.checkCounters(); - // Manual hacks - if (Engine._actionInProgress == true) { - Engine.updateHackProgress(numCycles); - } - // Update the running time of all active scripts updateOnlineScriptTimes(numCycles); @@ -623,42 +609,6 @@ const Engine = { } }, - // Calculates the hack progress for a manual (non-scripted) hack and updates the progress bar/time accordingly - // TODO Refactor this into Terminal module - _totalActionTime: 0, - _actionTimeLeft: 0, - _actionTimeStr: "Time left: ", - _actionProgressStr: "[ ]", - _actionProgressBarCount: 1, - _actionInProgress: false, - updateHackProgress: function (numCycles = 1) { - var timeElapsedMilli = numCycles * Engine._idleSpeed; - Engine._actionTimeLeft -= timeElapsedMilli / 1000; // Substract idle speed (ms) - Engine._actionTimeLeft = Math.max(Engine._actionTimeLeft, 0); - - // Calculate percent filled - var percent = Math.round((1 - Engine._actionTimeLeft / Engine._totalActionTime) * 100); - - // Update progress bar - while (Engine._actionProgressBarCount * 2 <= percent) { - Engine._actionProgressStr = replaceAt(Engine._actionProgressStr, Engine._actionProgressBarCount, "|"); - Engine._actionProgressBarCount += 1; - } - - // Update hack time remaining - Engine._actionTimeStr = "Time left: " + Math.max(0, Math.round(Engine._actionTimeLeft)).toString() + "s"; - document.getElementById("hack-progress").innerHTML = Engine._actionTimeStr; - - // Dynamically update progress bar - document.getElementById("hack-progress-bar").innerHTML = Engine._actionProgressStr.replace(/ /g, " "); - - // Once percent is 100, the hack is completed - if (percent >= 100) { - Engine._actionInProgress = false; - Terminal.finishAction(); - } - }, - /** * Used in game when clicking on a main menu header (NOT used for initialization) * @param open {boolean} Whether header is being opened or closed