diff --git a/doc/source/netscript/netscriptsingularityfunctions.rst b/doc/source/netscript/netscriptsingularityfunctions.rst index 44b311269..8069cc944 100644 --- a/doc/source/netscript/netscriptsingularityfunctions.rst +++ b/doc/source/netscript/netscriptsingularityfunctions.rst @@ -24,6 +24,8 @@ level 3, then you will be able to access all of the Singularity Functions. travelToCity() purchaseTor() purchaseProgram() + connect() + manualHack() getStats() getCharacterInformation() isBusy() diff --git a/doc/source/netscript/singularityfunctions/connect.rst b/doc/source/netscript/singularityfunctions/connect.rst new file mode 100644 index 000000000..974996363 --- /dev/null +++ b/doc/source/netscript/singularityfunctions/connect.rst @@ -0,0 +1,20 @@ +connect() Netscript Function +============================ + +.. js:function:: connect(hostname) + + :RAM cost: 2 GB + :param string hostname: hostname of the server to connect. + :returns: ``true`` if the connection was a success. + + If you are not in BitNode-4, then you must have Level 1 of Source-File 4 in order to use this function. + + This function will connect you to the specified server if it's directly connected to the current server. + You can also pass in 'home' to return to your home server from anywhere. + + Examples: + + .. code-block:: javascript + + connect("joesguns"); + connect("CSEC"); diff --git a/doc/source/netscript/singularityfunctions/executeCommand.rst b/doc/source/netscript/singularityfunctions/executeCommand.rst deleted file mode 100644 index 8b083009a..000000000 --- a/doc/source/netscript/singularityfunctions/executeCommand.rst +++ /dev/null @@ -1,21 +0,0 @@ -executeCommand() Netscript Function -======================================== - -.. js:function:: executeCommand(command) - - :RAM cost: 4 GB - :param string commands: The full string of the command. - - If you are not in BitNode-4, then you must have Level 1 of Source-File 4 in order to use this function. - - This function writes the command to the terminal and executes it. This - can be used to perform manual hacks. Only one command can be sent at a time. - - Examples: - - .. code-block:: javascript - - await ns.executeCommand('connect CSEC'); - await ns.executeCommand('hack'); - await ns.executeCommand('home'); - // a manual hack will be performed and CSEC will invite you. \ No newline at end of file diff --git a/doc/source/netscript/singularityfunctions/manualHack.rst b/doc/source/netscript/singularityfunctions/manualHack.rst new file mode 100644 index 000000000..5283ed6e4 --- /dev/null +++ b/doc/source/netscript/singularityfunctions/manualHack.rst @@ -0,0 +1,24 @@ +manualHack() Netscript Function +=============================== + +.. js:function:: manualHack() + + :RAM cost: 2 GB + :returns: The amount of money stolen if the hack is successful, and zero otherwise + + If you are not in BitNode-4, then you must have Level 1 of Source-File 4 in order to use this function. + + This function will perform a manual hack on the server you are currently connected to. + This is typically required to join factions. + + Examples: + + .. code-block:: javascript + + connect("CSEC"); + manualHack(); + + .. warning:: + For NS2 users: + + This function is async. \ No newline at end of file diff --git a/src/Constants.ts b/src/Constants.ts index 76ce092d5..45d20acce 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -231,6 +231,10 @@ export let CONSTANTS: IMap = { v0.51.0 - 2021-XX-XX formulas (hydroflame) ------- + Netscript + * 'connect': a new singularity function that connects you to a server. (like the terminal command) + * 'manualHack': a new singularity function that performs a manual hack on the players current server. + Misc. * New shortcut, Alt + b, brings you to bladeburner * New shortcut, Alt + g, brings you to gang diff --git a/src/Netscript/RamCostGenerator.ts b/src/Netscript/RamCostGenerator.ts index da1d79c64..6afb78180 100644 --- a/src/Netscript/RamCostGenerator.ts +++ b/src/Netscript/RamCostGenerator.ts @@ -176,7 +176,8 @@ export const RamCosts: IMap = { travelToCity: () => RamCostConstants.ScriptSingularityFn1RamCost, purchaseTor: () => RamCostConstants.ScriptSingularityFn1RamCost, purchaseProgram: () => RamCostConstants.ScriptSingularityFn1RamCost, - executeCommand: () => RamCostConstants.ScriptSingularityFn1RamCost * 2, + connect: () => RamCostConstants.ScriptSingularityFn1RamCost, + manualHack: () => RamCostConstants.ScriptSingularityFn1RamCost, getStats: () => RamCostConstants.ScriptSingularityFn1RamCost / 4, getCharacterInformation: () => RamCostConstants.ScriptSingularityFn1RamCost / 4, isBusy: () => RamCostConstants.ScriptSingularityFn1RamCost / 4, diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 3e413e1ef..9536bf89a 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -555,6 +555,80 @@ function NetscriptFunctions(workerScript) { } } + const hack = function(ip, manual, { threads: requestedThreads, stock } = {}) { + if (ip === undefined) { + throw makeRuntimeErrorMsg("hack", "Takes 1 argument."); + } + const threads = resolveNetscriptRequestedThreads(workerScript, "hack", requestedThreads); + const server = getServer(ip); + if (server == null) { + throw makeRuntimeErrorMsg("hack", `Invalid IP/hostname: ${ip}.`); + } + + // Calculate the hacking time + var hackingTime = calculateHackingTime(server); // This is in seconds + + // No root access or skill level too low + const canHack = netscriptCanHack(server, Player); + if (!canHack.res) { + throw makeRuntimeErrorMsg('hack', canHack.msg); + } + + workerScript.log("hack", `Executing ${ip} in ${hackingTime.toFixed(3)} seconds (t=${threads})`); + + return netscriptDelay(hackingTime * 1000, workerScript).then(function() { + if (workerScript.env.stopFlag) {return Promise.reject(workerScript);} + var hackChance = calculateHackingChance(server); + var rand = Math.random(); + var expGainedOnSuccess = calculateHackingExpGain(server) * threads; + var expGainedOnFailure = (expGainedOnSuccess / 4); + if (rand < hackChance) { // Success! + const percentHacked = calculatePercentMoneyHacked(server); + let maxThreadNeeded = Math.ceil(1/percentHacked*(server.moneyAvailable/server.moneyMax)); + if (isNaN(maxThreadNeeded)) { + // Server has a 'max money' of 0 (probably). We'll set this to an arbitrarily large value + maxThreadNeeded = 1e6; + } + + let moneyDrained = Math.floor(server.moneyAvailable * percentHacked) * threads; + + // Over-the-top safety checks + if (moneyDrained <= 0) { + moneyDrained = 0; + expGainedOnSuccess = expGainedOnFailure; + } + if (moneyDrained > server.moneyAvailable) {moneyDrained = server.moneyAvailable;} + server.moneyAvailable -= moneyDrained; + if (server.moneyAvailable < 0) {server.moneyAvailable = 0;} + + const moneyGained = moneyDrained * BitNodeMultipliers.ScriptHackMoneyGain; + + Player.gainMoney(moneyGained); + workerScript.scriptRef.onlineMoneyMade += moneyGained; + Player.scriptProdSinceLastAug += moneyGained; + Player.recordMoneySource(moneyGained, "hacking"); + workerScript.scriptRef.recordHack(server.ip, moneyGained, threads); + Player.gainHackingExp(expGainedOnSuccess); + workerScript.scriptRef.onlineExpGained += expGainedOnSuccess; + workerScript.log("hack", `Successfully hacked '${server.hostname}' for ${numeralWrapper.format(moneyGained, '$0.000a')} and ${numeralWrapper.format(expGainedOnSuccess, '0.000a')} exp (t=${threads})`); + server.fortify(CONSTANTS.ServerFortifyAmount * Math.min(threads, maxThreadNeeded)); + if (stock) { + influenceStockThroughServerHack(server, moneyGained); + } + if(manual) { + server.manuallyHacked = true; + } + return Promise.resolve(moneyGained); + } else { + // Player only gains 25% exp for failure? + Player.gainHackingExp(expGainedOnFailure); + workerScript.scriptRef.onlineExpGained += expGainedOnFailure; + workerScript.log("hack", `Failed to hack '${server.hostname}'. Gained ${numeralWrapper.format(expGainedOnFailure, '0.000a')} exp (t=${threads})`); + return Promise.resolve(0); + } + }); + } + return { hacknet : { numNodes : function() { @@ -671,74 +745,7 @@ function NetscriptFunctions(workerScript) { }, hack : function(ip, { threads: requestedThreads, stock } = {}){ updateDynamicRam("hack", getRamCost("hack")); - if (ip === undefined) { - throw makeRuntimeErrorMsg("hack", "Takes 1 argument."); - } - const threads = resolveNetscriptRequestedThreads(workerScript, "hack", requestedThreads); - const server = getServer(ip); - if (server == null) { - throw makeRuntimeErrorMsg("hack", `Invalid IP/hostname: ${ip}.`); - } - - // Calculate the hacking time - var hackingTime = calculateHackingTime(server); // This is in seconds - - // No root access or skill level too low - const canHack = netscriptCanHack(server, Player); - if (!canHack.res) { - throw makeRuntimeErrorMsg('hack', canHack.msg); - } - - workerScript.log("hack", `Executing ${ip} in ${hackingTime.toFixed(3)} seconds (t=${threads})`); - - return netscriptDelay(hackingTime * 1000, workerScript).then(function() { - if (workerScript.env.stopFlag) {return Promise.reject(workerScript);} - var hackChance = calculateHackingChance(server); - var rand = Math.random(); - var expGainedOnSuccess = calculateHackingExpGain(server) * threads; - var expGainedOnFailure = (expGainedOnSuccess / 4); - if (rand < hackChance) { // Success! - const percentHacked = calculatePercentMoneyHacked(server); - let maxThreadNeeded = Math.ceil(1/percentHacked*(server.moneyAvailable/server.moneyMax)); - if (isNaN(maxThreadNeeded)) { - // Server has a 'max money' of 0 (probably). We'll set this to an arbitrarily large value - maxThreadNeeded = 1e6; - } - - let moneyDrained = Math.floor(server.moneyAvailable * percentHacked) * threads; - - // Over-the-top safety checks - if (moneyDrained <= 0) { - moneyDrained = 0; - expGainedOnSuccess = expGainedOnFailure; - } - if (moneyDrained > server.moneyAvailable) {moneyDrained = server.moneyAvailable;} - server.moneyAvailable -= moneyDrained; - if (server.moneyAvailable < 0) {server.moneyAvailable = 0;} - - const moneyGained = moneyDrained * BitNodeMultipliers.ScriptHackMoneyGain; - - Player.gainMoney(moneyGained); - workerScript.scriptRef.onlineMoneyMade += moneyGained; - Player.scriptProdSinceLastAug += moneyGained; - Player.recordMoneySource(moneyGained, "hacking"); - workerScript.scriptRef.recordHack(server.ip, moneyGained, threads); - Player.gainHackingExp(expGainedOnSuccess); - workerScript.scriptRef.onlineExpGained += expGainedOnSuccess; - workerScript.log("hack", `Successfully hacked '${server.hostname}' for ${numeralWrapper.format(moneyGained, '$0.000a')} and ${numeralWrapper.format(expGainedOnSuccess, '0.000a')} exp (t=${threads})`); - server.fortify(CONSTANTS.ServerFortifyAmount * Math.min(threads, maxThreadNeeded)); - if (stock) { - influenceStockThroughServerHack(server, moneyGained); - } - return Promise.resolve(moneyGained); - } else { - // Player only gains 25% exp for failure? - Player.gainHackingExp(expGainedOnFailure); - workerScript.scriptRef.onlineExpGained += expGainedOnFailure; - workerScript.log("hack", `Failed to hack '${server.hostname}'. Gained ${numeralWrapper.format(expGainedOnFailure, '0.000a')} exp (t=${threads})`); - return Promise.resolve(0); - } - }); + return hack(ip, false, {threads: requestedThreads, stock: stock}); }, hackAnalyzeThreads : function(ip, hackAmount) { updateDynamicRam("hackAnalyzeThreads", getRamCost("hackAnalyzeThreads")); @@ -2679,16 +2686,46 @@ function NetscriptFunctions(workerScript) { workerScript.log("purchaseProgram", `You have purchased the '${item.program}' program. The new program can be found on your home computer.`); return true; }, - executeCommand: function(command) { - updateDynamicRam("executeCommand", getRamCost("executeCommand")); - checkSingularityAccess("executeCommand", 1); - Terminal.executeCommand(command); - return new Promise(function (resolve, reject) { - (function wait(){ - if (!Terminal.hackFlag && !Terminal.analyzeFlag) return resolve(); - setTimeout(wait, 30); - })(); - }); + connect: function(hostname) { + if (!hostname) { + throw makeRuntimeErrorMsg("connect", `Invalid hostname: '${hostname}'`); + } + + let target = getServer(hostname); + if (target == null) { + throw makeRuntimeErrorMsg("connect", `Invalid hostname: '${hostname}'`); + return; + } + + if(hostname === 'home') { + Player.getCurrentServer().isConnectedTo = false; + Player.currentServer = Player.getHomeComputer().ip; + Player.getCurrentServer().isConnectedTo = true; + Terminal.currDir = "/"; + Terminal.resetTerminalInput(true); + return true; + } + + const server = Player.getCurrentServer(); + for (let i = 0; i < server.serversOnNetwork.length; i++) { + const other = getServerOnNetwork(server, i); + if (other.ip == hostname || other.hostname == hostname) { + Player.getCurrentServer().isConnectedTo = false; + Player.currentServer = target.ip; + Player.getCurrentServer().isConnectedTo = true; + Terminal.currDir = "/"; + Terminal.resetTerminalInput(true); + return true; + } + } + + return false; + }, + manualHack: function() { + updateDynamicRam("manualHack", getRamCost("manualHack")); + checkSingularityAccess("manualHack", 1); + const server = Player.getCurrentServer(); + return hack(server.hostname, true); }, getStats: function() { updateDynamicRam("getStats", getRamCost("getStats")); diff --git a/src/NetscriptWorker.js b/src/NetscriptWorker.js index f030ac93d..7b9803ab3 100644 --- a/src/NetscriptWorker.js +++ b/src/NetscriptWorker.js @@ -163,7 +163,7 @@ function startNetscript1Script(workerScript) { if (typeof entry === "function") { //Async functions need to be wrapped. See JS-Interpreter documentation if (name === "hack" || name === "grow" || name === "weaken" || name === "sleep" || - name === "prompt") { + name === "prompt" || name === "manualHack") { let tempWrapper = function() { let fnArgs = []; diff --git a/src/Terminal.js b/src/Terminal.js index 2b3ed51a6..be230aa10 100644 --- a/src/Terminal.js +++ b/src/Terminal.js @@ -113,6 +113,10 @@ function isNumber(str) { return !isNaN(str) && !isNaN(parseFloat(str)); } +function getTerminalInput() { + return document.getElementById("terminal-input-text-box").value; +} + // Defines key commands in terminal $(document).keydown(function(event) { // Terminal @@ -122,7 +126,7 @@ $(document).keydown(function(event) { if (event.keyCode === KEY.ENTER) { event.preventDefault(); // Prevent newline from being entered in Script Editor - const command = terminalInput.value; + const command = getTerminalInput(); const dir = Terminal.currDir; post( "[" + @@ -344,22 +348,36 @@ let Terminal = { // Excludes the trailing forward slash currDir: "/", - resetTerminalInput: function() { + resetTerminalInput: function(keepInput=false) { + let input = ""; + if(keepInput) { + input = getTerminalInput(); + } const dir = Terminal.currDir; if (FconfSettings.WRAP_INPUT) { document.getElementById("terminal-input-td").innerHTML = `
[${Player.getCurrentServer().hostname} ~${dir}]$
` + - '