Minor bugfixes with killing Netscript scripts, and cleaned up text

This commit is contained in:
danielyxie 2019-07-13 20:55:58 -07:00 committed by danielyxie
parent c0432359c3
commit 042f926700
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
: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).

@ -11,3 +11,6 @@ getGrowTime() Netscript Function
The function takes in an optional *hackLvl* parameter that can be specified
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
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
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)
: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
:RAM cost: 1 GB

@ -57,6 +57,10 @@ And the data in port 1 will look like::
[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**
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.
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 such as

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

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

@ -227,7 +227,6 @@ export let CONSTANTS: IMap<any> = {
Netscript Changes
* Added tail() Netscript function
* 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())
* 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
@ -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
** (A PID is just a positive integer)
* 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
* 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
* 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: 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,
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,
name: "Reduce Minimum Security",
value: 0.98,
},
{
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,
name: "Increase Maximum Money",
value: 1.02,

@ -89,7 +89,7 @@ function removeWorkerScript(workerScript: WorkerScript, rerenderUi: boolean=true
// Recalculate ram used on that server
server.ramUsed = roundToTwo(server.ramUsed - workerScript.ramUsage);
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;
}

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

@ -48,6 +48,8 @@ for (var i = 0; i < CONSTANTS.NumNetscriptPorts; ++i) {
export function prestigeWorkerScripts() {
for (const ws of workerScripts.values()) {
ws.env.stopFlag = true;
killWorkerScript(ws);
console.log(`Killing ${ws.name}`);
}
WorkerScriptStartStopEventEmitter.emitEvent();
@ -149,6 +151,7 @@ function startNetscript1Script(workerScript) {
dialogBoxCreate("Error processing Imports in " + workerScript.name + ":<br>" + e);
workerScript.env.stopFlag = true;
workerScript.running = false;
killWorkerScript(workerScript);
return;
}
@ -160,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 === "run" || name === "exec") {
name === "prompt") {
let tempWrapper = function() {
let fnArgs = [];
@ -183,7 +186,8 @@ function startNetscript1Script(workerScript) {
}
int.setProperty(scope, name, int.createAsyncFunction(tempWrapper));
} 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 fnArgs = [];
@ -232,6 +236,7 @@ function startNetscript1Script(workerScript) {
dialogBoxCreate("Syntax ERROR in " + workerScript.name + ":<br>" + e);
workerScript.env.stopFlag = true;
workerScript.running = false;
killWorkerScript(workerScript);
return;
}
@ -445,14 +450,35 @@ function generateNextPid() {
}
/**
* Start a script
*
* Given a RunningScript object, constructs a corresponding WorkerScript,
* Used to start a RunningScript (by creating and starting its
* corresponding WorkerScript), and add the RunningScript to the server on which
* 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.
* @param {RunningScript} runningScriptObj - Script that's being 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;
// 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 ` +
`your changes to the script.)`
);
return;
return false;
}
server.ramUsed = roundToTwo(server.ramUsed + ramUsage);
@ -489,31 +515,35 @@ export function addWorkerScript(runningScriptObj, server) {
const s = new WorkerScript(runningScriptObj, pid, NetscriptFunctions);
s.ramUsage = ramUsage;
// Add the WorkerScript to the global pool
workerScripts.set(pid, s);
WorkerScriptStartStopEventEmitter.emitEvent();
// Start the script's execution
let p = null; // Script's resulting promise
if (s.name.endsWith(".js") || s.name.endsWith(".ns")) {
p = startNetscript2Script(s);
} else {
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
// running status to false
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");
killWorkerScript(s);
w.scriptRef.log("Script finished running");
w.log("Script finished running");
}).catch(function(w) {
if (w instanceof Error) {
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());
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) {
if (isScriptErrorMessage(w.errorMessage)) {
var errorTextArray = w.errorMessage.split("|");
@ -529,9 +559,9 @@ export function addWorkerScript(runningScriptObj, server) {
dialogBoxCreate("Script runtime error: <br>Server Ip: " + serverIp +
"<br>Script name: " + scriptName +
"<br>Args:" + arrayToString(w.args) + "<br>" + errorMsg);
w.scriptRef.log("Script crashed with runtime error");
w.log("Script crashed with runtime error");
} else {
w.scriptRef.log("Script killed");
w.log("Script killed");
return; // Already killed, so stop here
}
w.running = false;
@ -548,10 +578,7 @@ export function addWorkerScript(runningScriptObj, server) {
killWorkerScript(s);
});
// Add the WorkerScript to the global pool
workerScripts.set(pid, s);
WorkerScriptStartStopEventEmitter.emitEvent();
return;
return true;
}
/**
@ -589,7 +616,7 @@ export function loadAllRunningScripts() {
server.runningScripts.length = 0;
} else {
for (let j = 0; j < server.runningScripts.length; ++j) {
addWorkerScript(server.runningScripts[j], server);
createAndAddWorkerScript(server.runningScripts[j], server);
// Offline production
total += scriptCalculateOfflineProduction(server.runningScripts[j]);
@ -655,19 +682,12 @@ export function runScriptFromScript(server, scriptname, args, workerScript, thre
}
let runningScriptObj = new RunningScript(script, args);
runningScriptObj.threads = threads;
addWorkerScript(runningScriptObj, server);
// Push onto runningScripts.
// 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;
return startWorkerScript(runningScriptObj, server);
}
}
}
workerScript.log(`Could not find script ${scriptname} on ${server.hostname}`);
return 0;
}

@ -11,6 +11,7 @@ import { HacknetServer } from "../Hacknet/HacknetServer";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Programs } from "../Programs/Programs";
import { isValidNumber } from "../utils/helpers/isValidNumber";
import { isValidIPAddress } from "../../utils/helpers/isValidIPAddress";
/**
@ -35,11 +36,17 @@ export function safetlyCreateUniqueServer(params: IConstructorParams): Server {
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) {
let ajdGrowthRate = 1 + (CONSTANTS.ServerBaseGrowthRate - 1) / server.hackDifficulty;
if(ajdGrowthRate > CONSTANTS.ServerMaxGrowthRate) {
if (ajdGrowthRate > CONSTANTS.ServerMaxGrowthRate) {
ajdGrowthRate = CONSTANTS.ServerMaxGrowthRate;
}
@ -75,12 +82,12 @@ export function processSingleServerGrowth(server: Server, numCycles: number, p:
server.moneyAvailable *= serverGrowth;
// in case of data corruption
if (server.moneyMax && isNaN(server.moneyAvailable)) {
if (isValidNumber(server.moneyMax) && isNaN(server.moneyAvailable)) {
server.moneyAvailable = server.moneyMax;
}
// cap at max
if (server.moneyMax && server.moneyAvailable > server.moneyMax) {
if (isValidNumber(server.moneyMax) && server.moneyAvailable > server.moneyMax) {
server.moneyAvailable = server.moneyMax;
}

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