Various QOL improvements and bug fixes

This commit is contained in:
danielyxie 2019-02-08 18:46:30 -08:00
parent 8c8e3f2476
commit 840df3087f
14 changed files with 221 additions and 68 deletions

@ -454,3 +454,48 @@ do not allow cross-origin origin sharing (CORS). This includes websites such
as gist and pastebin. One notable site it will work on is rawgithub. Example::
$ wget https://raw.githubusercontent.com/danielyxie/bitburner/master/README.md game_readme.txt
Argument Parsing
----------------
When evaluating a terminal command, arguments are initially parsed based on whitespace (usually spaces).
Each whitespace character signifies the end of an argument, and potentially the start
of new one. For most terminal commands, this is all you need to know.
When running scripts, however, it is important to know in more detail how arguments are parsed.
There are two main points:
1. Quotation marks can be used to wrap a single argument and force it to be parsed as
a string. Any whitespace inside the quotation marks will not cause a new argument
to be parsed.
2. Anything that can represent a number is automatically cast to a number, unless its
surrounded by quotation marks.
Here's an example to show how these rules work. Consider the following script `argType.script`::
tprint("Number of args: " + args.length);
for (var i = 0; i < args.length; ++i) {
tprint(typeof args[i]);
}
Then if we run the following terminal command::
$ run argType.script 123 1e3 "5" "this is a single argument"
We'll see the following in the Terminal::
Running script with 1 thread(s) and args: [123, 1000, "5", "this is a single argument"].
May take a few seconds to start up the process...
argType.script: Number of args: 4
argType.script: number
argType.script: number
argType.script: string
argType.script: string
Chaining Commands
-----------------
You can run multiple Terminal commands at once by separating each command
with a semicolon (;).
Example::
$ run foo.script; tail foo.script

@ -1211,6 +1211,26 @@ vsprintf
See `this link <https://github.com/alexei/sprintf.js>`_ for details.
nFormat
^^^^^^^
.. js:function:: nFormat(n, format)
:param number n: Number to format
:param string format: Formatter
Converts a number into a string with the specified formatter. This uses the
`numeraljs <http://numeraljs.com/>`_ library, so the formatters must be compatible
with that.
This is the same function that the game itself uses to display numbers.
Examples::
nFormat(1.23e9, "$0.000a"); // Returns "$1.230b"
nFormat(12345.678, "0,0"); // Returns "12,346"
nFormat(0.84, "0.0%"); // Returns "84.0%
prompt
^^^^^^

@ -209,7 +209,19 @@ to specify a namespace for the import::
//...
}
Note that exporting functions is not required.
.. warning:: For those who are experienced with JavaScript, note that the `export`
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
-------------------------------------

