Minor bugfixes with killing Netscript scripts, and cleaned up text

This commit is contained in:
danielyxie 2019-07-13 20:55:58 -07:00
parent 4f2f75762c
commit 06cd584f10
16 changed files with 147 additions and 100 deletions

@ -6,7 +6,7 @@ clear() Netscript Function
:param string/number port/fn: Port or text file to clear :param string/number port/fn: Port or text file to clear
:RAM cost: 1 GB :RAM cost: 1 GB
This function is used to clear data in a `Netscript Ports <http://bitburner.wikia.com/wiki/Netscript_Ports>`_ or a text file. This function is used to clear data in a :ref:`Netscript Port <netscript_ports>` or a text file.
If the *port/fn* argument is a number between 1 and 20, then it specifies a port and will clear it (deleting all data from the underlying queue). If the *port/fn* argument is a number between 1 and 20, then it specifies a port and will clear it (deleting all data from the underlying queue).

@ -11,3 +11,6 @@ getGrowTime() Netscript Function
The function takes in an optional *hackLvl* parameter that can be specified The function takes in an optional *hackLvl* parameter that can be specified
to see what the grow time would be at different hacking levels. to see what the grow time would be at different hacking levels.
.. note:: For Hacknet Servers (the upgraded version of a Hacknet Node), this function will
return :code:`Infinity`.

@ -11,3 +11,6 @@ getHackTime() Netscript Function
The function takes in an optional *hackLvl* parameter that can be specified The function takes in an optional *hackLvl* parameter that can be specified
to see what the hack time would be at different hacking levels. to see what the hack time would be at different hacking levels.
.. note:: For Hacknet Servers (the upgraded version of a Hacknet Node), this function will
return :code:`Infinity`.

@ -11,3 +11,6 @@ getWeakenTime() Netscript Function
The function takes in an optional *hackLvl* parameter that can be specified The function takes in an optional *hackLvl* parameter that can be specified
to see what the weaken time would be at different hacking levels. to see what the weaken time would be at different hacking levels.
.. note:: For Hacknet Servers (the upgraded version of a Hacknet Node), this function will
return :code:`Infinity`.

@ -4,7 +4,7 @@ growthAnalyze() Netscript Function
.. js:function:: growthAnalyze(hostname/ip, growthAmount) .. js:function:: growthAnalyze(hostname/ip, growthAmount)
:param string hostname/ip: IP or hostname of server to analyze :param string hostname/ip: IP or hostname of server to analyze
:param number growthAmount: Multiplicative factor by which the server is grown. Decimal form. :param number growthAmount: Multiplicative factor by which the server is grown. Decimal form. Must be >= 1.
:returns: The amount of grow() calls needed to grow the specified server by the specified amount :returns: The amount of grow() calls needed to grow the specified server by the specified amount
:RAM cost: 1 GB :RAM cost: 1 GB

@ -57,6 +57,10 @@ And the data in port 1 will look like::
[3, 4, 5, 6, 7, 8, 9] [3, 4, 5, 6, 7, 8, 9]
.. warning:: In :ref:`netscriptjs`, do not trying writing base
`Promises <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise>`_
to a port.
**Port Handles** **Port Handles**
WARNING: Port Handles only work in :ref:`netscriptjs`. They do not work in :ref:`netscript1` WARNING: Port Handles only work in :ref:`netscriptjs`. They do not work in :ref:`netscript1`
@ -213,16 +217,6 @@ to specify a namespace for the import::
keyword should **NOT** be used in :ref:`netscript1`, as this will break the script. keyword should **NOT** be used in :ref:`netscript1`, as this will break the script.
It can, however, be used in :ref:`netscriptjs` (but it's not required). It can, however, be used in :ref:`netscriptjs` (but it's not required).
Importing in NetscriptJS
^^^^^^^^^^^^^^^^^^^^^^^^
There is a minor annoyance when using the `import` feature in :ref:`netscriptjs`.
If you make a change in a NetscriptJS script, then you have to manually "refresh" all other
scripts that import from that script.
The easiest way to do this is to simply save and then refresh the game. Alternatively,
you can open up all the scripts that need to be "refreshed" in the script editor
and then simply re-save them.
Standard, Built-In JavaScript Objects Standard, Built-In JavaScript Objects
------------------------------------- -------------------------------------
Standard built-in JavaScript objects such as Standard built-in JavaScript objects such as

