Fixed numerous reported bugs. Refactored some of the directory-related code. Added documentation for MasonDs changes to hack/grow/weaken

This commit is contained in:
danielyxie 2019-05-11 19:20:20 -07:00
parent 29e0ce5f96
commit b0918d7bd3
19 changed files with 172 additions and 88 deletions

@ -134,10 +134,15 @@ A Stop Order to buy will execute if the stock's price <= order's price
A Stop Order to sell will execute if the stock's price >= order's price.
.. note:: Limit and Stop orders do **not** take into account the fact that
transactions can influence a stock's price. If a stock's price
changes mid-transaction, a limit/stop order will continue to execute
even if its conditions are no longer met.
.. note:: Stop Orders do **not** take into account the fact that transactions can
influence a stock's price. Limit Orders, however, do take this into account.
For example, assume you have a Limit Order set to purchase a stock at
$5. Then, the stock's price drops to $5 and your Limit Order executes.
However, the transaction causes the stock's price to increase before
the order finishes executing all of the shares. Your Limit Order will
stop executing, and will continue only when the stock's price drops back to
$5 or below.
Automating the Stock Market
---------------------------

@ -4,6 +4,11 @@ grow() Netscript Function
.. js:function:: grow(hostname/ip)
:param string hostname/ip: IP or hostname of the target server to grow
:param object options: Optional parameters for configuring function behavior. Properties:
* threads (*number*) - Number of threads to use for this function.
Must be less than or equal to the number of threads the script is running with.
:returns: The number by which the money on the server was multiplied for the growth
:RAM cost: 0.15 GB
@ -19,3 +24,4 @@ grow() Netscript Function
Example::
grow("foodnstuff");
grow("foodnstuff", { threads: 5 }); // Only use 5 threads to grow

@ -4,6 +4,11 @@ hack() Netscript Function
.. js:function:: hack(hostname/ip)
:param string hostname/ip: IP or hostname of the target server to hack
:param object options: Optional parameters for configuring function behavior. Properties:
* threads (*number*) - Number of threads to use for this function.
Must be less than or equal to the number of threads the script is running with.
:returns: The amount of money stolen if the hack is successful, and zero otherwise
:RAM cost: 0.1 GB
@ -20,3 +25,4 @@ hack() Netscript Function
hack("foodnstuff");
hack("10.1.2.3");
hack("foodnstuff", { threads: 5 }); // Only use 5 threads to hack

@ -1,9 +1,14 @@
weaken() Netscript Function
===========================
.. js:function:: weaken(hostname/ip)
.. js:function:: weaken(hostname/ip, options={})
:param string hostname/ip: IP or hostname of the target server to weaken
:param object options: Optional parameters for configuring function behavior. Properties:
* threads (*number*) - Number of threads to use for this function.
Must be less than or equal to the number of threads the script is running with.
:returns: The amount by which the target server's security level was decreased. This is equivalent to 0.05 multiplied
by the number of script threads
:RAM cost: 0.15 GB
@ -18,3 +23,4 @@ weaken() Netscript Function
Example::
weaken("foodnstuff");
weaken("foodnstuff", { threads: 5 }); // Only use 5 threads to weaken

@ -5,7 +5,7 @@ workForFaction() Netscript Function
:param string factionName: Name of faction to work for. CASE-SENSITIVE
:param string workType:
Type of work to perform for the faction
Type of work to perform for the faction:
* hacking/hacking contracts/hackingcontracts
* field/fieldwork/field work