@ -1,21 +1,22 @@
import {workerScripts,
killWorkerScript} from "./NetscriptWorker";
import {Player} from "./Player";
import {getServer} from "./Server";
import {numeralWrapper} from "./ui/numeralFormat";
import {dialogBoxCreate} from "../utils/DialogBox";
import {createAccordionElement} from "../utils/uiHelpers/createAccordionElement";
import {arrayToString} from "../utils/helpers/arrayToString";
import {createElement} from "../utils/uiHelpers/createElement";
import {createProgressBarText} from "../utils/helpers/createProgressBarText";
import {exceptionAlert} from "../utils/helpers/exceptionAlert";
import {getElementById} from "../utils/uiHelpers/getElementById";
import {logBoxCreate} from "../utils/LogBox";
import {formatNumber} from "../utils/StringHelperFunctions";
import {removeChildrenFromElement} from "../utils/uiHelpers/removeChildrenFromElement";
import {removeElement} from "../utils/uiHelpers/removeElement";
import {roundToTwo} from "../utils/helpers/roundToTwo";
import {Page, routing} from "./ui/navigationTracking";
killWorkerScript} from "./NetscriptWorker";
import {Player} from "./Player";
import {getServer} from "./Server";
import {numeralWrapper} from "./ui/numeralFormat";
import {dialogBoxCreate} from "../utils/DialogBox";
import {createAccordionElement} from "../utils/uiHelpers/createAccordionElement";
import {arrayToString} from "../utils/helpers/arrayToString";
import {createElement} from "../utils/uiHelpers/createElement";
import {createProgressBarText} from "../utils/helpers/createProgressBarText";
import {exceptionAlert} from "../utils/helpers/exceptionAlert";
import {getElementById} from "../utils/uiHelpers/getElementById";
import {logBoxCreate} from "../utils/LogBox";
import {formatNumber,
convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
import {removeChildrenFromElement} from "../utils/uiHelpers/removeChildrenFromElement";
import {removeElement} from "../utils/uiHelpers/removeElement";
import {roundToTwo} from "../utils/helpers/roundToTwo";
import {Page, routing} from "./ui/navigationTracking";
/* {
* serverName: {
@ -242,9 +243,9 @@ function updateActiveScriptsItems(maxTasks=150) {
}
}
getElementById("active-scripts-total-production-active").innerText = numeralWrapper.format(total, '$0.000a');
getElementById("active-scripts-total-prod-aug-total").innerText = numeralWrapper.format(Player.scriptProdSinceLastAug, '$0.000a');
getElementById("active-scripts-total-prod-aug-avg").innerText = numeralWrapper.format(Player.scriptProdSinceLastAug / (Player.playtimeSinceLastAug/1000), '$0.000a');
getElementById("active-scripts-total-production-active").innerText = numeralWrapper.formatMoney(total);
getElementById("active-scripts-total-prod-aug-total").innerText = numeralWrapper.formatMoney(Player.scriptProdSinceLastAug);
getElementById("active-scripts-total-prod-aug-avg").innerText = numeralWrapper.formatMoney(Player.scriptProdSinceLastAug / (Player.playtimeSinceLastAug/1000));
return total;
}
@ -252,7 +253,7 @@ function updateActiveScriptsItems(maxTasks=150) {
function updateActiveScriptsItemContent(workerscript) {
var server = getServer(workerscript.serverIp);
if (server == null) {
console.log("ERROR: Invalid server IP for workerscript.");
console.log("ERROR: Invalid server IP for workerscript in updateActiveScriptsItemContent().");
return;
}
let hostname = server.hostname;
@ -280,7 +281,7 @@ function updateActiveScriptsItemContent(workerscript) {
function updateActiveScriptsText(workerscript, item, itemName) {
var server = getServer(workerscript.serverIp);
if (server == null) {
console.log("ERROR: Invalid server IP for workerscript.");
console.log("ERROR: Invalid server IP for workerscript for updateActiveScriptsText()");
return;
}
let hostname = server.hostname;
@ -298,24 +299,27 @@ function updateActiveScriptsText(workerscript, item, itemName) {
removeChildrenFromElement(item);
//Online
var onlineTotalMoneyMade = "Total online production: $" + formatNumber(workerscript.scriptRef.onlineMoneyMade, 2);
var onlineTotalExpEarned = (Array(26).join(" ") + formatNumber(workerscript.scriptRef.onlineExpGained, 2) + " hacking exp").replace( / /g, "&nbsp;");
var onlineTime = "Online Time: " + convertTimeMsToTimeElapsedString(workerscript.scriptRef.onlineRunningTime * 1e3);
var offlineTime = "Offline Time: " + convertTimeMsToTimeElapsedString(workerscript.scriptRef.offlineRunningTime * 1e3);
var onlineMpsText = "Online production rate: $" + formatNumber(onlineMps, 2) + "/second";
//Online
var onlineTotalMoneyMade = "Total online production: " + numeralWrapper.formatMoney(workerscript.scriptRef.onlineMoneyMade);
var onlineTotalExpEarned = (Array(26).join(" ") + numeralWrapper.formatBigNumber(workerscript.scriptRef.onlineExpGained) + " hacking exp").replace( / /g, "&nbsp;");
var onlineMpsText = "Online production rate: " + numeralWrapper.formatMoney(onlineMps) + " / second";
var onlineEps = workerscript.scriptRef.onlineExpGained / workerscript.scriptRef.onlineRunningTime;
var onlineEpsText = (Array(25).join(" ") + formatNumber(onlineEps, 4) + " hacking exp/second").replace( / /g, "&nbsp;");
var onlineEpsText = (Array(25).join(" ") + numeralWrapper.formatBigNumber(onlineEps) + " hacking exp / second").replace( / /g, "&nbsp;");
//Offline
var offlineTotalMoneyMade = "Total offline production: $" + formatNumber(workerscript.scriptRef.offlineMoneyMade, 2);
var offlineTotalExpEarned = (Array(27).join(" ") + formatNumber(workerscript.scriptRef.offlineExpGained, 2) + " hacking exp").replace( / /g, "&nbsp;");
var offlineTotalMoneyMade = "Total offline production: " + numeralWrapper.formatMoney(workerscript.scriptRef.offlineMoneyMade);
var offlineTotalExpEarned = (Array(27).join(" ") + numeralWrapper.formatBigNumber(workerscript.scriptRef.offlineExpGained) + " hacking exp").replace( / /g, "&nbsp;");
var offlineMps = workerscript.scriptRef.offlineMoneyMade / workerscript.scriptRef.offlineRunningTime;
var offlineMpsText = "Offline production rate: $" + formatNumber(offlineMps, 2) + "/second";
var offlineMpsText = "Offline production rate: " + numeralWrapper.formatMoney(offlineMps) + " / second";
var offlineEps = workerscript.scriptRef.offlineExpGained / workerscript.scriptRef.offlineRunningTime;
var offlineEpsText = (Array(26).join(" ") + formatNumber(offlineEps, 4) + " hacking exp/second").replace( / /g, "&nbsp;");
var offlineEpsText = (Array(26).join(" ") + numeralWrapper.formatBigNumber(offlineEps) + " hacking exp / second").replace( / /g, "&nbsp;");
item.innerHTML = onlineTotalMoneyMade + "<br>" + onlineTotalExpEarned + "<br>" +
item.innerHTML = onlineTime + "<br>" + offlineTime + "<br>" + onlineTotalMoneyMade + "<br>" + onlineTotalExpEarned + "<br>" +
onlineMpsText + "<br>" + onlineEpsText + "<br>" + offlineTotalMoneyMade + "<br>" + offlineTotalExpEarned + "<br>" +
offlineMpsText + "<br>" + offlineEpsText + "<br>";
return onlineMps;

@ -78,6 +78,9 @@ export class Augmentation {
// The Player/Person classes
mults: IMap<number> = {}
// Initial cost. Doesn't change when you purchase multiple Augmentation
startingCost: number = 0;
constructor(params: IConstructorParams={ info: "", moneyCost: 0, name: "", repCost: 0 }) {
this.name = params.name;
this.info = params.info;
@ -85,6 +88,7 @@ export class Augmentation {
this.baseRepRequirement = params.repCost * CONSTANTS.AugmentationRepMultiplier * BitNodeMultipliers.AugmentationRepCost;
this.baseCost = params.moneyCost * CONSTANTS.AugmentationCostMultiplier * BitNodeMultipliers.AugmentationMoneyCost;
this.startingCost = this.baseCost;
this.level = 0;

@ -512,17 +512,22 @@ export let CONSTANTS: IMap<any> = {
`
v0.43.1
* Terminal changes:
** Quoted arguments are now properly parsed. (e.g. run f.script "this is one argument" will be correctly parsed)
** Quoted arguments are now properly parsed. (e.g. 'run f.script "this is one argument"' will be correctly parsed)
** Errors are now shown in red text
** 'unalias' command now has a different format and no longer needs the quotations
** Bug Fix: Fixed several edge cases where autocomplete wasnt working properly
* Added two new Bladeburner skills for increasing money and experience gain
* Made some minor adjustments to Bladeburner UI
* Corporation "Smart Factories" and "Smart Storage" upgrades have slightly lower price multipliers
* Added nFormat Netscript function
* Added 6 new Coding Contract problems
* Updated documentation with list of all Coding Contract problems
* Minor improvements for 'Active Scripts' UI
* 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%)
* Bug Fix: The cost of Resleeves should no longer be affected by buying Augs
* Bug Fix: You can now call the prompt() Netscript function from multiple scripts simultaneously
`
}

@ -51,6 +51,7 @@ import {StockMarket, StockSymbols, SymbolToStockMap,
sellStock, updateStockPlayerPosition,
shortStock, sellShort, OrderTypes,
PositionTypes, placeOrder, cancelOrder} from "./StockMarket/StockMarket";
import {numeralWrapper} from "./ui/numeralFormat";
import {post} from "./ui/postToTerminal";
import {TextFile, getTextFile, createTextFile} from "./TextFile";
@ -76,6 +77,10 @@ import {yesNoBoxClose, yesNoBoxGetYesButton,
yesNoBoxGetNoButton, yesNoBoxCreate,
yesNoBoxOpen} from "../utils/YesNoBox";
import { createElement } from "../utils/uiHelpers/createElement";
import { createPopup } from "../utils/uiHelpers/createPopup";
import { removeElementById } from "../utils/uiHelpers/removeElementById";
var hasCorporationSF = false, //Source-File 3
hasSingularitySF = false, //Source-File 4
hasAISF = false, //Source-File 5
@ -2453,6 +2458,14 @@ function NetscriptFunctions(workerScript) {
return runningScriptObj.onlineExpGained / runningScriptObj.onlineRunningTime;
}
},
nFormat : function(n, format) {
if (workerScript.checkingRam) { return 0; }
if (isNaN(n) || isNaN(parseFloat(n)) || typeof format !== "string") {
return "";
}
return numeralWrapper.format(parseFloat(n), format);
},
getTimeSinceLastAug : function() {
if (workerScript.checkingRam) {
return updateStaticRam("getTimeSinceLastAug", CONSTANTS.ScriptGetHackTimeRamCost);
@ -2467,19 +2480,32 @@ function NetscriptFunctions(workerScript) {
return false;
}
if (!isString(txt)) {txt = String(txt);}
var yesBtn = yesNoBoxGetYesButton(), noBtn = yesNoBoxGetNoButton();
yesBtn.innerHTML = "Yes";
noBtn.innerHTML = "No";
// The id for this popup will consist of the first 20 characters of the prompt string..
// Thats hopefully good enough to be unique
const popupId = `prompt-popup-${txt.slice(0, 20)}`;
const textElement = createElement("p", { innerHTML: txt });
return new Promise(function(resolve, reject) {
yesBtn.addEventListener("click", ()=>{
yesNoBoxClose();
resolve(true);
const yesBtn = createElement("button", {
class: "popup-box-button",
innerText: "Yes",
clickListener: () => {
removeElementById(popupId);
resolve(true);
},
});
noBtn.addEventListener("click", ()=>{
yesNoBoxClose();
resolve(false);
const noBtn = createElement("button", {
class: "popup-box-button",
innerText: "No",
clickListener: () => {
removeElementById(popupId);
resolve(false);
},
});
yesNoBoxCreate(txt);
createPopup(popupId, [textElement, yesBtn, noBtn]);
});
},
wget : async function(url, target, ip=workerScript.serverIp) {

@ -219,6 +219,8 @@ function startNetscript1Script(workerScript) {
let fnPromise = entry.apply(null, fnArgs);
fnPromise.then(function(res) {
cb(res);
}).catch(function(e) {
// Do nothing?
});
}
int.setProperty(scope, name, int.createAsyncFunction(tempWrapper));
@ -278,7 +280,7 @@ function startNetscript1Script(workerScript) {
return new Promise(function(resolve, reject) {
function runInterpreter() {
try {
if (workerScript.env.stopFlag) {return reject(workerScript);}
if (workerScript.env.stopFlag) { return reject(workerScript); }
if (interpreter.step()) {
window.setTimeout(runInterpreter, Settings.CodeInstructionRunTime);
@ -498,7 +500,7 @@ function runScriptsLoop() {
p = startNetscript2Script(workerScripts[i]);
} else {
p = startNetscript1Script(workerScripts[i]);
if (!(p instanceof Promise)) {continue;}
if (!(p instanceof Promise)) { continue; }
}
//Once the code finishes (either resolved or rejected, doesnt matter), set its
@ -539,7 +541,6 @@ function runScriptsLoop() {
}
w.running = false;
w.env.stopFlag = true;
} else if (isScriptErrorMessage(w)) {
dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
console.log("ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " + w.toString());

@ -44,7 +44,7 @@ export class Resleeve extends Person {
console.error(`Could not find Augmentation ${this.augmentations[i].name}`);
continue;
}
totalAugmentationCost += aug!.baseCost;
totalAugmentationCost += aug!.startingCost;
}
return (totalExp * CostPerExp) + (totalAugmentationCost * Math.pow(NumAugsExponent, this.augmentations.length));

@ -34,6 +34,9 @@ import {initStockMarket, initSymbolToStockMap,
stockMarketContentCreated,
setStockMarketContentCreated} from "./StockMarket/StockMarket";
import {Terminal, postNetburnerText} from "./Terminal";
import {Page, routing} from "./ui/navigationTracking";
import Decimal from "decimal.js";
import {dialogBoxCreate} from "../utils/DialogBox";
import {removeElementById} from "../utils/uiHelpers/removeElementById";
@ -45,6 +48,13 @@ let BitNode8StartingMoney = 250e6;
//Prestige by purchasing augmentation
function prestigeAugmentation() {
// Load Terminal Screen
var mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "visible";
Terminal.resetTerminalInput();
Engine.loadTerminalContent();
routing.navigateTo(Page.Terminal);
initBitNodeMultipliers();
Player.prestigeAugmentation();
@ -146,12 +156,6 @@ function prestigeAugmentation() {
var watchlist = document.getElementById("stock-market-watchlist-filter");
watchlist.value = ""; //Reset watchlist filter
//Load Terminal Screen
var mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "visible";
Terminal.resetTerminalInput();
Engine.loadTerminalContent();
// Refresh Main Menu (the 'World' menu, specifically)
document.getElementById("world-menu-header").click();
document.getElementById("world-menu-header").click();

@ -165,7 +165,7 @@ $(document).keydown(function(event) {
const semiColonIndex = input.lastIndexOf(";");
if(semiColonIndex !== -1) {
input = input.slice(semiColonIndex+1);
input = input.slice(semiColonIndex + 1);
}
input = input.trim();
@ -310,6 +310,14 @@ function tabCompletion(command, arg, allPossibilities, index=0) {
}
}
const textBox = document.getElementById("terminal-input-text-box");
if (textBox == null) {
console.warn(`Couldn't find terminal input DOM element (id=terminal-input-text-box) when trying to autocomplete`);
return;
}
const oldValue = textBox.value;
const semiColonIndex = oldValue.lastIndexOf(";");
var val = "";
if (allPossibilities.length == 0) {
return;
@ -321,10 +329,7 @@ function tabCompletion(command, arg, allPossibilities, index=0) {
val = command + " " + allPossibilities[0];
}
const textBox = document.getElementById("terminal-input-text-box");
const oldValue = textBox.value;
const semiColonIndex = oldValue.lastIndexOf(";");
if(semiColonIndex === -1) {
if (semiColonIndex === -1) {
// no ; replace the whole thing.
textBox.value = val;
} else {
@ -332,7 +337,7 @@ function tabCompletion(command, arg, allPossibilities, index=0) {
textBox.value = textBox.value.slice(0, semiColonIndex+1)+" "+val;
}
document.getElementById("terminal-input-text-box").focus();
textBox.focus();
} else {
var longestStartSubstr = longestCommonStart(allPossibilities);
//If the longest common starting substring of remaining possibilities is the same
@ -348,8 +353,15 @@ function tabCompletion(command, arg, allPossibilities, index=0) {
post("> " + command);
post(allOptionsStr);
} else {
document.getElementById("terminal-input-text-box").value = longestStartSubstr;
document.getElementById("terminal-input-text-box").focus();
if (semiColonIndex === -1) {
// No ; just replace the whole thing
textBox.value = longestStartSubstr;
} else {
// Multiple commands, so only replace after the last semicolon
textBox.value = textBox.value.slice(0, semiColonIndex + 1) + " " + longestStartSubstr;
}
textBox.focus();
}
} else {
if (longestStartSubstr == arg) {
@ -357,8 +369,15 @@ function tabCompletion(command, arg, allPossibilities, index=0) {
post("> " + command + " " + arg);
post(allOptionsStr);
} else {
document.getElementById("terminal-input-text-box").value = command + " " + longestStartSubstr;
document.getElementById("terminal-input-text-box").focus();
if (semiColonIndex == -1) {
// No ; so just replace the whole thing
textBox.value = command + " " + longestStartSubstr;
} else {
// Multiple commands, so only replace after the last semiclon
textBox.value = textBox.value.slice(0, semiColonIndex + 1) + " " + command + " " + longestStartSubstr;
}
textBox.focus();
}
}

@ -1032,7 +1032,6 @@ const Engine = {
//is necessary and then resets the counter
checkCounters: function() {
if (Engine.Counters.autoSaveCounter <= 0) {
saveObject.saveGame(indexedDb);
if (Settings.AutosaveInterval == null) {
Settings.AutosaveInterval = 60;
}
@ -1040,6 +1039,7 @@ const Engine = {
Engine.Counters.autoSaveCounter = Infinity;
} else {
Engine.Counters.autoSaveCounter = Settings.AutosaveInterval * 5;
saveObject.saveGame(indexedDb);
}
}

@ -10,8 +10,8 @@ function replaceAt(base: string, index: number, character: string): string {
/*
Converts a date representing time in milliseconds to a string with the format H hours M minutes and S seconds
e.g. 10000 -> "0 hours 0 minutes and 10 seconds"
120000 -> "0 0 hours 2 minutes and 0 seconds"
e.g. 10000 -> "10 seconds"
120000 -> "2 minutes and 0 seconds"
*/
function convertTimeMsToTimeElapsedString(time: number): string {
const millisecondsPerSecond: number = 1000;

@ -1,6 +1,19 @@
/**
* Returns the input array as a comma separated string.
*
* Does several things that Array.toString() doesn't do
* - Adds brackets around the array
* - Adds quotation marks around strings
*/
export function arrayToString<T>(a: T[]) {
return `[${a.join(", ")}]`;
const vals: any[] = [];
for (let i = 0; i < a.length; ++i) {
let elem: any = a[i];
if (typeof elem === "string") {
elem = `"${elem}"`;
}
vals.push(elem);
}
return `[${vals.join(", ")}]`;
}