diff --git a/package.json b/package.json index 7d974b2d5..c85190af6 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ "ajv-keywords": "^2.0.0", "async": "^2.6.1", "autosize": "^4.0.2", - "bluebird": "^3.5.1", "brace": "^0.11.1", "codemirror": "^5.43.0", "decimal.js": "7.2.3", diff --git a/src/ActiveScriptsUI.js b/src/ActiveScriptsUI.js index c21a2ddd2..dcf841088 100644 --- a/src/ActiveScriptsUI.js +++ b/src/ActiveScriptsUI.js @@ -166,7 +166,7 @@ function addActiveScriptsItem(workerscript) { margin: "4px", padding: "4px", clickListener: () => { - killWorkerScript(workerscript.scriptRef, workerscript.scriptRef.scriptRef.server); + killWorkerScript(workerscript.scriptRef, workerscript.scriptRef.server); dialogBoxCreate("Killing script, may take a few minutes to complete..."); return false; } diff --git a/src/BitNode/BitNode.js b/src/BitNode/BitNode.js index 41eda2ef8..bb0c74d1e 100644 --- a/src/BitNode/BitNode.js +++ b/src/BitNode/BitNode.js @@ -364,6 +364,11 @@ function initBitNodeMultipliers() { var inc = Math.pow(1.02, sf12Lvl); var dec = 1/inc; BitNodeMultipliers.HackingLevelMultiplier = dec; + BitNodeMultipliers.StrengthLevelMultiplier = dec; + BitNodeMultipliers.DefenseLevelMultiplier = dec; + BitNodeMultipliers.DexterityLevelMultiplier = dec; + BitNodeMultipliers.AgilityLevelMultiplier = dec; + BitNodeMultipliers.CharismaLevelMultiplier = dec; BitNodeMultipliers.ServerMaxMoney = dec; BitNodeMultipliers.ServerStartingMoney = dec; @@ -373,6 +378,10 @@ function initBitNodeMultipliers() { //Does not scale, otherwise security might start at 300+ BitNodeMultipliers.ServerStartingSecurity = 1.5; + BitNodeMultipliers.PurchasedServerCost = inc; + BitNodeMultipliers.PurchasedServerLimit = dec; + BitNodeMultipliers.PurchasedServerMaxRam = dec; + BitNodeMultipliers.ManualHackMoney = dec; BitNodeMultipliers.ScriptHackMoney = dec; BitNodeMultipliers.CompanyWorkMoney = dec; diff --git a/src/Bladeburner.js b/src/Bladeburner.js index 894d08f3f..9f6544389 100644 --- a/src/Bladeburner.js +++ b/src/Bladeburner.js @@ -3434,12 +3434,12 @@ Bladeburner.prototype.startActionNetscriptFn = function(type, name, workerScript try { this.startAction(actionId); if (workerScript.shouldLog("startAction")) { - workerScript.scriptRef.log("Starting bladeburner action with type " + type + " and name " + name); + workerScript.log("Starting bladeburner action with type " + type + " and name " + name); } return true; } catch(e) { this.resetAction(); - workerScript.scriptRef.log("ERROR: bladeburner.startAction() failed to start action of type " + type + " due to invalid name: " + name + + workerScript.log("ERROR: bladeburner.startAction() failed to start action of type " + type + " due to invalid name: " + name + "Note that this name is case-sensitive and whitespace-sensitive"); return false; } diff --git a/src/Constants.ts b/src/Constants.ts index 168d1a53a..3207785de 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -521,6 +521,7 @@ export let CONSTANTS: IMap = { * Corporation "Smart Factories" and "Smart Storage" upgrades have slightly lower price multipliers * Added 6 new Coding Contract problems * Updated documentation with list of all Coding Contract problems + * Implemented several optimizations for active scripts. The game should now use less memory and the savefile should be slightly smaller when there are many scripts running * Bug Fix: A Stock Forecast should no longer go above 1 (i.e. 100%) ` diff --git a/src/NetscriptBladeburner.js b/src/NetscriptBladeburner.js index ecffc8251..66298a6cc 100644 --- a/src/NetscriptBladeburner.js +++ b/src/NetscriptBladeburner.js @@ -13,10 +13,10 @@ function unknownBladeburnerExceptionMessage(functionName, err) { } function checkBladeburnerAccess(workerScript, functionName) { - const accessDenied = `${functionName}() failed because you do not` + - " currently have access to the Bladeburner API. This is either" + - " because you are not currently employed at the Bladeburner division" + - " or because you do not have Source-File 7"; + const accessDenied = `${functionName}() failed because you do not ` + + "currently have access to the Bladeburner API. To access the Bladeburner API" + + "you must be employed at the Bladeburner division, AND you must either be in " + + "BitNode-7 or have Source-File 7."; const hasAccess = Player.bladeburner instanceof Bladeburner && (Player.bitNodeN === 7 || Player.sourceFiles.some(a=>{return a.n === 7})); if(!hasAccess) { throw makeRuntimeRejectMsg(workerScript, accessDenied); diff --git a/src/NetscriptEvaluator.js b/src/NetscriptEvaluator.js index cd8b217c5..e86a72169 100644 --- a/src/NetscriptEvaluator.js +++ b/src/NetscriptEvaluator.js @@ -13,692 +13,7 @@ import {arrayToString} from "../utils/helpers/arrayToString import {isValidIPAddress} from "../utils/helpers/isValidIPAddress"; import {isString} from "../utils/helpers/isString"; -var Promise = require("bluebird"); - -Promise.config({ - warnings: false, - longStackTraces: false, - cancellation: true, - monitoring: false -}); -/* Evaluator - * Evaluates/Interprets the Abstract Syntax Tree generated by Acorns parser - * - * Returns a promise - */ -function evaluate(exp, workerScript) { - return Promise.delay(Settings.CodeInstructionRunTime).then(function() { - var env = workerScript.env; - if (env.stopFlag) {return Promise.reject(workerScript);} - if (exp == null) { - return Promise.reject(makeRuntimeRejectMsg(workerScript, "Error: NULL expression", exp)); - } - if (env.stopFlag) {return Promise.reject(workerScript);} - switch (exp.type) { - case "BlockStatement": - case "Program": - var evaluateProgPromise = evaluateProg(exp, workerScript, 0); //TODO: make every block/program use individual enviroment - return evaluateProgPromise.then(function(w) { - return Promise.resolve(workerScript); - }).catch(function(e) { - if (e.constructor === Array && e.length === 2 && e[0] === "RETURNSTATEMENT") { - return Promise.reject(e); - } else if (isString(e)) { - workerScript.errorMessage = e; - return Promise.reject(workerScript); - } else if (e instanceof WorkerScript) { - return Promise.reject(e); - } else { - return Promise.reject(workerScript); - } - }); - break; - case "Literal": - return Promise.resolve(exp.value); - break; - case "Identifier": - //Javascript constructor() method can be used as an exploit to run arbitrary code - if (exp.name == "constructor") { - return Promise.reject(makeRuntimeRejectMsg(workerScript, "Illegal usage of constructor() method. If you have your own function named 'constructor', you must re-name it.", exp)); - } - - if (!(exp.name in env.vars)){ - return Promise.reject(makeRuntimeRejectMsg(workerScript, "variable " + exp.name + " not defined", exp)); - } - return Promise.resolve(env.get(exp.name)) - break; - case "ExpressionStatement": - return evaluate(exp.expression, workerScript); - break; - case "ArrayExpression": - var argPromises = exp.elements.map(function(arg) { - return evaluate(arg, workerScript); - }); - return Promise.all(argPromises).then(function(array) { - return Promise.resolve(array) - }); - break; - case "CallExpression": - return evaluate(exp.callee, workerScript).then(function(func) { - return Promise.map(exp.arguments, function(arg) { - return evaluate(arg, workerScript); - }).then(function(args) { - if (func instanceof Node) { //Player-defined function - //Create new Environment for the function - //Should be automatically garbage collected... - var funcEnv = env.extend(); - - //Define function arguments in this new environment - for (var i = 0; i < func.params.length; ++i) { - var arg; - if (i >= args.length) { - arg = null; - } else { - arg = args[i]; - } - funcEnv.def(func.params[i].name, arg); - } - - //Create a new WorkerScript for this function evaluation - var funcWorkerScript = new WorkerScript(workerScript.scriptRef); - funcWorkerScript.serverIp = workerScript.serverIp; - funcWorkerScript.env = funcEnv; - workerScript.fnWorker = funcWorkerScript; - - return evaluate(func.body, funcWorkerScript).then(function(res) { - //If the function finished successfuly, that means there - //was no return statement since a return statement rejects. So resolve to null - workerScript.fnWorker = null; - return Promise.resolve(null); - }).catch(function(e) { - if (e.constructor === Array && e.length === 2 && e[0] === "RETURNSTATEMENT") { - //Return statement from function - return Promise.resolve(e[1]); - workerScript.fnWorker = null; - } else if (isString(e)) { - return Promise.reject(makeRuntimeRejectMsg(workerScript, e)); - } else if (e instanceof WorkerScript) { - //Parse out the err message from the WorkerScript and re-reject - var errorMsg = e.errorMessage; - var errorTextArray = errorMsg.split("|"); - if (errorTextArray.length === 4) { - errorMsg = errorTextArray[3]; - return Promise.reject(makeRuntimeRejectMsg(workerScript, errorMsg)); - } else { - if (env.stopFlag) { - return Promise.reject(workerScript); - } else { - return Promise.reject(makeRuntimeRejectMsg(workerScript, "Error in one of your functions. Could not identify which function")); - } - } - } else if (e instanceof Error) { - return Promise.reject(makeRuntimeRejectMsg(workerScript, e.toString())); - } - }); - } else if (exp.callee.type === "MemberExpression"){ - return evaluate(exp.callee.object, workerScript).then(function(object) { - try { - if (func === "NETSCRIPTFOREACH") { - return evaluateForeach(object, args, workerScript).then(function(res) { - return Promise.resolve(res); - }).catch(function(e) { - return Promise.reject(e); - }); - } - var res = func.apply(object,args); - return Promise.resolve(res); - } catch (e) { - return Promise.reject(makeRuntimeRejectMsg(workerScript, e, exp)); - } - }); - } else { - try { - var out = func.apply(null,args); - if (out instanceof Promise){ - return out.then(function(res) { - return Promise.resolve(res) - }).catch(function(e) { - if (isScriptErrorMessage(e)) { - //Functions don't have line number appended in error message, so add it - var num = getErrorLineNumber(exp, workerScript); - e += " (Line " + num + ")"; - } - return Promise.reject(e); - }); - } else { - return Promise.resolve(out); - } - } catch (e) { - if (isScriptErrorMessage(e)) { - if (isScriptErrorMessage(e)) { - //Functions don't have line number appended in error message, so add it - var num = getErrorLineNumber(exp, workerScript); - e += " (Line " + num + ")"; - } - return Promise.reject(e); - } else { - return Promise.reject(makeRuntimeRejectMsg(workerScript, e, exp)); - } - } - } - }); - }); - break; - case "MemberExpression": - return evaluate(exp.object, workerScript).then(function(object) { - if (exp.computed){ - return evaluate(exp.property, workerScript).then(function(index) { - if (index >= object.length) { - return Promise.reject(makeRuntimeRejectMsg(workerScript, "Invalid index for arrays", exp)); - } - return Promise.resolve(object[index]); - }).catch(function(e) { - if (e instanceof WorkerScript || isScriptErrorMessage(e)) { - return Promise.reject(e); - } else { - return Promise.reject(makeRuntimeRejectMsg(workerScript, "Invalid MemberExpression", exp)); - } - }); - } else { - if (exp.property.name === "constructor") { - return Promise.reject(makeRuntimeRejectMsg(workerScript, "Illegal usage of constructor() method. If you have your own function named 'constructor', you must re-name it.", exp)); - } - if (object != null && object instanceof Array && exp.property.name === "forEach") { - return "NETSCRIPTFOREACH"; - } - try { - return Promise.resolve(object[exp.property.name]) - } catch (e) { - return Promise.reject(makeRuntimeRejectMsg(workerScript, "Failed to get property: " + e.toString(), exp)); - } - } - }); - break; - case "LogicalExpression": - case "BinaryExpression": - return evalBinary(exp, workerScript); - break; - case "UnaryExpression": - return evalUnary(exp, workerScript); - break; - case "AssignmentExpression": - return evalAssignment(exp, workerScript); - break; - case "VariableDeclaration": - return evalVariableDeclaration(exp, workerScript); - break; - case "UpdateExpression": - if (exp.argument.type==="Identifier"){ - if (exp.argument.name in env.vars){ - if (exp.operator === "++" || exp.operator === "--") { - switch (exp.operator) { - case "++": - env.set(exp.argument.name,env.get(exp.argument.name)+1); - break; - case "--": - env.set(exp.argument.name,env.get(exp.argument.name)-1); - break; - default: break; - } - return Promise.resolve(env.get(exp.argument.name)); - } - //Not sure what prefix UpdateExpressions there would be besides ++/-- - if (exp.prefix){ - return Promise.resolve(env.get(exp.argument.name)) - } - switch (exp.operator){ - default: - return Promise.reject(makeRuntimeRejectMsg(workerScript, "Unrecognized token: " + exp.type + ". You are trying to use code that is currently unsupported", exp)); - } - return Promise.resolve(env.get(exp.argument.name)) - } else { - return Promise.reject(makeRuntimeRejectMsg(workerScript, "variable " + exp.argument.name + " not defined", exp)); - } - } else { - return Promise.reject(makeRuntimeRejectMsg(workerScript, "argument must be an identifier", exp)); - } - break; - case "EmptyStatement": - return Promise.resolve(false); - break; - case "ReturnStatement": - return evaluate(exp.argument, workerScript).then(function(res) { - return Promise.reject(["RETURNSTATEMENT", res]); - }); - break; - case "BreakStatement": - return Promise.reject("BREAKSTATEMENT"); - break; - case "ContinueStatement": - return Promise.reject("CONTINUESTATEMENT"); - break; - case "IfStatement": - return evaluateIf(exp, workerScript); - break; - case "SwitchStatement": - return Promise.reject(makeRuntimeRejectMsg(workerScript, "Switch statements are not yet implemented in Netscript", exp)); - break; - case "WhileStatement": - return evaluateWhile(exp, workerScript).then(function(res) { - return Promise.resolve(res); - }).catch(function(e) { - if (e == "BREAKSTATEMENT" || - (e instanceof WorkerScript && e.errorMessage == "BREAKSTATEMENT")) { - return Promise.resolve("whileLoopBroken"); - } else { - return Promise.reject(e); - } - }); - break; - case "ForStatement": - return evaluate(exp.init, workerScript).then(function(expInit) { - return evaluateFor(exp, workerScript); - }).then(function(forLoopRes) { - return Promise.resolve("forLoopDone"); - }).catch(function(e) { - if (e == "BREAKSTATEMENT" || - (e instanceof WorkerScript && e.errorMessage == "BREAKSTATEMENT")) { - return Promise.resolve("forLoopBroken"); - } else { - return Promise.reject(e); - } - }); - break; - case "FunctionDeclaration": - if (exp.id && exp.id.name) { - env.set(exp.id.name, exp); - return Promise.resolve(true); - } else { - return Promise.reject(makeRuntimeRejectMsg(workerScript, "Invalid function declaration", exp)); - } - break; - case "ImportDeclaration": - return evaluateImport(exp, workerScript).then(function(res) { - return Promise.resolve(res); - }).catch(function(e) { - return Promise.reject(e); - }); - break; - case "ThrowStatement": - return evaluate(exp.argument, workerScript).then(function(res) { - return Promise.reject(makeRuntimeRejectMsg(workerScript, res)); - }); - break; - default: - return Promise.reject(makeRuntimeRejectMsg(workerScript, "Unrecognized token: " + exp.type + ". This is currently unsupported in Netscript", exp)); - break; - } //End switch - }).catch(function(e) { - return Promise.reject(e); - }); // End Promise -} - -function evalBinary(exp, workerScript){ - return evaluate(exp.left, workerScript).then(function(expLeft) { - //Short circuiting - if (expLeft && exp.operator === "||") { - return Promise.resolve(expLeft); - } - if (!expLeft && exp.operator === "&&") { - return Promise.resolve(expLeft); - } - return evaluate(exp.right, workerScript).then(function(expRight) { - switch (exp.operator){ - case "===": - case "==": - return Promise.resolve(expLeft===expRight); - break; - case "!==": - case "!=": - return Promise.resolve(expLeft!==expRight); - break; - case "<": - return Promise.resolve(expLeft": - return Promise.resolve(expLeft>expRight); - break; - case ">=": - return Promise.resolve(expLeft>=expRight); - break; - case "+": - return Promise.resolve(expLeft+expRight); - break; - case "-": - return Promise.resolve(expLeft-expRight); - break; - case "*": - return Promise.resolve(expLeft*expRight); - break; - case "/": - if (expRight === 0) { - return Promise.reject(makeRuntimeRejectMsg(workerScript, "ERROR: Divide by zero")); - } else { - return Promise.resolve(expLeft/expRight); - } - break; - case "%": - return Promise.resolve(expLeft%expRight); - break; - case "in": - return Promise.resolve(expLeft in expRight); - break; - case "instanceof": - return Promise.resolve(expLeft instanceof expRight); - break; - case "||": - return Promise.resolve(expLeft || expRight); - break; - case "&&": - return Promise.resolve(expLeft && expRight); - break; - default: - return Promise.reject(makeRuntimeRejectMsg(workerScript, "Unsupported operator: " + exp.operator)); - } - }); - }); -} - -function evalUnary(exp, workerScript){ - var env = workerScript.env; - if (env.stopFlag) {return Promise.reject(workerScript);} - return evaluate(exp.argument, workerScript).then(function(res) { - if (exp.operator == "!") { - return Promise.resolve(!res); - } else if (exp.operator == "-") { - if (isNaN(res)) { - return Promise.resolve(res); - } else { - return Promise.resolve(-1 * res); - } - } else { - return Promise.reject(makeRuntimeRejectMsg(workerScript, "Unimplemented unary operator: " + exp.operator)); - } - }); -} - -//Takes in a MemberExpression that should represent a Netscript array (possible multidimensional) -//The return value is an array of the form: -// [0th index (leftmost), array name, 1st index, 2nd index, ...] -function getArrayElement(exp, workerScript) { - var indices = []; - return evaluate(exp.property, workerScript).then(function(idx) { - if (isNaN(idx)) { - return Promise.reject(makeRuntimeRejectMsg(workerScript, "Invalid access to array. Index is not a number: " + idx)); - } else { - if (exp.object.name === undefined && exp.object.object) { - return getArrayElement(exp.object, workerScript).then(function(res) { - res.push(idx); - indices = res; - return Promise.resolve(indices); - }).catch(function(e) { - return Promise.reject(e); - }); - } else { - indices.push(idx); - indices.push(exp.object.name); - return Promise.resolve(indices); - } - } - }); -} - -function evalAssignment(exp, workerScript) { - var env = workerScript.env; - if (env.stopFlag) {return Promise.reject(workerScript);} - - if (exp.left.type != "Identifier" && exp.left.type != "MemberExpression") { - return Promise.reject(makeRuntimeRejectMsg(workerScript, "Cannot assign to " + JSON.stringify(exp.left))); - } - - if (exp.operator !== "=" && !(exp.left.name in env.vars)){ - return Promise.reject(makeRuntimeRejectMsg(workerScript, "variable " + exp.left.name + " not defined")); - } - - return evaluate(exp.right, workerScript).then(function(expRight) { - if (exp.left.type == "MemberExpression") { - if (!exp.left.computed) { - return Promise.reject(makeRuntimeRejectMsg(workerScript, "Cannot assign to an object's property. This is currently unsupported in Netscript", exp)); - } - //Assign to array element - //Array object designed by exp.left.object.name - //Index designated by exp.left.property - return getArrayElement(exp.left, workerScript).then(function(res) { - if (!(res instanceof Array) || res.length < 2) { - return Promise.reject(makeRuntimeRejectMsg(workerScript, "Error evaluating array assignment. This is (probably) a bug please report to game dev")); - } - - //The array name is the second value - var arrName = res.splice(1, 1); - arrName = arrName[0]; - - var res; - try { - res = env.setArrayElement(arrName, res, expRight); - } catch (e) { - return Promise.reject(makeRuntimeRejectMsg(workerScript, e)); - } - return Promise.resolve(res); - }).catch(function(e) { - return Promise.reject(e); - }); - } else { - //Other assignments - try { - var assign; - switch (exp.operator) { - case "=": - assign = expRight; break; - case "+=": - assign = env.get(exp.left.name) + expRight; break; - case "-=": - assign = env.get(exp.left.name) - expRight; break; - case "*=": - assign = env.get(exp.left.name) * expRight; break; - case "/=": - assign = env.get(exp.left.name) / expRight; break; - case "%=": - assign = env.get(exp.left.name) % expRight; break; - default: - return Promise.reject(makeRuntimeRejectMsg(workerScript, "Bitwise assignment is not implemented")); - } - env.set(exp.left.name, assign); - return Promise.resolve(assign); - } catch (e) { - return Promise.reject(makeRuntimeRejectMsg(workerScript, "Failed to set environment variable: " + e.toString())); - } - } - }); -} - -function evalVariableDeclaration(exp, workerScript) { - var env = workerScript.env; - if (env.stopFlag) {return Promise.reject(workerScript);} - - if (!(exp.declarations instanceof Array)) { - return Promise.reject(makeRuntimeRejectMsg(workerScript, "Variable declarations parsed incorrectly. This may be a syntax error")); - } - - if (exp.kind !== "var") { - return Promise.reject(makeRuntimeRejectMsg(workerScript, "Only 'var' declarations are currently allowed (let, const, etc. are not allowed)")); - } - - return Promise.all(exp.declarations.map((decl)=>{ - evalVariableDeclarator(decl, workerScript); - })).then(function(res) { - return Promise.resolve(res); - }); -} - -//A Variable Declaration contains an array of Variable Declarators -function evalVariableDeclarator(exp, workerScript) { - var env = workerScript.env; - if (exp.type !== "VariableDeclarator") { - return Promise.reject(makeRuntimeRejectMsg(workerScript, "Invalid AST Node passed into evalVariableDeclarator: " + exp.type)); - } - if (exp.init == null) { - env.set(exp.id.name, null); - return Promise.resolve(null); - } else { - return evaluate(exp.init, workerScript).then(function(initValue) { - env.set(exp.id.name, initValue); - }); - } -} - -function evaluateIf(exp, workerScript, i) { - var env = workerScript.env; - return evaluate(exp.test, workerScript).then(function(condRes) { - if (condRes) { - return evaluate(exp.consequent, workerScript).then(function(res) { - return Promise.resolve(true); - }, function(e) { - return Promise.reject(e); - }); - } else if (exp.alternate) { - return evaluate(exp.alternate, workerScript).then(function(res) { - return Promise.resolve(true); - }, function(e) { - return Promise.reject(e); - }); - } else { - return Promise.resolve("endIf"); - } - }); -} - -//Evaluate the looping part of a for loop (Initialization block is NOT done in here) -function evaluateFor(exp, workerScript) { - var env = workerScript.env; - if (env.stopFlag) {return Promise.reject(workerScript);} - return new Promise(function(resolve, reject) { - function recurse() { - //Don't return a promise so the promise chain is broken on each recursion (saving memory) - evaluate(exp.test, workerScript).then(function(resCond) { - if (resCond) { - return evaluate(exp.body, workerScript).then(function(res) { - return evaluate(exp.update, workerScript); - }).catch(function(e) { - if (e == "CONTINUESTATEMENT" || - (e instanceof WorkerScript && e.errorMessage == "CONTINUESTATEMENT")) { - //Continue statement, recurse to next iteration - return evaluate(exp.update, workerScript).then(function(resPostloop) { - return evaluateFor(exp, workerScript); - }).then(function(foo) { - return Promise.resolve("endForLoop"); - }).catch(function(e) { - return Promise.reject(e); - }); - } else { - return Promise.reject(e); - } - }).then(recurse, reject).catch(function(e) { - return Promise.reject(e); - }); - } else { - resolve(); - } - }).catch(function(e) { - reject(e); - }); - } - recurse(); - }); -} - -function evaluateForeach(arr, args, workerScript) { - console.log("evaluateForeach called"); - if (!(arr instanceof Array)) { - return Promise.reject("Invalid array passed into forEach"); - } - if (!(args instanceof Array) && args.length != 1) { - return Promise.reject("Invalid argument passed into forEach"); - } - var func = args[0]; - if (typeof func !== "function") { - return Promise.reject("Invalid function passed into forEach"); - } - console.log(func); - return new Promise(function(resolve, reject) { - //Don't return a promise so the promise chain is broken on each recursion - function recurse(i) { - console.log("recurse() called with i: " + i); - if (i >= arr.length) { - resolve(); - } else { - return Promise.delay(Settings.CodeInstructionRunTime).then(function() { - console.log("About to apply function"); - var res = func.apply(null, [arr[i]]); - console.log("Applied function"); - ++i; - Promise.resolve(res).then(function(val) { - recurse(i); - }, reject).catch(function(e) { - return Promise.reject(e); - }); - }); - } - } - recurse(0); - }); -} - -function evaluateWhile(exp, workerScript) { - var env = workerScript.env; - if (env.stopFlag) {return Promise.reject(workerScript);} - return new Promise(function (resolve, reject) { - function recurse() { - //Don't return a promise so the promise chain is broken on each recursion (saving memory) - evaluate(exp.test, workerScript).then(function(resCond) { - if (resCond) { - return evaluate(exp.body, workerScript).catch(function(e) { - if (e == "CONTINUESTATEMENT" || - (e instanceof WorkerScript && e.errorMessage == "CONTINUESTATEMENT")) { - //Continue statement, recurse - return evaluateWhile(exp, workerScript).then(function(foo) { - return Promise.resolve("endWhileLoop"); - }, function(e) { - return Promise.reject(e); - }); - } else { - return Promise.reject(e); - } - }).then(recurse, reject).catch(function(e) { - return Promise.reject(e); - }); - } else { - resolve(); - } - }).catch(function(e) { - reject(e); - }); - } - recurse(); - }); -} - -function evaluateProg(exp, workerScript, index) { - var env = workerScript.env; - if (env.stopFlag) {return Promise.reject(workerScript);} - if (index >= exp.body.length) { - return Promise.resolve("progFinished"); - } else { - //Evaluate this line of code in the prog - //After the code finishes evaluating, evaluate the next line recursively - return evaluate(exp.body[index], workerScript).then(function(res) { - return evaluateProg(exp, workerScript, index + 1); - }).then(function(res) { - return Promise.resolve(workerScript); - }).catch(function(e) { - return Promise.reject(e); - }); - } -} - -function evaluateImport(exp, workerScript, checkingRam=false) { +export function evaluateImport(exp, workerScript, checkingRam=false) { //When its checking RAM, it exports an array of nodes for each imported function var ramCheckRes = []; @@ -808,7 +123,7 @@ function evaluateImport(exp, workerScript, checkingRam=false) { return Promise.resolve(true); } -function killNetscriptDelay(workerScript) { +export function killNetscriptDelay(workerScript) { if (workerScript instanceof WorkerScript) { if (workerScript.delay) { clearTimeout(workerScript.delay); @@ -817,7 +132,7 @@ function killNetscriptDelay(workerScript) { } } -function netscriptDelay(time, workerScript) { +export function netscriptDelay(time, workerScript) { return new Promise(function(resolve, reject) { workerScript.delay = setTimeout(()=>{ workerScript.delay = null; @@ -827,7 +142,7 @@ function netscriptDelay(time, workerScript) { }); } -function makeRuntimeRejectMsg(workerScript, msg, exp=null) { +export function makeRuntimeRejectMsg(workerScript, msg, exp=null) { var lineNum = ""; if (exp != null) { var num = getErrorLineNumber(exp, workerScript); @@ -837,7 +152,7 @@ function makeRuntimeRejectMsg(workerScript, msg, exp=null) { } //Run a script from inside a script using run() command -function runScriptFromScript(server, scriptname, args, workerScript, threads=1) { +export function runScriptFromScript(server, scriptname, args, workerScript, threads=1) { //Check if the script is already running var runningScriptObj = findRunningScript(scriptname, args, server); if (runningScriptObj != null) { @@ -887,8 +202,8 @@ function runScriptFromScript(server, scriptname, args, workerScript, threads=1) return Promise.resolve(false); } -function getErrorLineNumber(exp, workerScript) { - var code = workerScript.scriptRef.scriptRef.code; +export function getErrorLineNumber(exp, workerScript) { + var code = workerScript.scriptRef.codeCode(); //Split code up to the start of the node try { @@ -899,7 +214,7 @@ function getErrorLineNumber(exp, workerScript) { } } -function isScriptErrorMessage(msg) { +export function isScriptErrorMessage(msg) { if (!isString(msg)) {return false;} let splitMsg = msg.split("|"); if (splitMsg.length != 4){ @@ -911,6 +226,3 @@ function isScriptErrorMessage(msg) { } return true; } - -export {makeRuntimeRejectMsg, netscriptDelay, runScriptFromScript, evaluate, - isScriptErrorMessage, killNetscriptDelay, evaluateImport}; diff --git a/src/NetscriptWorker.js b/src/NetscriptWorker.js index 2ebfe5dc3..1d38eb5c3 100644 --- a/src/NetscriptWorker.js +++ b/src/NetscriptWorker.js @@ -28,8 +28,8 @@ const walk = require("acorn/dist/walk"); function WorkerScript(runningScriptObj) { this.name = runningScriptObj.filename; this.running = false; - this.serverIp = null; - this.code = runningScriptObj.scriptRef.code; + this.serverIp = runningScriptObj.server; + this.code = runningScriptObj.getCode(); this.env = new Environment(this); this.env.set("args", runningScriptObj.args.slice()); this.output = ""; @@ -588,7 +588,7 @@ function addWorkerScript(runningScriptObj, server) { } else { runningScriptObj.threads = 1; } - var ramUsage = roundToTwo(runningScriptObj.scriptRef.ramUsage * threads); + var ramUsage = roundToTwo(runningScriptObj.getRamUsage() * threads); var ramAvailable = server.maxRam - server.ramUsed; if (ramUsage > ramAvailable) { dialogBoxCreate("Not enough RAM to run script " + runningScriptObj.filename + " with args " + @@ -601,7 +601,6 @@ function addWorkerScript(runningScriptObj, server) { //Create the WorkerScript var s = new WorkerScript(runningScriptObj); - s.serverIp = server.ip; s.ramUsage = ramUsage; //Add the WorkerScript to the Active Scripts list diff --git a/src/Script.js b/src/Script.js index 72804e868..5bf56a1e2 100755 --- a/src/Script.js +++ b/src/Script.js @@ -650,7 +650,8 @@ async function calculateRamUsage(codeCopy) { var workerScript = new WorkerScript({ filename:"foo", scriptRef: {code:""}, - args:[] + args:[], + getCode: function() { return ""; } }); workerScript.checkingRam = true; //Netscript functions will return RAM usage workerScript.serverIp = currServ.ip; @@ -778,10 +779,9 @@ Reviver.constructors.Script = Script; //Called when the game is loaded. Loads all running scripts (from all servers) //into worker scripts so that they will start running function loadAllRunningScripts() { - var count = 0; var total = 0; let skipScriptLoad = (window.location.href.toLowerCase().indexOf("?noscripts") !== -1); - if (skipScriptLoad) {console.log("Skipping the load of any scripts during startup");} + if (skipScriptLoad) { console.info("Skipping the load of any scripts during startup"); } for (var property in AllServers) { if (AllServers.hasOwnProperty(property)) { var server = AllServers[property]; @@ -799,8 +799,6 @@ function loadAllRunningScripts() { server.runningScripts.length = 0; } else { for (var j = 0; j < server.runningScripts.length; ++j) { - count++; - server.runningScripts[j].scriptRef.module = ""; addWorkerScript(server.runningScripts[j], server); //Offline production @@ -809,8 +807,8 @@ function loadAllRunningScripts() { } } } + return total; - console.log("Loaded " + count.toString() + " running scripts"); } function scriptCalculateOfflineProduction(runningScriptObj) { @@ -827,7 +825,7 @@ function scriptCalculateOfflineProduction(runningScriptObj) { //Data map: [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken] - //Grow + // Grow for (var ip in runningScriptObj.dataMap) { if (runningScriptObj.dataMap.hasOwnProperty(ip)) { if (runningScriptObj.dataMap[ip][2] == 0 || runningScriptObj.dataMap[ip][2] == null) {continue;} @@ -841,6 +839,7 @@ function scriptCalculateOfflineProduction(runningScriptObj) { } } + // Money from hacking var totalOfflineProduction = 0; for (var ip in runningScriptObj.dataMap) { if (runningScriptObj.dataMap.hasOwnProperty(ip)) { @@ -862,19 +861,19 @@ function scriptCalculateOfflineProduction(runningScriptObj) { } } - //Offline EXP gain - //A script's offline production will always be at most half of its online production. + // Offline EXP gain + // A script's offline production will always be at most half of its online production. var expGain = 0.5 * (runningScriptObj.onlineExpGained / runningScriptObj.onlineRunningTime) * timePassed; expGain *= confidence; Player.gainHackingExp(expGain); - //Update script stats + // Update script stats runningScriptObj.offlineMoneyMade += totalOfflineProduction; runningScriptObj.offlineRunningTime += timePassed; runningScriptObj.offlineExpGained += expGain; - //Fortify a server's security based on how many times it was hacked + // Fortify a server's security based on how many times it was hacked for (var ip in runningScriptObj.dataMap) { if (runningScriptObj.dataMap.hasOwnProperty(ip)) { if (runningScriptObj.dataMap[ip][1] == 0 || runningScriptObj.dataMap[ip][1] == null) {continue;} @@ -887,7 +886,7 @@ function scriptCalculateOfflineProduction(runningScriptObj) { } } - //Weaken + // Weaken for (var ip in runningScriptObj.dataMap) { if (runningScriptObj.dataMap.hasOwnProperty(ip)) { if (runningScriptObj.dataMap[ip][3] == 0 || runningScriptObj.dataMap[ip][3] == null) {continue;} @@ -916,11 +915,11 @@ function findRunningScript(filename, args, server) { } function RunningScript(script, args) { - if (script == null || script == undefined) {return;} + if (script == null || script == undefined) { return; } this.filename = script.filename; this.args = args; - this.scriptRef = script; this.server = script.server; //IP Address only + this.ramUsage = script.ramUsage; this.logs = []; //Script logging. Array of strings, with each element being a log entry this.logUpd = false; @@ -935,8 +934,40 @@ function RunningScript(script, args) { this.threads = 1; - //[MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken] - this.dataMap = new AllServersMap([0, 0, 0, 0], true); + // Holds a map of all servers, where server = key and the value for each + // server is an array of four numbers. The four numbers represent: + // [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken] + // This data is used for offline progress + this.dataMap = {}; +} + +RunningScript.prototype.getCode = function() { + const server = AllServers[this.server]; + if (server == null) { return ""; } + for (let i = 0; i < server.scripts.length; ++i) { + if (server.scripts[i].filename === this.filename) { + return server.scripts[i].code; + } + } + + return ""; +} + +RunningScript.prototype.getRamUsage = function() { + if (this.ramUsage != null && this.ramUsage > 0) { return this.ramUsage; } // Use cached value + + const server = AllServers[this.server]; + if (server == null) { return 0; } + for (let i = 0; i < server.scripts.length; ++i) { + if (server.scripts[i].filename === this.filename) { + // Cache the ram usage for the next call + this.ramUsage = server.scripts[i].ramUsage; + return this.ramUsage; + } + } + + + return 0; } RunningScript.prototype.log = function(txt) { @@ -966,9 +997,8 @@ RunningScript.prototype.clearLog = function() { //Update the moneyStolen and numTimesHack maps when hacking RunningScript.prototype.recordHack = function(serverIp, moneyGained, n=1) { - if (this.dataMap == null) { - //[MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken] - this.dataMap = new AllServersMap([0, 0, 0, 0], true); + if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) { + this.dataMap[serverIp] = [0, 0, 0, 0]; } this.dataMap[serverIp][0] += moneyGained; this.dataMap[serverIp][1] += n; @@ -976,18 +1006,16 @@ RunningScript.prototype.recordHack = function(serverIp, moneyGained, n=1) { //Update the grow map when calling grow() RunningScript.prototype.recordGrow = function(serverIp, n=1) { - if (this.dataMap == null) { - //[MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken] - this.dataMap = new AllServersMap([0, 0, 0, 0], true); + if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) { + this.dataMap[serverIp] = [0, 0, 0, 0]; } this.dataMap[serverIp][2] += n; } //Update the weaken map when calling weaken() { RunningScript.prototype.recordWeaken = function(serverIp, n=1) { - if (this.dataMap == null) { - //[MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken] - this.dataMap = new AllServersMap([0, 0, 0, 0], true); + if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) { + this.dataMap[serverIp] = [0, 0, 0, 0]; } this.dataMap[serverIp][3] += n; } @@ -1003,33 +1031,5 @@ RunningScript.fromJSON = function(value) { Reviver.constructors.RunningScript = RunningScript; -//Creates an object that creates a map/dictionary with the IP of each existing server as -//a key. Initializes every key with a specified value that can either by a number or an array -function AllServersMap(arr=false, filterOwned=false) { - for (var ip in AllServers) { - if (AllServers.hasOwnProperty(ip)) { - if (filterOwned && (AllServers[ip].purchasedByPlayer || AllServers[ip].hostname === "home")) { - continue; - } - if (arr) { - this[ip] = [0, 0, 0, 0]; - } else { - this[ip] = 0; - } - } - } -} - -AllServersMap.prototype.toJSON = function() { - return Generic_toJSON("AllServersMap", this); -} - - -AllServersMap.fromJSON = function(value) { - return Generic_fromJSON(AllServersMap, value.data); -} - -Reviver.constructors.AllServersMap = AllServersMap; - export {loadAllRunningScripts, findRunningScript, - RunningScript, Script, AllServersMap, scriptEditorInit, isScriptFilename}; + RunningScript, Script, scriptEditorInit, isScriptFilename}; diff --git a/src/ServerPurchases.js b/src/ServerPurchases.js index 115f05a54..00535b997 100644 --- a/src/ServerPurchases.js +++ b/src/ServerPurchases.js @@ -33,8 +33,10 @@ export function getPurchaseServerLimit() { } export function getPurchaseServerMaxRam() { - // TODO ensure this is a power of 2? - return Math.round(CONSTANTS.PurchasedServerMaxRam * BitNodeMultipliers.PurchasedServerMaxRam); + const ram = Math.round(CONSTANTS.PurchasedServerMaxRam * BitNodeMultipliers.PurchasedServerMaxRam); + + // Round this to the nearest power of 2 + return 1 << 31 - Math.clz32(ram); } // Manually purchase a server (NOT through Netscript) diff --git a/src/Terminal.js b/src/Terminal.js index 1e74ece1a..55f62f13d 100644 --- a/src/Terminal.js +++ b/src/Terminal.js @@ -26,8 +26,9 @@ 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 { findRunningScript, + RunningScript, + isScriptFilename } from "./Script"; import {AllServers, GetServerByHostname, getServer, Server} from "./Server"; import {Settings} from "./Settings/Settings"; @@ -779,7 +780,7 @@ let Terminal = { } Terminal.analyzeFlag = false; - //Rename the progress bar so that the next hacks dont trigger it. Re-enable terminal + // 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"); Terminal.resetTerminalInput(); @@ -804,7 +805,7 @@ let Terminal = { commands = commands.split(";"); for (let i = 0; i < commands.length; i++) { if(commands[i].match(/^\s*$/)) { continue; } // Don't run commands that only have whitespace - Terminal.executeCommand(commands[i]); + Terminal.executeCommand(commands[i].trim()); } }, @@ -1538,7 +1539,7 @@ let Terminal = { let spacesThread = Array(numSpacesThread+1).join(" "); //Calculate and transform RAM usage - let ramUsage = numeralWrapper.format(script.scriptRef.ramUsage * script.threads, '0.00') + " GB"; + let ramUsage = numeralWrapper.format(script.getRamUsage() * script.threads, '0.00') + " GB"; var entry = [script.filename, spacesScript, script.threads, spacesThread, ramUsage]; post(entry.join("")); @@ -1823,11 +1824,15 @@ let Terminal = { }, executeScanAnalyzeCommand: function(depth=1, all=false) { - //We'll use the AllServersMap as a visited() array //TODO Using array as stack for now, can make more efficient post("~~~~~~~~~~ Beginning scan-analyze ~~~~~~~~~~"); post(" "); - var visited = new AllServersMap(); + + // Map of all servers to keep track of which have been visited + var visited = {}; + for (const ip in AllServers) { + visited[ip] = 0; + } var stack = []; var depthQueue = [0];