import {substituteAliases, printAliases, parseAliasDeclaration, removeAlias, GlobalAliases, Aliases} from "./Alias"; import {CodingContract, CodingContractResult, CodingContractRewardType} from "./CodingContracts"; import {CONSTANTS} from "./Constants"; import { Programs } from "./Programs/Programs"; import { executeDarkwebTerminalCommand, checkIfConnectedToDarkweb } from "./DarkWeb/DarkWeb"; import { DarkWebItems } from "./DarkWeb/DarkWebItems"; import {Engine} from "./engine"; import {FconfSettings, parseFconfSettings, createFconf} from "./Fconf"; import {calculateHackingChance, calculateHackingExpGain, calculatePercentMoneyHacked, calculateHackingTime, calculateGrowTime, calculateWeakenTime} from "./Hacking"; import {TerminalHelpText, HelpTexts} from "./HelpText"; import {iTutorialNextStep, iTutorialSteps, ITutorial} from "./InteractiveTutorial"; import {showLiterature} from "./Literature"; import {showMessage, Message} from "./Message"; import {killWorkerScript, addWorkerScript} from "./NetscriptWorker"; import {Player} from "./Player"; import {hackWorldDaemon} from "./RedPill"; import {findRunningScript, RunningScript, AllServersMap, isScriptFilename} from "./Script"; import {AllServers, GetServerByHostname, getServer, Server} from "./Server"; import {Settings} from "./Settings/Settings"; import {SpecialServerIps, SpecialServerNames} from "./SpecialServerIps"; import {getTextFile} from "./TextFile"; import {containsAllStrings, longestCommonStart} from "../utils/StringHelperFunctions"; import {Page, routing} from "./ui/navigationTracking"; import {numeralWrapper} from "./ui/numeralFormat"; import {KEY} from "../utils/helpers/keyCodes"; import {addOffset} from "../utils/helpers/addOffset"; import {isString} from "../utils/helpers/isString"; import {arrayToString} from "../utils/helpers/arrayToString"; import {getTimestamp} from "../utils/helpers/getTimestamp"; import {logBoxCreate} from "../utils/LogBox"; import {yesNoBoxCreate, yesNoBoxGetYesButton, yesNoBoxGetNoButton, yesNoBoxClose} from "../utils/YesNoBox"; import { post, postError, hackProgressBarPost, hackProgressPost } from "./ui/postToTerminal"; import autosize from 'autosize'; import * as JSZip from 'jszip'; import * as FileSaver from 'file-saver'; function postNetburnerText() { post("Bitburner v" + CONSTANTS.Version); } // Helper function that checks if an argument (which is a string) is a valid number function isNumber(str) { if (typeof str != "string") { return false; } // Only process strings return !isNaN(str) && !isNaN(parseFloat(str)); } //Defines key commands in terminal $(document).keydown(function(event) { //Terminal if (routing.isOn(Page.Terminal)) { var terminalInput = document.getElementById("terminal-input-text-box"); if (terminalInput != null && !event.ctrlKey && !event.shiftKey && !Terminal.contractOpen) {terminalInput.focus();} if (event.keyCode === KEY.ENTER) { event.preventDefault(); //Prevent newline from being entered in Script Editor var command = terminalInput.value; post( "[" + (FconfSettings.ENABLE_TIMESTAMPS ? getTimestamp() + " " : "") + Player.getCurrentServer().hostname + " ~]> " + command ); if (command.length > 0) { Terminal.resetTerminalInput(); //Clear input first Terminal.executeCommands(command); } } 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.resetTerminalInput(); //Clear Terminal } } if (event.keyCode === KEY.L && event.ctrlKey) { event.preventDefault(); Terminal.executeCommand("clear"); //Clear screen } //Ctrl p same as up arrow //Ctrl n same as down arrow if (event.keyCode === KEY.UPARROW || (FconfSettings.ENABLE_BASH_HOTKEYS && event.keyCode === KEY.P && event.ctrlKey)) { if (FconfSettings.ENABLE_BASH_HOTKEYS) {event.preventDefault();} //Cycle through past commands if (terminalInput == null) {return;} var i = Terminal.commandHistoryIndex; var len = Terminal.commandHistory.length; if (len == 0) {return;} if (i < 0 || i > len) { Terminal.commandHistoryIndex = len; } if (i != 0) { --Terminal.commandHistoryIndex; } var prevCommand = Terminal.commandHistory[Terminal.commandHistoryIndex]; terminalInput.value = prevCommand; setTimeout(function(){terminalInput.selectionStart = terminalInput.selectionEnd = 10000; }, 0); } if (event.keyCode === KEY.DOWNARROW || (FconfSettings.ENABLE_BASH_HOTKEYS && event.keyCode === KEY.M && event.ctrlKey)) { if (FconfSettings.ENABLE_BASH_HOTKEYS) {event.preventDefault();} //Cycle through past commands if (terminalInput == null) {return;} var i = Terminal.commandHistoryIndex; var len = Terminal.commandHistory.length; if (len == 0) {return;} if (i < 0 || i > len) { Terminal.commandHistoryIndex = len; } //Latest command, put nothing if (i == len || i == len-1) { Terminal.commandHistoryIndex = len; terminalInput.value = ""; } else { ++Terminal.commandHistoryIndex; var prevCommand = Terminal.commandHistory[Terminal.commandHistoryIndex]; terminalInput.value = prevCommand; } } if (event.keyCode === KEY.TAB) { event.preventDefault(); //Autocomplete if (terminalInput == null) {return;} var input = terminalInput.value; if (input == "") {return;} const semiColonIndex = input.lastIndexOf(";"); if(semiColonIndex !== -1) { input = input.slice(semiColonIndex+1); } input = input.trim(); input = input.replace(/\s\s+/g, ' '); var commandArray = input.split(" "); var index = commandArray.length - 2; if (index < -1) {index = 0;} var allPos = determineAllPossibilitiesForTabCompletion(input, index); if (allPos.length == 0) {return;} var arg = ""; var command = ""; if (commandArray.length == 0) {return;} if (commandArray.length == 1) {command = commandArray[0];} else if (commandArray.length == 2) { command = commandArray[0]; arg = commandArray[1]; } else if (commandArray.length == 3) { command = commandArray[0] + " " + commandArray[1]; arg = commandArray[2]; } else { arg = commandArray.pop(); command = commandArray.join(" "); } tabCompletion(command, arg, allPos); terminalInput.focus(); } //Extra Bash Emulation Hotkeys, must be enabled through .fconf if (FconfSettings.ENABLE_BASH_HOTKEYS) { if (event.keyCode === KEY.A && event.ctrlKey) { event.preventDefault(); Terminal.moveTextCursor("home"); } if (event.keyCode === KEY.E && event.ctrlKey) { event.preventDefault(); Terminal.moveTextCursor("end"); } if (event.keyCode === KEY.B && event.ctrlKey) { event.preventDefault(); Terminal.moveTextCursor("prevchar"); } if (event.keyCode === KEY.B && event.altKey) { event.preventDefault(); Terminal.moveTextCursor("prevword"); } if (event.keyCode === KEY.F && event.ctrlKey) { event.preventDefault(); Terminal.moveTextCursor("nextchar"); } if (event.keyCode === KEY.F && event.altKey) { event.preventDefault(); Terminal.moveTextCursor("nextword"); } if ((event.keyCode === KEY.H || event.keyCode === KEY.D) && event.ctrlKey) { Terminal.modifyInput("backspace"); event.preventDefault(); } //TODO AFTER THIS: //alt + d deletes word after cursor //^w deletes word before cursor //^k clears line after cursor //^u clears line before cursor } } }); //Keep terminal in focus let terminalCtrlPressed = false, shiftKeyPressed = false; $(document).ready(function() { if (routing.isOn(Page.Terminal)) { $('.terminal-input').focus(); } }); $(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; } } }) $(document).keyup(function(e) { if (routing.isOn(Page.Terminal)) { if (e.which == KEY.CTRL) { terminalCtrlPressed = false; } if (e.shiftKey) { shiftKeyPressed = false; } } }) //Implements a tab completion feature for terminal // command - Terminal command except for the last incomplete argument // arg - Incomplete argument string that the function will try to complete, or will display // a series of possible options for // allPossibilities - Array of strings containing all possibilities that the // string can complete to // index - index of argument that is being "tab completed". By default is 0, the first argument function tabCompletion(command, arg, allPossibilities, index=0) { if (!(allPossibilities.constructor === Array)) {return;} if (!containsAllStrings(allPossibilities)) {return;} //if (!command.startsWith("./")) { //command = command.toLowerCase(); //} //Remove all options in allPossibilities that do not match the current string //that we are attempting to autocomplete if (arg == "") { for (var i = allPossibilities.length-1; i >= 0; --i) { if (!allPossibilities[i].toLowerCase().startsWith(command.toLowerCase())) { allPossibilities.splice(i, 1); } } } else { for (var i = allPossibilities.length-1; i >= 0; --i) { if (!allPossibilities[i].toLowerCase().startsWith(arg.toLowerCase())) { allPossibilities.splice(i, 1); } } } var val = ""; if (allPossibilities.length == 0) { return; } else if (allPossibilities.length == 1) { if (arg == "") { //Autocomplete command val = allPossibilities[0] + " "; } else { val = command + " " + allPossibilities[0]; } const textBox = document.getElementById("terminal-input-text-box"); const oldValue = textBox.value; const semiColonIndex = oldValue.lastIndexOf(";"); if(semiColonIndex === -1) { // no ; replace the whole thing. textBox.value = val; } else { // replace just after the last semicolon textBox.value = textBox.value.slice(0, semiColonIndex+1)+" "+val; } document.getElementById("terminal-input-text-box").focus(); } else { var longestStartSubstr = longestCommonStart(allPossibilities); //If the longest common starting substring of remaining possibilities is the same //as whatevers already in terminal, just list all possible options. Otherwise, //change the input in the terminal to the longest common starting substr var allOptionsStr = ""; for (var i = 0; i < allPossibilities.length; ++i) { allOptionsStr += allPossibilities[i]; allOptionsStr += " "; } if (arg == "") { if (longestStartSubstr == command) { post("> " + command); post(allOptionsStr); } else { document.getElementById("terminal-input-text-box").value = longestStartSubstr; document.getElementById("terminal-input-text-box").focus(); } } else { if (longestStartSubstr == arg) { //List all possible options post("> " + command + " " + arg); post(allOptionsStr); } else { document.getElementById("terminal-input-text-box").value = command + " " + longestStartSubstr; document.getElementById("terminal-input-text-box").focus(); } } } } function determineAllPossibilitiesForTabCompletion(input, index=0) { var allPos = []; allPos = allPos.concat(Object.keys(GlobalAliases)); var currServ = Player.getCurrentServer(); input = input.toLowerCase(); //If the command starts with './' and the index == -1, then the user //has input ./partialexecutablename so autocomplete the script or program //Put './' in front of each script/executable if (input.startsWith("./") && index == -1) { //All programs and scripts for (var i = 0; i < currServ.scripts.length; ++i) { allPos.push("./" + currServ.scripts[i].filename); } //Programs are on home computer var homeComputer = Player.getHomeComputer(); for(var i = 0; i < homeComputer.programs.length; ++i) { allPos.push("./" + homeComputer.programs[i]); } return allPos; } //Autocomplete the command if (index == -1) { return ["alias", "analyze", "cat", "check", "clear", "cls", "connect", "download", "expr", "free", "hack", "help", "home", "hostname", "ifconfig", "kill", "killall", "ls", "lscpu", "mem", "nano", "ps", "rm", "run", "scan", "scan-analyze", "scp", "sudov", "tail", "theme", "top"].concat(Object.keys(Aliases)).concat(Object.keys(GlobalAliases)); } if (input.startsWith ("buy ")) { let options = []; for(const i in DarkWebItems) { const item = DarkWebItems[i] options.push(item.program); } return options.concat(Object.keys(GlobalAliases)); } if (input.startsWith("scp ") && index == 1) { for (var iphostname in AllServers) { if (AllServers.hasOwnProperty(iphostname)) { allPos.push(AllServers[iphostname].ip); allPos.push(AllServers[iphostname].hostname); } } } if (input.startsWith("scp ") && index == 0) { //All Scripts and lit files for (var i = 0; i < currServ.scripts.length; ++i) { allPos.push(currServ.scripts[i].filename); } for (var i = 0; i < currServ.messages.length; ++i) { if (!(currServ.messages[i] instanceof Message)) { allPos.push(currServ.messages[i]); } } for (var i = 0; i < currServ.textFiles.length; ++i) { allPos.push(currServ.textFiles[i].fn); } } if (input.startsWith("connect ") || input.startsWith("telnet ")) { //All network connections for (var i = 0; i < currServ.serversOnNetwork.length; ++i) { var serv = AllServers[currServ.serversOnNetwork[i]]; if (serv == null) {continue;} allPos.push(serv.ip); //IP allPos.push(serv.hostname); //Hostname } return allPos; } if (input.startsWith("kill ") || input.startsWith("tail ") || input.startsWith("mem ") || input.startsWith("check ")) { //All Scripts for (var i = 0; i < currServ.scripts.length; ++i) { allPos.push(currServ.scripts[i].filename); } return allPos; } if (input.startsWith("nano ")) { //Scripts and text files and .fconf for (var i = 0; i < currServ.scripts.length; ++i) { allPos.push(currServ.scripts[i].filename); } for (var i = 0; i < currServ.textFiles.length; ++i) { allPos.push(currServ.textFiles[i].fn); } allPos.push(".fconf"); return allPos; } if (input.startsWith("rm ")) { for (let i = 0; i < currServ.scripts.length; ++i) { allPos.push(currServ.scripts[i].filename); } for (let i = 0; i < currServ.programs.length; ++i) { allPos.push(currServ.programs[i]); } for (let i = 0; i < currServ.messages.length; ++i) { if (!(currServ.messages[i] instanceof Message) && isString(currServ.messages[i]) && currServ.messages[i].endsWith(".lit")) { allPos.push(currServ.messages[i]); } } for (let i = 0; i < currServ.textFiles.length; ++i) { allPos.push(currServ.textFiles[i].fn); } for (let i = 0; i < currServ.contracts.length; ++i) { allPos.push(currServ.contracts[i].fn); } return allPos; } if (input.startsWith("run ")) { //All programs, scripts, and contracts for (let i = 0; i < currServ.scripts.length; ++i) { allPos.push(currServ.scripts[i].filename); } //Programs are on home computer var homeComputer = Player.getHomeComputer(); for (let i = 0; i < homeComputer.programs.length; ++i) { allPos.push(homeComputer.programs[i]); } for (let i = 0; i < currServ.contracts.length; ++i) { allPos.push(currServ.contracts[i].fn); } return allPos; } if (input.startsWith("cat ")) { for (var i = 0; i < currServ.messages.length; ++i) { if (currServ.messages[i] instanceof Message) { allPos.push(currServ.messages[i].filename); } else { allPos.push(currServ.messages[i]); } } for (var i = 0; i < currServ.textFiles.length; ++i) { allPos.push(currServ.textFiles[i].fn); } return allPos; } if (input.startsWith("download ")) { for (var i = 0; i < currServ.textFiles.length; ++i) { allPos.push(currServ.textFiles[i].fn); } for (var i = 0; i < currServ.scripts.length; ++i) { allPos.push(currServ.scripts[i].filename); } } return allPos; } let Terminal = { //Flags to determine whether the player is currently running a hack or an analyze hackFlag: false, analyzeFlag: false, actionStarted: false, actionTime: 0, commandHistory: [], commandHistoryIndex: 0, contractOpen: false, //True if a Coding Contract prompt is opened resetTerminalInput: function() { if (FconfSettings.WRAP_INPUT) { document.getElementById("terminal-input-td").innerHTML = "
[" + Player.getCurrentServer().hostname + " ~]" + "$
" + '