@ -233,9 +233,12 @@ export let CONSTANTS: IMap<any> = {
* Re-sleeves can no longer have the NeuroFlux Governor augmentation
** This is just a temporary patch until the mechanic gets re-worked
* hack(), grow(), and weaken() functions now take optional arguments for number of threads to use (by MasonD)
* Adjusted RAM costs of Netscript Singularity functions (mostly increased)
* Netscript Singularity functions no longer cost extra RAM outside of BitNode-4
* Corporation employees no longer have an "age" stat
* Gang Wanted level gain rate capped at 100 (per employee)
* Bug Fix: Corporation employees stats should no longer become negative
* Bug Fix: Fixed sleeve.getInformation() throwing error in certain scenarios
* Bug Fix: Coding contracts should no longer generate on the w0r1d_d43m0n server
@ -245,5 +248,9 @@ export let CONSTANTS: IMap<any> = {
* Bug Fix: Purchasing hash upgrades for Bladeburner/Corporation when you don't actually have access to those mechanics no longer gives hashes
* Bug Fix: run(), exec(), and spawn() Netscript functions now throw if called with 0 threads
* Bug Fix: Faction UI should now automatically update reputation
* Bug Fix: Fixed purchase4SMarketData()
* Bug Fix: Netscript1.0 now works properly for multiple 'namespace' imports (import * as namespace from "script")
* Bug Fix: Terminal 'wget' command now correctly evaluates directory paths
* Bug Fix: wget(), write(), and scp() Netscript functions now fail if an invalid filepath is passed in
`
}

@ -682,21 +682,25 @@ GangMember.prototype.calculateRespectGain = function(gang) {
GangMember.prototype.calculateWantedLevelGain = function(gang) {
const task = this.getTask();
if (task == null || !(task instanceof GangMemberTask) || task.baseWanted === 0) {return 0;}
var statWeight = (task.hackWeight/100) * this.hack +
(task.strWeight/100) * this.str +
(task.defWeight/100) * this.def +
(task.dexWeight/100) * this.dex +
(task.agiWeight/100) * this.agi +
(task.chaWeight/100) * this.cha;
if (task == null || !(task instanceof GangMemberTask) || task.baseWanted === 0) { return 0; }
let statWeight = (task.hackWeight / 100) * this.hack +
(task.strWeight / 100) * this.str +
(task.defWeight / 100) * this.def +
(task.dexWeight / 100) * this.dex +
(task.agiWeight / 100) * this.agi +
(task.chaWeight / 100) * this.cha;
statWeight -= (3.5 * task.difficulty);
if (statWeight <= 0) { return 0; }
const territoryMult = Math.pow(AllGangs[gang.facName].territory * 100, task.territory.wanted) / 100;
if (isNaN(territoryMult) || territoryMult <= 0) { return 0; }
if (task.baseWanted < 0) {
return 0.5 * task.baseWanted * statWeight * territoryMult;
return 0.4 * task.baseWanted * statWeight * territoryMult;
} else {
return 7 * task.baseWanted / (Math.pow(3 * statWeight * territoryMult, 0.8));
const calc = 7 * task.baseWanted / (Math.pow(3 * statWeight * territoryMult, 0.8));
// Put an arbitrary cap on this to prevent wanted level from rising too fast if the
// denominator is very small. Might want to rethink formula later
return Math.max(100, calc);
}
}

@ -6,7 +6,6 @@
*/
import { IReturnStatus } from "../types";
//import { HacknetServer } from "../Hacknet/HacknetServer";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Server } from "../Server/Server";

@ -110,6 +110,7 @@ import {
getStockMarket4SDataCost,
getStockMarket4STixApiCost
} from "./StockMarket/StockMarketCosts";
import { isValidFilePath } from "./Terminal/DirectoryHelpers";
import { TextFile, getTextFile, createTextFile } from "./TextFile";
import {
@ -1013,8 +1014,14 @@ function NetscriptFunctions(workerScript) {
});
return res;
}
if (!scriptname.endsWith(".lit") && !isScriptFilename(scriptname) &&
!scriptname.endsWith("txt")) {
// Invalid file type
if (!isValidFilePath(scriptname)) {
throw makeRuntimeRejectMsg(workerScript, `Error: scp() failed due to invalid filename: ${scriptname}`);
}
// Invalid file name
if (!scriptname.endsWith(".lit") && !isScriptFilename(scriptname) && !scriptname.endsWith("txt")) {
throw makeRuntimeRejectMsg(workerScript, "ERROR: scp() does not work with this file type. It only works for .script, .lit, and .txt files");
}
@ -1724,6 +1731,7 @@ function NetscriptFunctions(workerScript) {
if (workerScript.shouldLog("purchase4SMarketData")) {
workerScript.log("Purchased 4S Market Data");
}
displayStockMarketContent();
return true;
},
purchase4SMarketDataTixApi : function() {
@ -1749,6 +1757,7 @@ function NetscriptFunctions(workerScript) {
if (workerScript.shouldLog("purchase4SMarketDataTixApi")) {
workerScript.log("Purchased 4S Market Data TIX API");
}
displayStockMarketContent();
return true;
},
getPurchasedServerLimit : function() {
@ -1919,8 +1928,12 @@ function NetscriptFunctions(workerScript) {
}
return port.write(data);
} else if (isString(port)) { // Write to script or text file
var fn = port;
var server = workerScript.getServer();
const fn = port;
if (!isValidFilePath(fn)) {
throw makeRuntimeRejectMsg(workerScript, `write() failed due to invalid filepath: ${fn}`);
}
const server = workerScript.getServer();
if (server == null) {
throw makeRuntimeRejectMsg(workerScript, "Error getting Server for this script in write(). This is a bug please contact game dev");
}
@ -2251,7 +2264,7 @@ function NetscriptFunctions(workerScript) {
},
wget: async function(url, target, ip=workerScript.serverIp) {
if (!isScriptFilename(target) && !target.endsWith(".txt")) {
workerSript.log(`ERROR: wget() failed because of an invalid target file: ${target}. Target file must be a script or text file`);
workerScript.log(`ERROR: wget() failed because of an invalid target file: ${target}. Target file must be a script or text file`);
return Promise.resolve(false);
}
var s = safeGetServer(ip, "wget");

@ -178,7 +178,6 @@ function startNetscript1Script(workerScript) {
fnArgs.push(arguments[i]);
}
}
console.log(fnArgs);
let cb = arguments[arguments.length-1];
let fnPromise = entry.apply(null, fnArgs);
fnPromise.then(function(res) {
@ -289,7 +288,7 @@ function startNetscript1Script(workerScript) {
*/
function processNetscript1Imports(code, workerScript) {
//allowReserved prevents 'import' from throwing error in ES5
var ast = parse(code, {ecmaVersion:6, allowReserved:true, sourceType:"module"});
const ast = parse(code, { ecmaVersion: 6, allowReserved: true, sourceType: "module" });
var server = workerScript.getServer();
if (server == null) {
@ -305,10 +304,10 @@ function processNetscript1Imports(code, workerScript) {
return null;
}
var generatedCode = ""; //Generated Javascript Code
var hasImports = false;
let generatedCode = ""; // Generated Javascript Code
let hasImports = false;
//Walk over the tree and process ImportDeclaration nodes
// Walk over the tree and process ImportDeclaration nodes
walk.simple(ast, {
ImportDeclaration: (node) => {
hasImports = true;
@ -323,7 +322,7 @@ function processNetscript1Imports(code, workerScript) {
let scriptAst = parse(script.code, {ecmaVersion:5, allowReserved:true, sourceType:"module"});
if (node.specifiers.length === 1 && node.specifiers[0].type === "ImportNamespaceSpecifier") {
//import * as namespace from script
// import * as namespace from script
let namespace = node.specifiers[0].local.name;
let fnNames = []; //Names only
let fnDeclarations = []; //FunctionDeclaration Node objects
@ -335,7 +334,7 @@ function processNetscript1Imports(code, workerScript) {
});
//Now we have to generate the code that would create the namespace
generatedCode =
generatedCode +=
"var " + namespace + ";\n" +
"(function (namespace) {\n";
@ -406,6 +405,7 @@ function processNetscript1Imports(code, workerScript) {
//Add the imported code and re-generate in ES5 (JS Interpreter for NS1 only supports ES5);
code = generatedCode + code;
var res = {
code: code,
lineOffset: lineOffset

@ -0,0 +1,5 @@
export enum RamCalculationErrorCode {
SyntaxError = -1,
ImportError = -2,
URLImportError = -3,
}

@ -1,6 +1,8 @@
// Calculate a script's RAM usage
import * as walk from "acorn-walk";
import { RamCalculationErrorCode } from "./RamCalculationErrorCodes";
import { RamCosts, RamCostConstants } from "../Netscript/RamCostGenerator";
import { parse, Node } from "../../utils/acorn";
@ -71,12 +73,12 @@ async function parseOnlyRamCalculate(otherScripts, code, workerScript) {
}
} catch(e) {
console.error(`Error dynamically importing module from ${nextModule} for RAM calculations: ${e}`);
return -1;
return RamCalculationErrorCode.URLImportError;
}
} else {
if (!Array.isArray(otherScripts)) {
console.warn(`parseOnlyRamCalculate() not called with array of scripts`);
return -1;
return RamCalculationErrorCode.ImportError;
}
let script = null;
@ -89,8 +91,7 @@ async function parseOnlyRamCalculate(otherScripts, code, workerScript) {
}
if (script == null) {
console.warn("Invalid script");
return -1; // No such script on the server.
return RamCalculationErrorCode.ImportError; // No such script on the server
}
code = script.code;
@ -191,7 +192,7 @@ async function parseOnlyRamCalculate(otherScripts, code, workerScript) {
// console.info("parse or eval error: ", error);
// This is not unexpected. The user may be editing a script, and it may be in
// a transitory invalid state.
return -1;
return RamCalculationErrorCode.SyntaxError;
}
}
@ -310,8 +311,8 @@ export async function calculateRamUsage(codeCopy, otherScripts) {
} catch (e) {
console.error(`Failed to parse script for RAM calculations:`);
console.error(e);
return -1;
return RamCalculationErrorCode.SyntaxError;
}
return -1;
return RamCalculationErrorCode.SyntaxError;
}

@ -89,7 +89,7 @@ export class Script {
// Updates the script's RAM usage based on its code
async updateRamUsage(otherScripts: Script[]) {
var res = await calculateRamUsage(this.code, otherScripts);
if (res !== -1) {
if (res > 0) {
this.ramUsage = roundToTwo(res);
}
}

@ -1,5 +1,6 @@
import { Script } from "./Script";
import { RamCalculationErrorCode } from "./RamCalculationErrorCodes";
import { calculateRamUsage } from "./RamCalculations";
import { isScriptFilename } from "./ScriptHelpersTS";
@ -190,10 +191,22 @@ export async function updateScriptEditorContent() {
var codeCopy = code.repeat(1);
var ramUsage = await calculateRamUsage(codeCopy, Player.getCurrentServer().scripts);
if (ramUsage !== -1) {
if (ramUsage > 0) {
scriptEditorRamText.innerText = "RAM: " + numeralWrapper.format(ramUsage, '0.00') + " GB";
} else {
switch (ramUsage) {
case RamCalculationErrorCode.ImportError:
scriptEditorRamText.innerText = "RAM: Import Error";
break;
case RamCalculationErrorCode.URLImportError:
scriptEditorRamText.innerText = "RAM: HTTP Import Error";
break;
case RamCalculationErrorCode.SyntaxError:
default:
scriptEditorRamText.innerText = "RAM: Syntax Error";
break;
}
}
}

@ -5,6 +5,7 @@ import { CodingContract } from "../CodingContracts";
import { Message } from "../Message/Message";
import { RunningScript } from "../Script/RunningScript";
import { Script } from "../Script/Script";
import { isValidFilePath } from "../Terminal/DirectoryHelpers";
import { TextFile } from "../TextFile";
import { IReturnStatus } from "../types";
@ -223,10 +224,10 @@ export class BaseServer {
* Overwrites existing files. Creates new files if the script does not eixst
*/
writeToScriptFile(fn: string, code: string) {
var ret = {success: false, overwritten: false};
if (!isScriptFilename(fn)) { return ret; }
var ret = { success: false, overwritten: false };
if (!isValidFilePath(fn) || !isScriptFilename(fn)) { return ret; }
//Check if the script already exists, and overwrite it if it does
// Check if the script already exists, and overwrite it if it does
for (let i = 0; i < this.scripts.length; ++i) {
if (fn === this.scripts[i].filename) {
let script = this.scripts[i];
@ -239,7 +240,7 @@ export class BaseServer {
}
}
//Otherwise, create a new script
// Otherwise, create a new script
const newScript = new Script(fn, code, this.ip, this.scripts);
this.scripts.push(newScript);
ret.success = true;
@ -250,9 +251,9 @@ export class BaseServer {
// Overwrites existing files. Creates new files if the text file does not exist
writeToTextFile(fn: string, txt: string) {
var ret = { success: false, overwritten: false };
if (!fn.endsWith("txt")) { return ret; }
if (!isValidFilePath(fn) || !fn.endsWith("txt")) { return ret; }
//Check if the text file already exists, and overwrite if it does
// Check if the text file already exists, and overwrite if it does
for (let i = 0; i < this.textFiles.length; ++i) {
if (this.textFiles[i].fn === fn) {
ret.overwritten = true;
@ -262,7 +263,7 @@ export class BaseServer {
}
}
//Otherwise create a new text file
// Otherwise create a new text file
var newFile = new TextFile(fn, txt);
this.textFiles.push(newFile);
ret.success = true;

@ -1475,7 +1475,7 @@ let Terminal = {
}
let url = commandArray[1];
let target = commandArray[2];
let target = Terminal.getFilepath(commandArray[2]);
if (!isScriptFilename(target) && !target.endsWith(".txt")) {
return post(`wget failed: Invalid target file. Target file must be script or text file`);
}

@ -1,9 +1,12 @@
/**
* Helper functions that implement "directory" functionality in the Terminal.
* These aren't real directories, they're more of a pseudo-directory implementation
* These aren't "real" directories, it's more of a pseudo-directory implementation
* that uses mainly string manipulation.
*
* This file contains functions that deal only with that string manipulation.
* Functions that need to access/process Server-related things can be
* found in ./DirectoryServerHelpers.ts
*/
import { HacknetServer } from "../Hacknet/HacknetServer";
import { Server } from "../Server/Server";
/**
* Removes leading forward slash ("/") from a string.
@ -149,44 +152,6 @@ export function getAllParentDirectories(path: string): string {
return t_path.slice(0, lastSlash + 1);
}
/**
* Given a directory (by the full directory path) and a server, returns all
* subdirectories of that directory. This is only for FIRST-LEVEl/immediate subdirectories
*/
export function getSubdirectories(serv: Server | HacknetServer, dir: string): string[] {
const res: string[] = [];
if (!isValidDirectoryPath(dir)) { return res; }
let t_dir = dir;
if (!t_dir.endsWith("/")) { t_dir += "/"; }
function processFile(fn: string) {
if (t_dir === "/" && isInRootDirectory(fn)) {
const subdir = getFirstParentDirectory(fn);
if (subdir !== "/" && !res.includes(subdir)) {
res.push(subdir);
}
} else if (fn.startsWith(t_dir)) {
const remaining = fn.slice(t_dir.length);
const subdir = getFirstParentDirectory(remaining);
if (subdir !== "/" && !res.includes(subdir)) {
res.push(subdir);
}
}
}
for (const script of serv.scripts) {
processFile(script.filename);
}
for (const txt of serv.textFiles) {
processFile(txt.fn);
}
return res;
}
/**
* Checks if a file path refers to a file in the root directory.
*/

@ -0,0 +1,53 @@
/**
* Helper functions that implement "directory" functionality in the Terminal.
* These aren't "real" directories, it's more of a pseudo-directory implementation
* that uses mainly string manipulation.
*
* This file contains function that deal with Server-related directory things.
* Functions that deal with the string manipulation can be found in
* ./DirectoryHelpers.ts
*/
import {
isValidDirectoryPath,
isInRootDirectory,
getFirstParentDirectory,
} from "./DirectoryHelpers";
import { BaseServer } from "../Server/BaseServer";
/**
* Given a directory (by the full directory path) and a server, returns all
* subdirectories of that directory. This is only for FIRST-LEVEl/immediate subdirectories
*/
export function getSubdirectories(serv: BaseServer, dir: string): string[] {
const res: string[] = [];
if (!isValidDirectoryPath(dir)) { return res; }
let t_dir = dir;
if (!t_dir.endsWith("/")) { t_dir += "/"; }
function processFile(fn: string) {
if (t_dir === "/" && isInRootDirectory(fn)) {
const subdir = getFirstParentDirectory(fn);
if (subdir !== "/" && !res.includes(subdir)) {
res.push(subdir);
}
} else if (fn.startsWith(t_dir)) {
const remaining = fn.slice(t_dir.length);
const subdir = getFirstParentDirectory(remaining);
if (subdir !== "/" && !res.includes(subdir)) {
res.push(subdir);
}
}
}
for (const script of serv.scripts) {
processFile(script.filename);
}
for (const txt of serv.textFiles) {
processFile(txt.fn);
}
return res;
}

@ -1,8 +1,8 @@
import {
evaluateDirectoryPath,
getAllParentDirectories,
getSubdirectories,
} from "./DirectoryHelpers";
import { getSubdirectories } from "./DirectoryServerHelpers";
import {
Aliases,