@ -63,17 +63,17 @@ Examples
**Basic example usage**:: **Basic example usage**::
for (var i = 0; i < sleeve.getNumSleeves(); i++) { for (var i = 0; i < sleeve.getNumSleeves(); i++) {
sleeve.shockRecovery(i); sleeve.setToShockRecovery(i);
}
sleep(10 * 60 * 60); // wait 10h
for (var i = 0; i < sleeve.getNumSleeves(); i++) {
sleeve.setToSynchronize(i);
} }
sleep(10*60*60); // wait 10h sleep(10*60*60); // wait 10h
for (var i = 0; i < sleeve.getNumSleeves(); i++) { for (var i = 0; i < sleeve.getNumSleeves(); i++) {
sleeve.synchronize(i); sleeve.setToCommitCrime(i, 'shoplift');
}
sleep(10*60*60); // wait 10h
for (var i = 0; i < sleeve.getNumSleeves(); i++) {
sleeve.commitCrime(i, 'shoplift');
} }

@ -8,7 +8,7 @@ import { AugmentationsRoot } from "./ui/Root";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../Constants";
import { Factions, factionExists } from "../Faction/Factions"; import { Factions, factionExists } from "../Faction/Factions";
import { addWorkerScript } from "../NetscriptWorker"; import { startWorkerScript } from "../NetscriptWorker";
import { Player } from "../Player"; import { Player } from "../Player";
import { prestigeAugmentation } from "../Prestige"; import { prestigeAugmentation } from "../Prestige";
import { saveObject } from "../SaveObject"; import { saveObject } from "../SaveObject";
@ -2078,18 +2078,17 @@ function installAugmentations(cbScript=null) {
//Run a script after prestiging //Run a script after prestiging
if (cbScript && isString(cbScript)) { if (cbScript && isString(cbScript)) {
var home = Player.getHomeComputer(); var home = Player.getHomeComputer();
for (var i = 0; i < home.scripts.length; ++i) { for (const script of home.scripts) {
if (home.scripts[i].filename === cbScript) { if (script.filename === cbScript) {
var script = home.scripts[i]; const ramUsage = script.ramUsage;
var ramUsage = script.ramUsage; const ramAvailable = home.maxRam - home.ramUsed;
var ramAvailable = home.maxRam - home.ramUsed;
if (ramUsage > ramAvailable) { if (ramUsage > ramAvailable) {
return; //Not enough RAM return; // Not enough RAM
} }
var runningScriptObj = new RunningScript(script, []); //No args const runningScriptObj = new RunningScript(script, []); // No args
runningScriptObj.threads = 1; //Only 1 thread runningScriptObj.threads = 1; // Only 1 thread
home.runScript(runningScriptObj, Player.hacknet_node_money_mult);
addWorkerScript(runningScriptObj, home); startWorkerScript(runningScriptObj, home);
} }
} }
} }

@ -227,7 +227,6 @@ export let CONSTANTS: IMap<any> = {
Netscript Changes Netscript Changes
* Added tail() Netscript function * Added tail() Netscript function
* hacknet.getNodeStats() function now returns an additional property for Hacknet Servers: hashCapacity * hacknet.getNodeStats() function now returns an additional property for Hacknet Servers: hashCapacity
* Bug fix: workForFaction() function now properly accounts for disabled logs
* When writing to a file, the write() function now casts the data being written to a string (using String()) * When writing to a file, the write() function now casts the data being written to a string (using String())
* BitNode-selection page now shows what Source-File level you have for each BitNode * BitNode-selection page now shows what Source-File level you have for each BitNode
* Overloaded kill() function so that you can kill a script by its PID * Overloaded kill() function so that you can kill a script by its PID
@ -235,6 +234,9 @@ export let CONSTANTS: IMap<any> = {
* run() and exec() now return the PID of the newly-executed scripts, rather than a boolean * run() and exec() now return the PID of the newly-executed scripts, rather than a boolean
** (A PID is just a positive integer) ** (A PID is just a positive integer)
* run(), exec(), and spawn() no longer need to be await-ed in NetscriptJS * run(), exec(), and spawn() no longer need to be await-ed in NetscriptJS
* Bug fix: workForFaction() function now properly accounts for disabled logs
* Bug fix: RAM should now be properly calculated when running a callback script with installAugmentations()
* Bug fix: Fixed bug that caused scripts killed by exit()/spawn() to "clean up" twice
Misc Changes Misc Changes
* The 'kill' Terminal command can now kill a script by its PID * The 'kill' Terminal command can now kill a script by its PID
@ -242,5 +244,7 @@ export let CONSTANTS: IMap<any> = {
* After Infiltration, you will now return to the company page rather than the city page * After Infiltration, you will now return to the company page rather than the city page
* Bug fix: Stock Market UI should no longer crash for certain locale settings * Bug fix: Stock Market UI should no longer crash for certain locale settings
* Bug fix: You can now properly remove unfinished programs (the *.exe-N%-INC files) * Bug fix: You can now properly remove unfinished programs (the *.exe-N%-INC files)
* Bug fix: Fixed an issue that allowed you to increase money on servers with a 'maxMoney' of 0 (like CSEC)
* Bug fix: Scripts no longer persist if they were started with syntax/import errors
` `
} }

@ -18,14 +18,17 @@ export const HashUpgradesMetadata: IConstructorParams[] = [
{ {
costPerLevel: 50, costPerLevel: 50,
desc: "Use hashes to decrease the minimum security of a single server by 2%. " + desc: "Use hashes to decrease the minimum security of a single server by 2%. " +
"Note that a server's minimum security cannot go below 1.", "Note that a server's minimum security cannot go below 1. This effect persists " +
"until you install Augmentations (since servers are reset at that time).",
hasTargetServer: true, hasTargetServer: true,
name: "Reduce Minimum Security", name: "Reduce Minimum Security",
value: 0.98, value: 0.98,
}, },
{ {
costPerLevel: 50, costPerLevel: 50,
desc: "Use hashes to increase the maximum amount of money on a single server by 2%", desc: "Use hashes to increase the maximum amount of money on a single server by 2%. " +
"This effect persists until you install Augmentations (since servers " +
"are reset at that time).",
hasTargetServer: true, hasTargetServer: true,
name: "Increase Maximum Money", name: "Increase Maximum Money",
value: 1.02, value: 1.02,

@ -89,7 +89,7 @@ function removeWorkerScript(workerScript: WorkerScript, rerenderUi: boolean=true
// Recalculate ram used on that server // Recalculate ram used on that server
server.ramUsed = roundToTwo(server.ramUsed - workerScript.ramUsage); server.ramUsed = roundToTwo(server.ramUsed - workerScript.ramUsage);
if (server.ramUsed < 0) { if (server.ramUsed < 0) {
console.warn(`Server RAM usage went negative (if it's due to floating pt imprecision, it's okay): ${server.ramUsed}`); console.warn(`Server (${server.hostname}) RAM usage went negative (if it's due to floating pt imprecision, it's okay): ${server.ramUsed}`);
server.ramUsed = 0; server.ramUsed = 0;
} }

@ -611,8 +611,8 @@ function NetscriptFunctions(workerScript) {
if (time === undefined) { if (time === undefined) {
throw makeRuntimeRejectMsg(workerScript, "sleep() call has incorrect number of arguments. Takes 1 argument"); throw makeRuntimeRejectMsg(workerScript, "sleep() call has incorrect number of arguments. Takes 1 argument");
} }
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.sleep == null) { if (workerScript.shouldLog("sleep")) {
workerScript.scriptRef.log("Sleeping for " + time + " milliseconds"); workerScript.log(`Sleeping for ${time} milliseconds`);
} }
return netscriptDelay(time, workerScript).then(function() { return netscriptDelay(time, workerScript).then(function() {
return Promise.resolve(true); return Promise.resolve(true);
@ -652,8 +652,8 @@ function NetscriptFunctions(workerScript) {
if (growthPercentage == 1) { if (growthPercentage == 1) {
expGain = 0; expGain = 0;
} }
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.grow == null) { if (workerScript.shouldLog("grow")) {
workerScript.scriptRef.log("Available money on " + server.hostname + " grown by " + workerScript.log("Available money on " + server.hostname + " grown by " +
formatNumber((moneyAfter/moneyBefore)*100 - 100, 6) + "%. Gained " + formatNumber((moneyAfter/moneyBefore)*100 - 100, 6) + "%. Gained " +
formatNumber(expGain, 4) + " hacking exp (t=" + threads +")"); formatNumber(expGain, 4) + " hacking exp (t=" + threads +")");
} }
@ -670,8 +670,8 @@ function NetscriptFunctions(workerScript) {
// Check argument validity // Check argument validity
const server = safeGetServer(ip, 'growthAnalyze'); const server = safeGetServer(ip, 'growthAnalyze');
if (isNaN(growth)) { if (typeof growth !== "number" || isNaN(growth) || growth < 1) {
throw makeRuntimeRejectMsg(workerScript, `Invalid growth argument passed into growthAnalyze: ${growth}. Must be numeric`); throw makeRuntimeRejectMsg(workerScript, `Invalid growth argument passed into growthAnalyze: ${growth}. Must be numeric and >= 1`);
} }
return numCycleForGrowth(server, Number(growth), Player); return numCycleForGrowth(server, Number(growth), Player);
@ -993,10 +993,15 @@ function NetscriptFunctions(workerScript) {
return runScriptFromScript(scriptServer, scriptname, argsForNewScript, workerScript, threads); return runScriptFromScript(scriptServer, scriptname, argsForNewScript, workerScript, threads);
}, spawnDelay * 1e3); }, spawnDelay * 1e3);
if (workerScript.shouldLog("spawn")) { if (workerScript.shouldLog("spawn")) {
workerScript.scriptRef.log(`spawn() will execute ${scriptname} in ${spawnDelay} seconds`); workerScript.log(`spawn() will execute ${scriptname} in ${spawnDelay} seconds`);
}
workerScript.running = false; // Prevent workerScript from "finishing execution naturally"
if (killWorkerScript(workerScript)) {
workerScript.log("Exiting...");
} }
NetscriptFunctions(workerScript).exit();
}, },
kill: function(filename, ip, ...scriptArgs) { kill: function(filename, ip, ...scriptArgs) {
updateDynamicRam("kill", getRamCost("kill")); updateDynamicRam("kill", getRamCost("kill"));
@ -1015,7 +1020,7 @@ function NetscriptFunctions(workerScript) {
const server = safeGetServer(ip); const server = safeGetServer(ip);
const runningScriptObj = getRunningScript(filename, ip, "kill", scriptArgs); const runningScriptObj = getRunningScript(filename, ip, "kill", scriptArgs);
if (runningScriptObj == null) { if (runningScriptObj == null) {
workerScript.log(`tail() failed. ${getCannotFindRunningScriptErrorMessage(filename, ip, scriptArgs)}`) workerScript.log(`kill() failed. ${getCannotFindRunningScriptErrorMessage(filename, ip, scriptArgs)}`)
return false; return false;
} }
@ -1064,14 +1069,11 @@ function NetscriptFunctions(workerScript) {
return scriptsRunning; return scriptsRunning;
}, },
exit : function() { exit : function() {
var server = getServer(workerScript.serverIp); workerScript.running = false; // Prevent workerScript from "finishing execution naturally"
if (server == null) { if (killWorkerScript(workerScript)) {
throw makeRuntimeRejectMsg(workerScript, "Error getting Server for this script in exit(). This is a bug please contact game dev"); workerScript.log("Exiting...");
}
if (killWorkerScript(workerScript.scriptRef, server.ip)) {
workerScript.scriptRef.log("Exiting...");
} else { } else {
workerScript.scriptRef.log("Exit failed(). This is a bug please contact game developer"); workerScript.log("Exit failed(). This is a bug please contact game developer");
} }
}, },
scp: function(scriptname, ip1, ip2) { scp: function(scriptname, ip1, ip2) {
@ -3495,9 +3497,13 @@ function NetscriptFunctions(workerScript) {
return false; return false;
} }
Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain); Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain);
workerScript.scriptRef.log("Installing Augmentations. This will cause this script to be killed"); workerScript.log("Installing Augmentations. This will cause this script to be killed");
setTimeoutRef(() => {
installAugmentations(cbScript); installAugmentations(cbScript);
return true; }, 0);
workerScript.running = false; // Prevent workerScript from "finishing execution naturally"
killWorkerScript(workerScript);
}, },
// Gang API // Gang API

@ -48,6 +48,8 @@ for (var i = 0; i < CONSTANTS.NumNetscriptPorts; ++i) {
export function prestigeWorkerScripts() { export function prestigeWorkerScripts() {
for (const ws of workerScripts.values()) { for (const ws of workerScripts.values()) {
ws.env.stopFlag = true; ws.env.stopFlag = true;
killWorkerScript(ws);
console.log(`Killing ${ws.name}`);
} }
WorkerScriptStartStopEventEmitter.emitEvent(); WorkerScriptStartStopEventEmitter.emitEvent();
@ -149,6 +151,7 @@ function startNetscript1Script(workerScript) {
dialogBoxCreate("Error processing Imports in " + workerScript.name + ":<br>" + e); dialogBoxCreate("Error processing Imports in " + workerScript.name + ":<br>" + e);
workerScript.env.stopFlag = true; workerScript.env.stopFlag = true;
workerScript.running = false; workerScript.running = false;
killWorkerScript(workerScript);
return; return;
} }
@ -160,7 +163,7 @@ function startNetscript1Script(workerScript) {
if (typeof entry === "function") { if (typeof entry === "function") {
//Async functions need to be wrapped. See JS-Interpreter documentation //Async functions need to be wrapped. See JS-Interpreter documentation
if (name === "hack" || name === "grow" || name === "weaken" || name === "sleep" || if (name === "hack" || name === "grow" || name === "weaken" || name === "sleep" ||
name === "prompt" || name === "run" || name === "exec") { name === "prompt") {
let tempWrapper = function() { let tempWrapper = function() {
let fnArgs = []; let fnArgs = [];
@ -183,7 +186,8 @@ function startNetscript1Script(workerScript) {
} }
int.setProperty(scope, name, int.createAsyncFunction(tempWrapper)); int.setProperty(scope, name, int.createAsyncFunction(tempWrapper));
} else if (name === "sprintf" || name === "vsprintf" || name === "scp" || } else if (name === "sprintf" || name === "vsprintf" || name === "scp" ||
name == "write" || name === "read" || name === "tryWrite") { name == "write" || name === "read" || name === "tryWrite" ||
name === "run" || name === "exec") {
let tempWrapper = function() { let tempWrapper = function() {
let fnArgs = []; let fnArgs = [];
@ -232,6 +236,7 @@ function startNetscript1Script(workerScript) {
dialogBoxCreate("Syntax ERROR in " + workerScript.name + ":<br>" + e); dialogBoxCreate("Syntax ERROR in " + workerScript.name + ":<br>" + e);
workerScript.env.stopFlag = true; workerScript.env.stopFlag = true;
workerScript.running = false; workerScript.running = false;
killWorkerScript(workerScript);
return; return;
} }
@ -445,14 +450,35 @@ function generateNextPid() {
} }
/** /**
* Start a script * Used to start a RunningScript (by creating and starting its
* * corresponding WorkerScript), and add the RunningScript to the server on which
* Given a RunningScript object, constructs a corresponding WorkerScript, * it is active
* @param {RunningScript} runningScriptObj - Script that's being run
* @param {Server} server - Server on which the script is to be run
* @returns {number} pid of started script
*/
export function startWorkerScript(runningScript, server) {
if (createAndAddWorkerScript(runningScript, server)) {
// Push onto runningScripts.
// This has to come after createAndAddWorkerScript() because that fn updates RAM usage
server.runScript(runningScript, Player.hacknet_node_money_mult);
// Once the WorkerScript is constructed in createAndAddWorkerScript(), the RunningScript
// object should have a PID assigned to it, so we return that
return runningScript.pid;
}
return 0;
}
/**
* Given a RunningScript object, constructs its corresponding WorkerScript,
* adds it to the global 'workerScripts' pool, and begins executing it. * adds it to the global 'workerScripts' pool, and begins executing it.
* @param {RunningScript} runningScriptObj - Script that's being run * @param {RunningScript} runningScriptObj - Script that's being run
* @param {Server} server - Server on which the script is to be run * @param {Server} server - Server on which the script is to be run
* returns {boolean} indicating whether or not the workerScript was successfully added
*/ */
export function addWorkerScript(runningScriptObj, server) { export function createAndAddWorkerScript(runningScriptObj, server) {
const filename = runningScriptObj.filename; const filename = runningScriptObj.filename;
// Update server's ram usage // Update server's ram usage
@ -471,7 +497,7 @@ export function addWorkerScript(runningScriptObj, server) {
`the game and the script's RAM usage increased (either because of an update to the game or ` + `the game and the script's RAM usage increased (either because of an update to the game or ` +
`your changes to the script.)` `your changes to the script.)`
); );
return; return false;
} }
server.ramUsed = roundToTwo(server.ramUsed + ramUsage); server.ramUsed = roundToTwo(server.ramUsed + ramUsage);
@ -489,31 +515,35 @@ export function addWorkerScript(runningScriptObj, server) {
const s = new WorkerScript(runningScriptObj, pid, NetscriptFunctions); const s = new WorkerScript(runningScriptObj, pid, NetscriptFunctions);
s.ramUsage = ramUsage; s.ramUsage = ramUsage;
// Add the WorkerScript to the global pool
workerScripts.set(pid, s);
WorkerScriptStartStopEventEmitter.emitEvent();
// Start the script's execution // Start the script's execution
let p = null; // Script's resulting promise let p = null; // Script's resulting promise
if (s.name.endsWith(".js") || s.name.endsWith(".ns")) { if (s.name.endsWith(".js") || s.name.endsWith(".ns")) {
p = startNetscript2Script(s); p = startNetscript2Script(s);
} else { } else {
p = startNetscript1Script(s); p = startNetscript1Script(s);
if (!(p instanceof Promise)) { return; } if (!(p instanceof Promise)) { return false; }
} }
// Once the code finishes (either resolved or rejected, doesnt matter), set its // Once the code finishes (either resolved or rejected, doesnt matter), set its
// running status to false // running status to false
p.then(function(w) { p.then(function(w) {
// If the WorkerScript is no longer "running", then this means its execution was
// already stopped somewhere else (maybe by something like exit()). This prevents
// the script from being cleaned up twice
if (!w.running) { return; }
console.log("Stopping script " + w.name + " because it finished running naturally"); console.log("Stopping script " + w.name + " because it finished running naturally");
killWorkerScript(s); killWorkerScript(s);
w.scriptRef.log("Script finished running"); w.log("Script finished running");
}).catch(function(w) { }).catch(function(w) {
if (w instanceof Error) { if (w instanceof Error) {
dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer"); dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
console.error("Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + w.toString()); console.error("Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + w.toString());
return; return;
} else if (w.constructor === Array && w.length === 2 && w[0] === "RETURNSTATEMENT") {
// Script ends with a return statement
console.log("Script returning with value: " + w[1]);
// TODO maybe do something with this in the future
return;
} else if (w instanceof WorkerScript) { } else if (w instanceof WorkerScript) {
if (isScriptErrorMessage(w.errorMessage)) { if (isScriptErrorMessage(w.errorMessage)) {
var errorTextArray = w.errorMessage.split("|"); var errorTextArray = w.errorMessage.split("|");
@ -529,9 +559,9 @@ export function addWorkerScript(runningScriptObj, server) {
dialogBoxCreate("Script runtime error: <br>Server Ip: " + serverIp + dialogBoxCreate("Script runtime error: <br>Server Ip: " + serverIp +
"<br>Script name: " + scriptName + "<br>Script name: " + scriptName +
"<br>Args:" + arrayToString(w.args) + "<br>" + errorMsg); "<br>Args:" + arrayToString(w.args) + "<br>" + errorMsg);
w.scriptRef.log("Script crashed with runtime error"); w.log("Script crashed with runtime error");
} else { } else {
w.scriptRef.log("Script killed"); w.log("Script killed");
return; // Already killed, so stop here return; // Already killed, so stop here
} }
w.running = false; w.running = false;
@ -548,10 +578,7 @@ export function addWorkerScript(runningScriptObj, server) {
killWorkerScript(s); killWorkerScript(s);
}); });
// Add the WorkerScript to the global pool return true;
workerScripts.set(pid, s);
WorkerScriptStartStopEventEmitter.emitEvent();
return;
} }
/** /**
@ -589,7 +616,7 @@ export function loadAllRunningScripts() {
server.runningScripts.length = 0; server.runningScripts.length = 0;
} else { } else {
for (let j = 0; j < server.runningScripts.length; ++j) { for (let j = 0; j < server.runningScripts.length; ++j) {
addWorkerScript(server.runningScripts[j], server); createAndAddWorkerScript(server.runningScripts[j], server);
// Offline production // Offline production
total += scriptCalculateOfflineProduction(server.runningScripts[j]); total += scriptCalculateOfflineProduction(server.runningScripts[j]);
@ -655,15 +682,8 @@ export function runScriptFromScript(server, scriptname, args, workerScript, thre
} }
let runningScriptObj = new RunningScript(script, args); let runningScriptObj = new RunningScript(script, args);
runningScriptObj.threads = threads; runningScriptObj.threads = threads;
addWorkerScript(runningScriptObj, server);
// Push onto runningScripts. return startWorkerScript(runningScriptObj, server);
// This has to come after addWorkerScript() because that fn updates RAM usage
server.runScript(runningScriptObj, Player.hacknet_node_money_mult);
// Once the WorkerScript is constructed in addWorkerScript(), the RunningScript
// object should have a PID assigned to it, so we return that
return runningScriptObj.pid;
} }
} }
} }

@ -11,6 +11,7 @@ import { HacknetServer } from "../Hacknet/HacknetServer";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { Programs } from "../Programs/Programs"; import { Programs } from "../Programs/Programs";
import { isValidNumber } from "../utils/helpers/isValidNumber";
import { isValidIPAddress } from "../../utils/helpers/isValidIPAddress"; import { isValidIPAddress } from "../../utils/helpers/isValidIPAddress";
/** /**
@ -35,11 +36,17 @@ export function safetlyCreateUniqueServer(params: IConstructorParams): Server {
return new Server(params); return new Server(params);
} }
// Returns the number of cycles needed to grow the specified server by the /**
// specified amount. 'growth' parameter is in decimal form, not percentage * Returns the number of "growth cycles" needed to grow the specified server by the
* specified amount.
* @param server - Server being grown
* @param growth - How much the server is being grown by, in DECIMAL form (e.g. 1.5 rather than 50)
* @param p - Reference to Player object
* @returns Number of "growth cycles" needed
*/
export function numCycleForGrowth(server: Server, growth: number, p: IPlayer) { export function numCycleForGrowth(server: Server, growth: number, p: IPlayer) {
let ajdGrowthRate = 1 + (CONSTANTS.ServerBaseGrowthRate - 1) / server.hackDifficulty; let ajdGrowthRate = 1 + (CONSTANTS.ServerBaseGrowthRate - 1) / server.hackDifficulty;
if(ajdGrowthRate > CONSTANTS.ServerMaxGrowthRate) { if (ajdGrowthRate > CONSTANTS.ServerMaxGrowthRate) {
ajdGrowthRate = CONSTANTS.ServerMaxGrowthRate; ajdGrowthRate = CONSTANTS.ServerMaxGrowthRate;
} }
@ -75,12 +82,12 @@ export function processSingleServerGrowth(server: Server, numCycles: number, p:
server.moneyAvailable *= serverGrowth; server.moneyAvailable *= serverGrowth;
// in case of data corruption // in case of data corruption
if (server.moneyMax && isNaN(server.moneyAvailable)) { if (isValidNumber(server.moneyMax) && isNaN(server.moneyAvailable)) {
server.moneyAvailable = server.moneyMax; server.moneyAvailable = server.moneyMax;
} }
// cap at max // cap at max
if (server.moneyMax && server.moneyAvailable > server.moneyMax) { if (isValidNumber(server.moneyMax) && server.moneyAvailable > server.moneyMax) {
server.moneyAvailable = server.moneyMax; server.moneyAvailable = server.moneyMax;
} }

@ -53,7 +53,7 @@ import {
import { showLiterature } from "./Literature"; import { showLiterature } from "./Literature";
import { Message } from "./Message/Message"; import { Message } from "./Message/Message";
import { showMessage } from "./Message/MessageHelpers"; import { showMessage } from "./Message/MessageHelpers";
import { addWorkerScript } from "./NetscriptWorker"; import { startWorkerScript } from "./NetscriptWorker";
import { killWorkerScript } from "./Netscript/killWorkerScript"; import { killWorkerScript } from "./Netscript/killWorkerScript";
import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStartStopEventEmitter"; import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStartStopEventEmitter";
import { Player } from "./Player"; import { Player } from "./Player";
@ -2303,21 +2303,19 @@ let Terminal = {
return; return;
} else { } else {
// Able to run script // Able to run script
post("Running script with " + numThreads + " thread(s) and args: " + arrayToString(args) + ".");
var runningScriptObj = new RunningScript(script, args); var runningScriptObj = new RunningScript(script, args);
runningScriptObj.threads = numThreads; runningScriptObj.threads = numThreads;
addWorkerScript(runningScriptObj, server); if (startWorkerScript(runningScriptObj, server)) {
post("Running script with " + numThreads + " thread(s) and args: " + arrayToString(args) + ".");
// This has to come after addWorkerScript() because that fn } else {
// updates the RAM usage. This kinda sucks, address if possible postError(`Failed to start script`);
server.runScript(runningScriptObj, Player.hacknet_node_money_mult); }
return; return;
} }
} }
} }
post("ERROR: No such script"); post("ERROR: No such script");
}, },

@ -0,0 +1,7 @@
/**
* Checks that a variable is a valid number. A valid number
* must be a "number" type and cannot be NaN
*/
export function isValidNumber(n: number): boolean {
return (typeof n === "number") && !isNaN(n);
}