Merge pull request #536 from danielyxie/dev

Dev
This commit is contained in:
danielyxie 2019-02-11 17:26:01 -08:00 committed by GitHub
commit 1d0515a957
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 1848 additions and 1511 deletions

File diff suppressed because one or more lines are too long

80
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

@ -29,7 +29,7 @@ List of all Source-Files
| | * Each level of this Source-File opens up more of the Singularity Functions to use | | | * Each level of this Source-File opens up more of the Singularity Functions to use |
+------------------------------------+-------------------------------------------------------------------------------------+ +------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-5: Artificial Intelligence | * Unlocks :ref:`gameplay_intelligence` | | BitNode-5: Artificial Intelligence | * Unlocks :ref:`gameplay_intelligence` |
| | * Unlocks getBitNodeMultipliers() Netscript function | | | * Unlocks :js:func:`getBitNodeMultipliers` Netscript function |
| | * Increases all of the player's hacking-related multipliers by 8%/12%/14% | | | * Increases all of the player's hacking-related multipliers by 8%/12%/14% |
+------------------------------------+-------------------------------------------------------------------------------------+ +------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-6: Bladeburners | * Unlocks the Bladeburner feature in other BitNodes | | BitNode-6: Bladeburners | * Unlocks the Bladeburner feature in other BitNodes |

@ -76,3 +76,135 @@ Notes
^^^^^ ^^^^^
* The *scp* Terminal command does not work on Coding Contracts * The *scp* Terminal command does not work on Coding Contracts
List of all Problem Types
^^^^^^^^^^^^^^^^^^^^^^^^^
The following is a list of all of the problem types that a Coding Contract can contain.
The list contains the name of (i.e. the value returned by
:js:func:`getContractType`) and a brief summary of the problem it poses.
+------------------------------------+------------------------------------------------------------------------------------------+
| Name | Problem Summary |
+====================================+==========================================================================================+
| Find Largest Prime Factor | | Given a number, find its largest prime factor. A prime factor |
| | | is a factor that is a prime number. |
+------------------------------------+------------------------------------------------------------------------------------------+
| Subarray with Maximum Sum | | Given an array of integers, find the contiguous subarray (containing |
| | | at least one number) which has the largest sum and return that sum. |
+------------------------------------+------------------------------------------------------------------------------------------+
| Total Ways to Sum | | Given a number, how many different ways can that number be written as |
| | | a sum of at least two positive integers? |
+------------------------------------+------------------------------------------------------------------------------------------+
| Spiralize Matrix | | Given an array of array of numbers representing a 2D matrix, return the |
| | | elements of that matrix in clockwise spiral order. |
| | | |
| | | Example: The spiral order of |
| | | |
| | | [1, 2, 3, 4] |
| | | [5, 6, 7, 8] |
| | | [9, 10, 11, 12] |
| | | |
| | | is [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7] |
+------------------------------------+------------------------------------------------------------------------------------------+
| Array Jumping Game | | You are given an array of integers where each element represents the |
| | | maximum possible jump distance from that position. For example, if you |
| | | are at position i and your maximum jump length is n, then you can jump |
| | | to any position from i to i+n. |
| | | |
| | | Assuming you are initially positioned at the start of the array, determine |
| | | whether you are able to reach the last index of the array EXACTLY. |
+------------------------------------+------------------------------------------------------------------------------------------+
| Merge Overlapping Intervals | | Given an array of intervals, merge all overlapping intervals. An interval |
| | | is an array with two numbers, where the first number is always less than |
| | | the second (e.g. [1, 5]). |
| | | |
| | | The intervals must be returned in ASCENDING order. |
| | | |
| | | Example: |
| | | [[1, 3], [8, 10], [2, 6], [10, 16]] |
| | | merges into [[1, 6], [8, 16]] |
+------------------------------------+------------------------------------------------------------------------------------------+
| Generate IP Addresses | | Given a string containing only digits, return an array with all possible |
| | | valid IP address combinations that can be created from the string. |
| | | |
| | | An octet in the IP address cannot begin with '0' unless the number itself |
| | | is actually 0. For example, "192.168.010.1" is NOT a valid IP. |
| | | |
| | | Examples: |
| | | 25525511135 -> [255.255.11.135, 255.255.111.35] |
| | | 1938718066 -> [193.87.180.66] |
+------------------------------------+------------------------------------------------------------------------------------------+
| Algorithmic Stock Trader I | | You are given an array of numbers representing stock prices, where the |
| | | i-th element represents the stock price on day i. |
| | | |
| | | Determine the maximum possible profit you can earn using at most one |
| | | transaction (i.e. you can buy an sell the stock once). If no profit |
| | | can be made, then the answer should be 0. Note that you must buy the stock |
| | | before you can sell it. |
+------------------------------------+------------------------------------------------------------------------------------------+
| Algorithmic Stock Trader II | | You are given an array of numbers representing stock prices, where the |
| | | i-th element represents the stock price on day i. |
| | | |
| | | Determine the maximum possible profit you can earn using as many transactions |
| | | as you'd like. A transaction is defined as buying and then selling one |
| | | share of the stock. Note that you cannot engage in multiple transactions at |
| | | once. In other words, you must sell the stock before you buy it again. If no |
| | | profit can be made, then the answer should be 0. |
+------------------------------------+------------------------------------------------------------------------------------------+
| Algorithmic Stock Trader III | | You are given an array of numbers representing stock prices, where the |
| | | i-th element represents the stock price on day i. |
| | | |
| | | Determine the maximum possible profit you can earn using at most two |
| | | transactions. A transaction is defined as buying and then selling one share |
| | | of the stock. Note that you cannot engage in multiple transactions at once. |
| | | In other words, you must sell the stock before you buy it again. If no profit |
| | | can be made, then the answer should be 0. |
+------------------------------------+------------------------------------------------------------------------------------------+
| Algorithmic Stock Trader IV | | You are given an array with two elements. The first element is an integer k. |
| | | The second element is an array of numbers representing stock prices, where the |
| | | i-th element represents the stock price on day i. |
| | | |
| | | Determine the maximum possible profit you can earn using at most k transactions. |
| | | A transaction is defined as buying and then selling one share of the stock. |
| | | Note that you cannot engage in multiple transactions at once. In other words, |
| | | you must sell the stock before you can buy it. If no profit can be made, then |
| | | the answer should be 0. |
+------------------------------------+------------------------------------------------------------------------------------------+
| Minimum Path Sum in a Triangle | | You are given a 2D array of numbers (array of array of numbers) that represents a |
| | | triangle (the first array has one element, and each array has one more element than |
| | | the one before it, forming a triangle). Find the minimum path sum from the top to the |
| | | bottom of the triangle. In each step of the path, you may only move to adjacent |
| | | numbers in the row below. |
+------------------------------------+------------------------------------------------------------------------------------------+
| Unique Paths in a Grid I | | You are given an array with two numbers: [m, n]. These numbers represent a |
| | | m x n grid. Assume you are initially positioned in the top-left corner of that |
| | | grid and that you are trying to reach the bottom-right corner. On each step, |
| | | you may only move down or to the right. |
| | | |
| | |
| | | Determine how many unique paths there are from start to finish. |
+------------------------------------+------------------------------------------------------------------------------------------+
| Unique Paths in a Grid II | | You are given a 2D array of numbers (array of array of numbers) representing |
| | | a grid. The 2D array contains 1's and 0's, where 1 represents an obstacle and |
| | |
| | | 0 represents a free space. |
| | | |
| | | Assume you are initially positioned in top-left corner of that grid and that you |
| | | are trying to reach the bottom-right corner. In each step, you may only move down |
| | | or to the right. Furthermore, you cannot move onto spaces which have obstacles. |
| | | |
| | | Determine how many unique paths there are from start to finish. |
+------------------------------------+------------------------------------------------------------------------------------------+
| Sanitize Parentheses in Expression | | Given a string with parentheses and letters, remove the minimum number of invalid |
| | | parentheses in order to validate the string. If there are multiple minimal ways |
| | | to validate the string, provide all of the possible results. |
| | | |
| | | The answer should be provided as an array of strings. If it is impossible to validate |
| | | the string, the result should be an array with only an empty string. |
| | | |
| | | Examples: |
| | | ()())() -> ["()()()", "(())()"] |
| | | (a)())() -> ["(a)()()", "(a())()"] |
| | | )( -> [""] |
+------------------------------------+------------------------------------------------------------------------------------------+

@ -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:: 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 $ 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

@ -3,6 +3,28 @@
Changelog Changelog
========= =========
v0.43.1 - 2/11/2019
-------------------
* Terminal changes:
* 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 wasn't 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: Duplicate Sleeves now use their own stats to determine crime success rate, instead of the host consciousness' stats
* Bug Fix: You can now call the prompt() Netscript function from multiple scripts simultaneously
v0.43.0 - 2/4/2019 v0.43.0 - 2/4/2019
------------------ ------------------

@ -1211,6 +1211,26 @@ vsprintf
See `this link <https://github.com/alexei/sprintf.js>`_ for details. 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 prompt
^^^^^^ ^^^^^^

@ -312,6 +312,9 @@ getStockForecast
that the stock's price has a 30% chance of increasing and a 70% chance of that the stock's price has a 30% chance of increasing and a 70% chance of
decreasing during the next tick. decreasing during the next tick.
In order to use this function, you must first purchase access to the Four Sigma (4S)
Market Data TIX API.
purchase4SMarketData purchase4SMarketData
-------------------- --------------------

@ -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 Standard, Built-In JavaScript Objects
------------------------------------- -------------------------------------

@ -13,7 +13,6 @@
"ajv-keywords": "^2.0.0", "ajv-keywords": "^2.0.0",
"async": "^2.6.1", "async": "^2.6.1",
"autosize": "^4.0.2", "autosize": "^4.0.2",
"bluebird": "^3.5.1",
"brace": "^0.11.1", "brace": "^0.11.1",
"codemirror": "^5.43.0", "codemirror": "^5.43.0",
"decimal.js": "7.2.3", "decimal.js": "7.2.3",

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

@ -90,7 +90,10 @@ function removeAlias(name) {
//Aliases only applied to "whole words", one level deep //Aliases only applied to "whole words", one level deep
function substituteAliases(origCommand) { function substituteAliases(origCommand) {
var commandArray = origCommand.split(" "); var commandArray = origCommand.split(" ");
if (commandArray.length>0){ if (commandArray.length > 0){
// For the unalias command, dont substite
if (commandArray[0] === "unalias") { return commandArray.join(" "); }
var alias = getAlias(commandArray[0]); var alias = getAlias(commandArray[0]);
if (alias != null) { if (alias != null) {
commandArray[0] = alias; commandArray[0] = alias;

@ -78,6 +78,9 @@ export class Augmentation {
// The Player/Person classes // The Player/Person classes
mults: IMap<number> = {} 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 }) { constructor(params: IConstructorParams={ info: "", moneyCost: 0, name: "", repCost: 0 }) {
this.name = params.name; this.name = params.name;
this.info = params.info; this.info = params.info;
@ -85,6 +88,7 @@ export class Augmentation {
this.baseRepRequirement = params.repCost * CONSTANTS.AugmentationRepMultiplier * BitNodeMultipliers.AugmentationRepCost; this.baseRepRequirement = params.repCost * CONSTANTS.AugmentationRepMultiplier * BitNodeMultipliers.AugmentationRepCost;
this.baseCost = params.moneyCost * CONSTANTS.AugmentationCostMultiplier * BitNodeMultipliers.AugmentationMoneyCost; this.baseCost = params.moneyCost * CONSTANTS.AugmentationCostMultiplier * BitNodeMultipliers.AugmentationMoneyCost;
this.startingCost = this.baseCost;
this.level = 0; this.level = 0;

@ -364,6 +364,11 @@ function initBitNodeMultipliers() {
var inc = Math.pow(1.02, sf12Lvl); var inc = Math.pow(1.02, sf12Lvl);
var dec = 1/inc; var dec = 1/inc;
BitNodeMultipliers.HackingLevelMultiplier = dec; BitNodeMultipliers.HackingLevelMultiplier = dec;
BitNodeMultipliers.StrengthLevelMultiplier = dec;
BitNodeMultipliers.DefenseLevelMultiplier = dec;
BitNodeMultipliers.DexterityLevelMultiplier = dec;
BitNodeMultipliers.AgilityLevelMultiplier = dec;
BitNodeMultipliers.CharismaLevelMultiplier = dec;
BitNodeMultipliers.ServerMaxMoney = dec; BitNodeMultipliers.ServerMaxMoney = dec;
BitNodeMultipliers.ServerStartingMoney = dec; BitNodeMultipliers.ServerStartingMoney = dec;
@ -373,6 +378,10 @@ function initBitNodeMultipliers() {
//Does not scale, otherwise security might start at 300+ //Does not scale, otherwise security might start at 300+
BitNodeMultipliers.ServerStartingSecurity = 1.5; BitNodeMultipliers.ServerStartingSecurity = 1.5;
BitNodeMultipliers.PurchasedServerCost = inc;
BitNodeMultipliers.PurchasedServerLimit = dec;
BitNodeMultipliers.PurchasedServerMaxRam = dec;
BitNodeMultipliers.ManualHackMoney = dec; BitNodeMultipliers.ManualHackMoney = dec;
BitNodeMultipliers.ScriptHackMoney = dec; BitNodeMultipliers.ScriptHackMoney = dec;
BitNodeMultipliers.CompanyWorkMoney = dec; BitNodeMultipliers.CompanyWorkMoney = dec;

@ -156,7 +156,6 @@ $(document).keydown(function(event) {
if (!(Player.bladeburner instanceof Bladeburner)) {return;} if (!(Player.bladeburner instanceof Bladeburner)) {return;}
let consoleHistory = Player.bladeburner.consoleHistory; let consoleHistory = Player.bladeburner.consoleHistory;
//NOTE: Keycodes imported from Terminal.js
if (event.keyCode === KEY.ENTER) { if (event.keyCode === KEY.ENTER) {
event.preventDefault(); event.preventDefault();
var command = DomElems.consoleInput.value; var command = DomElems.consoleInput.value;
@ -355,6 +354,8 @@ function Skill(params={name:"foo", desc:"foo"}) {
if (params.effCha) {this.effCha = params.effCha;} if (params.effCha) {this.effCha = params.effCha;}
if (params.stamina) {this.stamina = params.stamina;} if (params.stamina) {this.stamina = params.stamina;}
if (params.money) {this.money = params.money;}
if (params.expGain) {this.expGain = params.expGain;}
//Equipment //Equipment
if (params.weaponAbility) {this.weaponAbility = params.weaponAbility;} if (params.weaponAbility) {this.weaponAbility = params.weaponAbility;}
@ -367,17 +368,19 @@ Skill.prototype.calculateCost = function(currentLevel) {
var Skills = {}; var Skills = {};
var SkillNames = { var SkillNames = {
BladesIntuition: "Blade's Intuition", BladesIntuition: "Blade's Intuition",
Reaper: "Reaper",
Cloak: "Cloak", Cloak: "Cloak",
Marksman: "Marksman", Marksman: "Marksman",
WeaponProficiency: "Weapon Proficiency", WeaponProficiency: "Weapon Proficiency",
Overclock: "Overclock",
EvasiveSystem: "Evasive System",
ShortCircuit: "Short-Circuit", ShortCircuit: "Short-Circuit",
DigitalObserver: "Digital Observer", DigitalObserver: "Digital Observer",
Datamancer: "Datamancer",
Tracer: "Tracer", Tracer: "Tracer",
CybersEdge: "Cyber's Edge" Overclock: "Overclock",
Reaper: "Reaper",
EvasiveSystem: "Evasive System",
Datamancer: "Datamancer",
CybersEdge: "Cyber's Edge",
HandsOfMidas: "Hands of Midas",
Hyperdrive: "Hyperdrive",
} }
//Base Class for Contracts, Operations, and BlackOps //Base Class for Contracts, Operations, and BlackOps
@ -998,23 +1001,25 @@ Bladeburner.prototype.getCurrentCity = function() {
Bladeburner.prototype.resetSkillMultipliers = function() { Bladeburner.prototype.resetSkillMultipliers = function() {
this.skillMultipliers = { this.skillMultipliers = {
successChanceAll:1, successChanceAll: 1,
successChanceStealth:1, successChanceStealth: 1,
successChanceKill:1, successChanceKill: 1,
successChanceContract:1, successChanceContract: 1,
successChanceOperation:1, successChanceOperation: 1,
successChanceEstimate:1, successChanceEstimate: 1,
actionTime:1, actionTime: 1,
effHack:1, effHack: 1,
effStr:1, effStr: 1,
effDef:1, effDef: 1,
effDex:1, effDex: 1,
effAgi:1, effAgi: 1,
effCha:1, effCha: 1,
effInt:1, effInt: 1,
stamina:1, stamina: 1,
weaponAbility:1, money: 1,
gunAbility:1, expGain: 1,
weaponAbility: 1,
gunAbility: 1,
}; };
} }
@ -1197,7 +1202,7 @@ Bladeburner.prototype.completeAction = function() {
//Earn money for contracts //Earn money for contracts
var moneyGain = 0; var moneyGain = 0;
if (!isOperation) { if (!isOperation) {
moneyGain = ContractBaseMoneyGain * rewardMultiplier; moneyGain = ContractBaseMoneyGain * rewardMultiplier * this.skillMultipliers.money;
Player.gainMoney(moneyGain); Player.gainMoney(moneyGain);
} }
@ -1526,13 +1531,14 @@ Bladeburner.prototype.gainActionStats = function(action, success) {
var unweightedGain = time * BaseStatGain * successMult * difficultyMult; var unweightedGain = time * BaseStatGain * successMult * difficultyMult;
var unweightedIntGain = time * BaseIntGain * successMult * difficultyMult; var unweightedIntGain = time * BaseIntGain * successMult * difficultyMult;
Player.gainHackingExp(unweightedGain * action.weights.hack * Player.hacking_exp_mult); const skillMult = this.skillMultipliers.expGain;
Player.gainStrengthExp(unweightedGain * action.weights.str * Player.strength_exp_mult); Player.gainHackingExp(unweightedGain * action.weights.hack * Player.hacking_exp_mult * skillMult);
Player.gainDefenseExp(unweightedGain * action.weights.def * Player.defense_exp_mult); Player.gainStrengthExp(unweightedGain * action.weights.str * Player.strength_exp_mult * skillMult);
Player.gainDexterityExp(unweightedGain * action.weights.dex * Player.dexterity_exp_mult); Player.gainDefenseExp(unweightedGain * action.weights.def * Player.defense_exp_mult * skillMult);
Player.gainAgilityExp(unweightedGain * action.weights.agi * Player.agility_exp_mult); Player.gainDexterityExp(unweightedGain * action.weights.dex * Player.dexterity_exp_mult * skillMult);
Player.gainCharismaExp(unweightedGain * action.weights.cha * Player.charisma_exp_mult); Player.gainAgilityExp(unweightedGain * action.weights.agi * Player.agility_exp_mult * skillMult);
Player.gainIntelligenceExp(unweightedIntGain * action.weights.int); Player.gainCharismaExp(unweightedGain * action.weights.cha * Player.charisma_exp_mult * skillMult);
Player.gainIntelligenceExp(unweightedIntGain * action.weights.int * skillMult);
} }
Bladeburner.prototype.randomEvent = function() { Bladeburner.prototype.randomEvent = function() {
@ -2193,6 +2199,12 @@ Bladeburner.prototype.createSkillsContent = function() {
case "stamina": case "stamina":
DomElems.actionsAndSkillsDesc.innerHTML += "Stamina: x" + mult + "<br>"; DomElems.actionsAndSkillsDesc.innerHTML += "Stamina: x" + mult + "<br>";
break; break;
case "money":
DomElems.actionsAndSkillsDesc.innerHTML += "Contract Money: x" + mult + "<br>";
break;
case "expGain":
DomElems.actionsAndSkillsDesc.innerHTML += "Exp Gain: x" + mult + "<br>";
break;
case "weaponAbility": case "weaponAbility":
//DomElems.actionsAndSkillsDesc.innerHTML += //DomElems.actionsAndSkillsDesc.innerHTML +=
break; break;
@ -2862,12 +2874,23 @@ Bladeburner.prototype.parseCommandArguments = function(command) {
//Returns an array with command and its arguments in each index. //Returns an array with command and its arguments in each index.
//e.g. skill "blade's intuition" foo returns [skill, blade's intuition, foo] //e.g. skill "blade's intuition" foo returns [skill, blade's intuition, foo]
//The input to this fn will be trimmed and will have all whitespace replaced w/ a single space //The input to this fn will be trimmed and will have all whitespace replaced w/ a single space
var args = []; const args = [];
var start = 0, i = 0; let start = 0, i = 0;
while (i < command.length) { while (i < command.length) {
var c = command.charAt(i); const c = command.charAt(i);
if (c === '"') { if (c === '"') { // Double quotes
var endQuote = command.indexOf('"', i+1); const endQuote = command.indexOf('"', i+1);
if (endQuote !== -1 && (endQuote === command.length-1 || command.charAt(endQuote+1) === " ")) {
args.push(command.substr(i+1, (endQuote - i - 1)));
if (endQuote === command.length-1) {
start = i = endQuote+1;
} else {
start = i = endQuote+2; //Skip the space
}
continue;
}
} else if (c === "'") { // Single quotes, same thing as above
const endQuote = command.indexOf("'", i+1);
if (endQuote !== -1 && (endQuote === command.length-1 || command.charAt(endQuote+1) === " ")) { if (endQuote !== -1 && (endQuote === command.length-1 || command.charAt(endQuote+1) === " ")) {
args.push(command.substr(i+1, (endQuote - i - 1))); args.push(command.substr(i+1, (endQuote - i - 1)));
if (endQuote === command.length-1) { if (endQuote === command.length-1) {
@ -2884,7 +2907,7 @@ Bladeburner.prototype.parseCommandArguments = function(command) {
++i; ++i;
} }
if (start !== i) {args.push(command.substr(start, i-start));} if (start !== i) {args.push(command.substr(start, i-start));}
console.log("Bladeburner.parseCommandArguments returned: " + args); console.log("Bladeburner console command parsing returned: " + args);
return args; return args;
} }
@ -3417,12 +3440,12 @@ Bladeburner.prototype.startActionNetscriptFn = function(type, name, workerScript
try { try {
this.startAction(actionId); this.startAction(actionId);
if (workerScript.shouldLog("startAction")) { if (workerScript.shouldLog("startAction")) {
workerScript.scriptRef.log("Starting bladeburner action with type " + type + " and name " + name); workerScript.log("Starting bladeburner action with type " + type + " and name " + name);
} }
return true; return true;
} catch(e) { } catch(e) {
this.resetAction(); this.resetAction();
workerScript.scriptRef.log("ERROR: bladeburner.startAction() failed to start action of type " + type + " due to invalid name: " + name + workerScript.log("ERROR: bladeburner.startAction() failed to start action of type " + type + " due to invalid name: " + name +
"Note that this name is case-sensitive and whitespace-sensitive"); "Note that this name is case-sensitive and whitespace-sensitive");
return false; return false;
} }
@ -3764,13 +3787,6 @@ function initBladeburner() {
baseCost:5, costInc:2, baseCost:5, costInc:2,
successChanceAll:3 successChanceAll:3
}); });
Skills[SkillNames.Reaper] = new Skill({
name:SkillNames.Reaper,
desc:"Each level of this skill increases your " +
"effective combat stats for Bladeburner actions by 3%",
baseCost:3, costInc:2,
effStr:3, effDef:3, effDex:3, effAgi:3
});
Skills[SkillNames.Cloak] = new Skill({ Skills[SkillNames.Cloak] = new Skill({
name:SkillNames.Cloak, name:SkillNames.Cloak,
desc:"Each level of this skill increases your " + desc:"Each level of this skill increases your " +
@ -3782,20 +3798,6 @@ function initBladeburner() {
//TODO Marksman //TODO Marksman
//TODO Weapon Proficiency //TODO Weapon Proficiency
Skills[SkillNames.Overclock] = new Skill({
name:SkillNames.Overclock,
desc:"Each level of this skill decreases the time it takes " +
"to attempt a Contract, Operation, and BlackOp by 1% (Max Level: 95)",
baseCost:5, costInc:1.1, maxLvl:95,
actionTime:1
});
Skills[SkillNames.EvasiveSystem] = new Skill({
name:SkillNames.EvasiveSystem,
desc:"Each level of this skill increases your effective " +
"dexterity and agility for Bladeburner actions by 5%",
baseCost:2, costInc: 1,
effDex:5, effAgi:5
});
Skills[SkillNames.ShortCircuit] = new Skill({ Skills[SkillNames.ShortCircuit] = new Skill({
name:SkillNames.ShortCircuit, name:SkillNames.ShortCircuit,
desc:"Each level of this skill increases your success chance " + desc:"Each level of this skill increases your success chance " +
@ -3810,15 +3812,6 @@ function initBladeburner() {
baseCost:5, costInc:2, baseCost:5, costInc:2,
successChanceOperation:4 successChanceOperation:4
}); });
Skills[SkillNames.Datamancer] = new Skill({
name:SkillNames.Datamancer,
desc:"Each level of this skill increases your effectiveness in " +
"synthoid population analysis and investigation by 5%. " +
"This affects all actions that can potentially increase " +
"the accuracy of your synthoid population/community estimates.",
baseCost:3,costInc:1,
successChanceEstimate:5
});
Skills[SkillNames.Tracer] = new Skill({ Skills[SkillNames.Tracer] = new Skill({
name:SkillNames.Tracer, name:SkillNames.Tracer,
desc:"Each level of this skill increases your success chance in " + desc:"Each level of this skill increases your success chance in " +
@ -3826,13 +3819,53 @@ function initBladeburner() {
baseCost:3, costInc:2, baseCost:3, costInc:2,
successChanceContract:4 successChanceContract:4
}); });
Skills[SkillNames.Overclock] = new Skill({
name:SkillNames.Overclock,
desc:"Each level of this skill decreases the time it takes " +
"to attempt a Contract, Operation, and BlackOp by 1% (Max Level: 95)",
baseCost:4, costInc:1.1, maxLvl:95,
actionTime:1
});
Skills[SkillNames.Reaper] = new Skill({
name:SkillNames.Reaper,
desc:"Each level of this skill increases your effective combat stats for Bladeburner actions by 3%",
baseCost:3, costInc:2,
effStr:3, effDef:3, effDex:3, effAgi:3
});
Skills[SkillNames.EvasiveSystem] = new Skill({
name:SkillNames.EvasiveSystem,
desc:"Each level of this skill increases your effective " +
"dexterity and agility for Bladeburner actions by 5%",
baseCost:2, costInc: 1,
effDex:5, effAgi:5
});
Skills[SkillNames.Datamancer] = new Skill({
name:SkillNames.Datamancer,
desc:"Each level of this skill increases your effectiveness in " +
"synthoid population analysis and investigation by 5%. " +
"This affects all actions that can potentially increase " +
"the accuracy of your synthoid population/community estimates.",
baseCost:3, costInc:1,
successChanceEstimate:5
});
Skills[SkillNames.CybersEdge] = new Skill({ Skills[SkillNames.CybersEdge] = new Skill({
name:SkillNames.CybersEdge, name:SkillNames.CybersEdge,
desc:"Each level of this skill increases your max " + desc:"Each level of this skill increases your max stamina by 2%",
"stamina by 2%",
baseCost:1, costInc:3, baseCost:1, costInc:3,
stamina:2 stamina:2
}); });
Skills[SkillNames.HandsOfMidas] = new Skill({
name: SkillNames.HandsOfMidas,
desc: "Each level of this skill increases the amount of money you receive from Contracts by 5%",
baseCost: 2, costInc: 2.5,
money: 5,
});
Skills[SkillNames.Hyperdrive] = new Skill({
name: SkillNames.Hyperdrive,
desc: "Each level of this skill increases the experience earned from Contracts, Operations, and BlackOps by 4%",
baseCost: 1, costInc: 3,
expGain: 4,
});
//General Actions //General Actions
var actionName = "Training"; var actionName = "Training";

@ -10,9 +10,7 @@ import { getRandomInt } from "../utils/helpers/getRandomInt";
export function generateRandomContract() { export function generateRandomContract() {
// First select a random problem type // First select a random problem type
const problemTypes = Object.keys(CodingContractTypes); let problemType = getRandomProblemType();
let randIndex = getRandomInt(0, problemTypes.length - 1);
let problemType = problemTypes[randIndex];
// Then select a random reward type. 'Money' will always be the last reward type // Then select a random reward type. 'Money' will always be the last reward type
const reward = getRandomReward(); const reward = getRandomReward();
@ -26,6 +24,22 @@ export function generateRandomContract() {
randServer.addContract(contract); randServer.addContract(contract);
} }
export function generateRandomContractOnHome() {
// First select a random problem type
let problemType = getRandomProblemType();
// Then select a random reward type. 'Money' will always be the last reward type
const reward = getRandomReward();
// Choose random server
const serv = Player.getHomeComputer();
let contractFn = getRandomFilename(serv, reward);
let contract = new CodingContract(contractFn, problemType, reward);
serv.addContract(contract);
}
export function generateContract(params) { export function generateContract(params) {
// Problem Type // Problem Type
let problemType; let problemType;
@ -33,8 +47,7 @@ export function generateContract(params) {
if (params.problemType != null && problemTypes.includes(params.problemType)) { if (params.problemType != null && problemTypes.includes(params.problemType)) {
problemType = params.problemType; problemType = params.problemType;
} else { } else {
let randIndex = getRandomInt(0, problemTypes.length - 1); problemType = getRandomProblemType();
problemType = problemTypes[randIndex];
} }
// Reward Type - This is always random for now // Reward Type - This is always random for now
@ -91,6 +104,13 @@ function sanitizeRewardType(rewardType) {
return type; return type;
} }
function getRandomProblemType() {
const problemTypes = Object.keys(CodingContractTypes);
let randIndex = getRandomInt(0, problemTypes.length - 1);
return problemTypes[randIndex];
}
function getRandomReward() { function getRandomReward() {
let reward = {}; let reward = {};
reward.type = getRandomInt(0, CodingContractRewardType.Money); reward.type = getRandomInt(0, CodingContractRewardType.Money);

@ -1,9 +1,17 @@
import { codingContractTypesMetadata,
DescriptionFunc,
GeneratorFunc,
SolverFunc } from "./data/codingcontracttypes";
import { IMap } from "./types";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
import { KEY } from "../utils/helpers/keyCodes";
import { createElement } from "../utils/uiHelpers/createElement"; import { createElement } from "../utils/uiHelpers/createElement";
import { createPopup } from "../utils/uiHelpers/createPopup"; import { createPopup } from "../utils/uiHelpers/createPopup";
import { removeElementById } from "../utils/uiHelpers/removeElementById"; import { removeElementById } from "../utils/uiHelpers/removeElementById";
import { codingContractTypesMetadata, DescriptionFunc, GeneratorFunc, SolverFunc } from "./data/codingcontracttypes";
import { IMap } from "./types";
/* tslint:disable:no-magic-numbers completed-docs max-classes-per-file no-console */ /* tslint:disable:no-magic-numbers completed-docs max-classes-per-file no-console */
@ -171,18 +179,22 @@ export class CodingContract {
const contractType: CodingContractType = CodingContractTypes[this.type]; const contractType: CodingContractType = CodingContractTypes[this.type];
const popupId: string = `coding-contract-prompt-popup-${this.fn}`; const popupId: string = `coding-contract-prompt-popup-${this.fn}`;
const txt: HTMLElement = createElement("p", { const txt: HTMLElement = createElement("p", {
innerText: ["You are attempting to solve a Coding Contract. You have", innerHTML: ["You are attempting to solve a Coding Contract. You have",
`${this.getMaxNumTries() - this.tries} tries remaining,`, `${this.getMaxNumTries() - this.tries} tries remaining,`,
"after which the contract will self-destruct.\n\n", "after which the contract will self-destruct.<br><br>",
`${contractType.desc(this.data)}`].join(" "), `${contractType.desc(this.data).replace(/\n/g, "<br>")}`].join(" "),
}); });
let answerInput: HTMLInputElement; let answerInput: HTMLInputElement;
let solveBtn: HTMLElement; let solveBtn: HTMLElement;
let cancelBtn: HTMLElement;
answerInput = createElement("input", { answerInput = createElement("input", {
onkeydown: (e: any) => { onkeydown: (e: any) => {
if (e.keyCode === 13 && answerInput.value !== "") { if (e.keyCode === KEY.ENTER && answerInput.value !== "") {
e.preventDefault(); e.preventDefault();
solveBtn.click(); solveBtn.click();
} else if (e.keyCode === KEY.ESC) {
e.preventDefault();
cancelBtn.click();
} }
}, },
placeholder: "Enter Solution here", placeholder: "Enter Solution here",
@ -200,7 +212,7 @@ export class CodingContract {
}, },
innerText: "Solve", innerText: "Solve",
}); });
const cancelBtn: HTMLElement = createElement("a", { cancelBtn = createElement("a", {
class: "a-link-button", class: "a-link-button",
clickListener: () => { clickListener: () => {
resolve(CodingContractResult.Cancelled); resolve(CodingContractResult.Cancelled);

@ -8,6 +8,7 @@ import { IMap } from "../../types";
const AllSoftwarePositions: IMap<boolean> = {}; const AllSoftwarePositions: IMap<boolean> = {};
const AllITPositions: IMap<boolean> = {}; const AllITPositions: IMap<boolean> = {};
const AllNetworkEngineerPositions: IMap<boolean> = {}; const AllNetworkEngineerPositions: IMap<boolean> = {};
const SecurityEngineerPositions: IMap<boolean> = {};
const AllTechnologyPositions: IMap<boolean> = {}; const AllTechnologyPositions: IMap<boolean> = {};
const AllBusinessPositions: IMap<boolean> = {}; const AllBusinessPositions: IMap<boolean> = {};
const AllAgentPositions: IMap<boolean> = {}; const AllAgentPositions: IMap<boolean> = {};
@ -40,6 +41,7 @@ posNames.NetworkEngineerCompanyPositions.forEach((e) => {
}); });
AllTechnologyPositions[posNames.SecurityEngineerCompanyPositions[0]] = true; AllTechnologyPositions[posNames.SecurityEngineerCompanyPositions[0]] = true;
SecurityEngineerPositions[posNames.SecurityEngineerCompanyPositions[0]] = true;
posNames.BusinessCompanyPositions.forEach((e) => { posNames.BusinessCompanyPositions.forEach((e) => {
AllBusinessPositions[e] = true; AllBusinessPositions[e] = true;
@ -373,6 +375,7 @@ export const companiesMetadata: IConstructorParams[] = [
companyPositions: Object.assign({}, companyPositions: Object.assign({},
SoftwarePositionsUpToHeadOfEngineering, SoftwarePositionsUpToHeadOfEngineering,
AllNetworkEngineerPositions, AllNetworkEngineerPositions,
SecurityEngineerPositions,
AllITPositions, AllITPositions,
AllSecurityPositions, AllSecurityPositions,
AllAgentPositions AllAgentPositions
@ -387,6 +390,7 @@ export const companiesMetadata: IConstructorParams[] = [
companyPositions: Object.assign({}, companyPositions: Object.assign({},
SoftwarePositionsUpToHeadOfEngineering, SoftwarePositionsUpToHeadOfEngineering,
AllNetworkEngineerPositions, AllNetworkEngineerPositions,
SecurityEngineerPositions,
AllITPositions, AllITPositions,
AllSecurityPositions, AllSecurityPositions,
AllAgentPositions AllAgentPositions
@ -492,8 +496,9 @@ export const companiesMetadata: IConstructorParams[] = [
info: "", info: "",
companyPositions: Object.assign({}, companyPositions: Object.assign({},
AllTechnologyPositions, AllTechnologyPositions,
AllSoftwareConsultantPositions,
AllAgentPositions, AllAgentPositions,
AllSecurityPositions AllSecurityPositions,
), ),
expMultiplier: 1.2, expMultiplier: 1.2,
salaryMultiplier: 1.2, salaryMultiplier: 1.2,

@ -1,7 +1,7 @@
import {IMap} from "./types"; import {IMap} from "./types";
export let CONSTANTS: IMap<any> = { export let CONSTANTS: IMap<any> = {
Version: "0.43.0", Version: "0.43.1",
//Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience //Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience
//and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then //and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then
@ -510,27 +510,25 @@ export let CONSTANTS: IMap<any> = {
LatestUpdate: LatestUpdate:
` `
v0.43.0 v0.43.1
* Added BitNode-10: Digital Carbon * Terminal changes:
** 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
* Stock Market Changes: * Added two new Bladeburner skills for increasing money and experience gain
** Each stock now has a maximum number of shares you can purchase (both Long and Short positions combined) * Made some minor adjustments to Bladeburner UI
** Added getStockMaxShares() Netscript function to the TIX API * Corporation "Smart Factories" and "Smart Storage" upgrades have slightly lower price multipliers
** The cost of 4S Market Data TIX API Access increased from $20b to $25b * Added nFormat Netscript function
* Added 6 new Coding Contract problems
* Job Changes: * Updated documentation with list of all Coding Contract problems
** You can now hold multiple jobs at once. This means you no longer lose reputation when leaving a company * Minor improvements for 'Active Scripts' UI
** Because of this change, the getCharacterInformation() Netscript function returns a slightly different value * 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%)
* Script Editor Changes: * Bug Fix: The cost of Resleeves should no longer be affected by buying Augs
** Added new script editor: CodeMirror. You can choose between the old editor (Ace) or CodeMirror * Bug Fix: Duplicate Sleeves now use their own stats to determine crime success rate, instead of the host consciousness' stats
** Navigation keyboard shortcuts no longer work if the script editor is focused * Bug Fix: You can now call the prompt() Netscript function from multiple scripts simultaneously
* Trying to programmatically run a script (run(), exec()) with a 'threads' argument of 0 will now cause the function to return false without running the script
* Home Computer RAM is now capped at 2 ^ 30 GB (1073741824 GB)
* The maximum amount, maximum RAM, and cost of purchasing servers can now vary between different BitNodes (new BitNode multipliers)
* Pop-up dialog boxes are a little bit bigger
* Bug Fix: When importing scripts, "./" will now be properly ignored (e.g. import { foo } from "./lib.script" )
` `
} }

@ -6,12 +6,12 @@ import { IMap } from "../../types";
// [index in Corporation upgrades array, base price, price mult, benefit mult (additive), name, desc] // [index in Corporation upgrades array, base price, price mult, benefit mult (additive), name, desc]
export const CorporationUpgrades: IMap<any[]> = { export const CorporationUpgrades: IMap<any[]> = {
//Smart factories, increases production //Smart factories, increases production
"0": [0, 2e9, 1.07, 0.03, "0": [0, 2e9, 1.06, 0.03,
"Smart Factories", "Advanced AI automatically optimizes the operation and productivity " + "Smart Factories", "Advanced AI automatically optimizes the operation and productivity " +
"of factories. Each level of this upgrade increases your global production by 3% (additive)."], "of factories. Each level of this upgrade increases your global production by 3% (additive)."],
//Smart warehouses, increases storage size //Smart warehouses, increases storage size
"1": [1, 2e9, 1.07, .1, "1": [1, 2e9, 1.06, .1,
"Smart Storage", "Advanced AI automatically optimizes your warehouse storage methods. " + "Smart Storage", "Advanced AI automatically optimizes your warehouse storage methods. " +
"Each level of this upgrade increases your global warehouse storage size by 10% (additive)."], "Each level of this upgrade increases your global warehouse storage size by 10% (additive)."],

@ -1,4 +1,7 @@
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../Constants";
import { IPlayer } from "../PersonObjects/IPlayer";
import { IPlayerOrSleeve } from "../PersonObjects/IPlayerOrSleeve";
export interface IConstructorParams { export interface IConstructorParams {
hacking_success_weight?: number; hacking_success_weight?: number;
strength_success_weight?: number; strength_success_weight?: number;
@ -17,29 +20,6 @@ export interface IConstructorParams {
kills?: number; kills?: number;
} }
interface IPlayer {
startCrime(crimeType: string,
hackExp: number,
strExp: number,
defExp: number,
dexExp: number,
agiExp: number,
chaExp: number,
money: number,
time: number,
singParams: any): void;
hacking_skill: number;
strength: number;
defense: number;
dexterity: number;
agility: number;
charisma: number;
intelligence: number;
crime_success_mult: number;
}
export class Crime { export class Crime {
// Number representing the difficulty of the crime. Used for success chance calculations // Number representing the difficulty of the crime. Used for success chance calculations
difficulty: number = 0; difficulty: number = 0;
@ -129,7 +109,7 @@ export class Crime {
return this.time; return this.time;
} }
successRate(p: IPlayer): number { successRate(p: IPlayerOrSleeve): number {
let chance: number = (this.hacking_success_weight * p.hacking_skill + let chance: number = (this.hacking_success_weight * p.hacking_skill +
this.strength_success_weight * p.strength + this.strength_success_weight * p.strength +
this.defense_success_weight * p.defense + this.defense_success_weight * p.defense +

@ -1,23 +1,26 @@
import { AugmentationNames } from "./Augmentation/data/AugmentationNames"; import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
import { generateRandomContract } from "./CodingContractGenerator"; import { CodingContractTypes } from "./CodingContracts";
import { Programs } from "./Programs/Programs"; import { generateContract,
import { Factions } from "./Faction/Factions"; generateRandomContract,
import { Player } from "./Player"; generateRandomContractOnHome } from "./CodingContractGenerator";
import { AllServers } from "./Server"; import { Programs } from "./Programs/Programs";
import { hackWorldDaemon } from "./RedPill"; import { Factions } from "./Faction/Factions";
import { Player } from "./Player";
import { AllServers } from "./Server";
import { hackWorldDaemon } from "./RedPill";
import { StockMarket, import { StockMarket,
SymbolToStockMap } from "./StockMarket/StockMarket"; SymbolToStockMap } from "./StockMarket/StockMarket";
import { Stock } from "./StockMarket/Stock"; import { Stock } from "./StockMarket/Stock";
import { Terminal } from "./Terminal"; import { Terminal } from "./Terminal";
import { numeralWrapper } from "./ui/numeralFormat"; import { numeralWrapper } from "./ui/numeralFormat";
import { dialogBoxCreate } from "../utils/DialogBox"; import { dialogBoxCreate } from "../utils/DialogBox";
import { exceptionAlert } from "../utils/helpers/exceptionAlert"; import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { createElement } from "../utils/uiHelpers/createElement"; import { createElement } from "../utils/uiHelpers/createElement";
import { createOptionElement } from "../utils/uiHelpers/createOptionElement"; import { createOptionElement } from "../utils/uiHelpers/createOptionElement";
import { getSelectText } from "../utils/uiHelpers/getSelectData"; import { getSelectText } from "../utils/uiHelpers/getSelectData";
import { removeElementById } from "../utils/uiHelpers/removeElementById"; import { removeElementById } from "../utils/uiHelpers/removeElementById";
const devMenuContainerId = "dev-menu-container"; const devMenuContainerId = "dev-menu-container";
@ -226,6 +229,24 @@ export function createDevMenu() {
innerText: "Receive Invite to Faction", innerText: "Receive Invite to Faction",
}); });
const factionsReputationInput = createElement("input", {
placeholder: "Rep to add to faction",
type: "number",
});
const factionsReputationButton = createElement("button", {
class: "std-button",
innerText: "Add rep to faction",
clickListener: () => {
const facName = getSelectText(factionsDropdown);
const fac = Factions[facName];
const rep = parseFloat(factionsReputationInput.value);
if (fac != null && !isNaN(rep)) {
fac.playerReputation += rep;
}
},
});
// Augmentations // Augmentations
const augmentationsHeader = createElement("h2", {innerText: "Augmentations"}); const augmentationsHeader = createElement("h2", {innerText: "Augmentations"});
@ -429,6 +450,31 @@ export function createDevMenu() {
innerText: "Generate Random Contract", innerText: "Generate Random Contract",
}); });
const generateRandomContractOnHomeBtn = createElement("button", {
class: "std-button",
clickListener: () => {
generateRandomContractOnHome();
},
innerText: "Generate Random Contract on Home Comp",
});
const generateContractWithTypeSelector = createElement("select", { margin: "5px" });
const contractTypes = Object.keys(CodingContractTypes);
for (let i = 0; i < contractTypes.length; ++i) {
generateContractWithTypeSelector.add(createOptionElement(contractTypes[i]));
}
const generateContractWithTypeBtn = createElement("button", {
class: "std-button",
clickListener: () => {
generateContract({
problemType: getSelectText(generateContractWithTypeSelector),
server: "home",
});
},
innerText: "Generate Specified Contract Type on Home Comp",
});
// Stock Market // Stock Market
const stockmarketHeader = createElement("h2", {innerText: "Stock Market"}); const stockmarketHeader = createElement("h2", {innerText: "Stock Market"});
@ -535,6 +581,9 @@ export function createDevMenu() {
devMenuContainer.appendChild(factionsHeader); devMenuContainer.appendChild(factionsHeader);
devMenuContainer.appendChild(factionsDropdown); devMenuContainer.appendChild(factionsDropdown);
devMenuContainer.appendChild(factionsAddButton); devMenuContainer.appendChild(factionsAddButton);
devMenuContainer.appendChild(createElement("br"));
devMenuContainer.appendChild(factionsReputationInput);
devMenuContainer.appendChild(factionsReputationButton);
devMenuContainer.appendChild(augmentationsHeader); devMenuContainer.appendChild(augmentationsHeader);
devMenuContainer.appendChild(augmentationsDropdown); devMenuContainer.appendChild(augmentationsDropdown);
devMenuContainer.appendChild(augmentationsQueueButton); devMenuContainer.appendChild(augmentationsQueueButton);
@ -563,6 +612,10 @@ export function createDevMenu() {
devMenuContainer.appendChild(createElement("br")); devMenuContainer.appendChild(createElement("br"));
devMenuContainer.appendChild(contractsHeader); devMenuContainer.appendChild(contractsHeader);
devMenuContainer.appendChild(generateRandomContractBtn); devMenuContainer.appendChild(generateRandomContractBtn);
devMenuContainer.appendChild(generateRandomContractOnHomeBtn);
devMenuContainer.appendChild(createElement("br"));
devMenuContainer.appendChild(generateContractWithTypeSelector);
devMenuContainer.appendChild(generateContractWithTypeBtn);
devMenuContainer.appendChild(stockmarketHeader); devMenuContainer.appendChild(stockmarketHeader);
devMenuContainer.appendChild(stockInput); devMenuContainer.appendChild(stockInput);
devMenuContainer.appendChild(stockPriceChangeInput); devMenuContainer.appendChild(stockPriceChangeInput);

@ -33,7 +33,7 @@ export const TerminalHelpText: string =
"tail [script] [args...] Displays dynamic logs for the specified script<br>" + "tail [script] [args...] Displays dynamic logs for the specified script<br>" +
"theme [preset] | bg txt hlgt Change the color scheme of the UI<br>" + "theme [preset] | bg txt hlgt Change the color scheme of the UI<br>" +
"top Displays all running scripts and their RAM usage<br>" + "top Displays all running scripts and their RAM usage<br>" +
'unalias "[alias name]" Deletes the specified alias<br>' + 'unalias [alias name] Deletes the specified alias<br>' +
"wget [url] [target file] Retrieves code/text from a web server<br>"; "wget [url] [target file] Retrieves code/text from a web server<br>";
interface IMap<T> { interface IMap<T> {
@ -215,12 +215,12 @@ export const HelpTexts: IMap<string> = {
top: "top<br>" + top: "top<br>" +
"Prints a list of all scripts running on the current server as well as their thread count and how much " + "Prints a list of all scripts running on the current server as well as their thread count and how much " +
"RAM they are using in total.", "RAM they are using in total.",
unalias: 'unalias "[alias name]"<br>' + unalias: 'unalias [alias name]<br>' +
"Deletes the specified alias. Note that the double quotation marks are required. <br><br>" + "Deletes the specified alias. Note that the double quotation marks are required. <br><br>" +
"As an example, if an alias was declared using:<br><br>" + "As an example, if an alias was declared using:<br><br>" +
'alias r="run"<br><br>' + 'alias r="run"<br><br>' +
"Then it could be removed using:<br><br>" + "Then it could be removed using:<br><br>" +
'unalias "r"<br><br>' + 'unalias r<br><br>' +
"It is not necessary to differentiate between global and non-global aliases when using 'unalias'", "It is not necessary to differentiate between global and non-global aliases when using 'unalias'",
wget: "wget [url] [target file]<br>" + wget: "wget [url] [target file]<br>" +
"Retrieves data from a URL and downloads it to a file on the current server. The data can only " + "Retrieves data from a URL and downloads it to a file on the current server. The data can only " +

@ -24,8 +24,8 @@ function initLiterature() {
title = "The Beginner's Guide to Hacking"; title = "The Beginner's Guide to Hacking";
fn = "hackers-starting-handbook.lit"; fn = "hackers-starting-handbook.lit";
txt = "Some resources:<br><br>" + txt = "Some resources:<br><br>" +
"<a class='a-link-button' href='https://bitburner.readthedocs.io/en/latest/netscriptlearntoprogram.html' target='_blank' style='margin:4px'>Learn to Program</a><br><br>" + "<a class='a-link-button' href='https://bitburner.readthedocs.io/en/latest/netscript/netscriptlearntoprogram.html' target='_blank' style='margin:4px'>Learn to Program</a><br><br>" +
"<a class='a-link-button' href='https://bitburner.readthedocs.io/en/latest/netscriptjs.html' target='_blank' style='margin:4px'>For Experienced JavaScript Developers: NetscriptJS</a><br><br>" + "<a class='a-link-button' href='https://bitburner.readthedocs.io/en/latest/netscript/netscriptjs.html' target='_blank' style='margin:4px'>For Experienced JavaScript Developers: NetscriptJS</a><br><br>" +
"<a class='a-link-button' href='https://bitburner.readthedocs.io/en/latest/netscript.html' target='_blank' style='margin:4px'>Netscript Documentation</a><br><br>" + "<a class='a-link-button' href='https://bitburner.readthedocs.io/en/latest/netscript.html' target='_blank' style='margin:4px'>Netscript Documentation</a><br><br>" +
"When starting out, hacking is the most profitable way to earn money and progress. This " + "When starting out, hacking is the most profitable way to earn money and progress. This " +
"is a brief collection of tips/pointers on how to make the most out of your hacking scripts.<br><br>" + "is a brief collection of tips/pointers on how to make the most out of your hacking scripts.<br><br>" +
@ -78,7 +78,7 @@ function initLiterature() {
"find investors. Instead, your Corporation will be publicly traded and its stock price will change based on how well " + "find investors. Instead, your Corporation will be publicly traded and its stock price will change based on how well " +
"it's performing financially. You can then sell your stock shares in order to make money.<br><br>" + "it's performing financially. You can then sell your stock shares in order to make money.<br><br>" +
"<u>Tips/Pointers</u><br>" + "<u>Tips/Pointers</u><br>" +
"-The 'Smart Supply' upgrade is extremely useful. Consider purchasing it as soon as possible.<br><br>" + "-The 'Smart Supply' upgrade is extremely useful. Consider purchasing it as soon as possible.<br><br>" +
"-Purchasing Hardware, Robots, AI Cores, and Real Estate can potentially increase your production. " + "-Purchasing Hardware, Robots, AI Cores, and Real Estate can potentially increase your production. " +
"The effects of these depend on what industry you are in.<br><br>" + "The effects of these depend on what industry you are in.<br><br>" +
"-In order to optimize your production, you will need a good balance of Operators, Managers, and Engineers<br><br>" + "-In order to optimize your production, you will need a good balance of Operators, Managers, and Engineers<br><br>" +

@ -13,10 +13,10 @@ function unknownBladeburnerExceptionMessage(functionName, err) {
} }
function checkBladeburnerAccess(workerScript, functionName) { function checkBladeburnerAccess(workerScript, functionName) {
const accessDenied = `${functionName}() failed because you do not` + const accessDenied = `${functionName}() failed because you do not ` +
" currently have access to the Bladeburner API. This is either" + "currently have access to the Bladeburner API. To access the Bladeburner API" +
" because you are not currently employed at the Bladeburner division" + "you must be employed at the Bladeburner division, AND you must either be in " +
" or because you do not have Source-File 7"; "BitNode-7 or have Source-File 7.";
const hasAccess = Player.bladeburner instanceof Bladeburner && (Player.bitNodeN === 7 || Player.sourceFiles.some(a=>{return a.n === 7})); const hasAccess = Player.bladeburner instanceof Bladeburner && (Player.bitNodeN === 7 || Player.sourceFiles.some(a=>{return a.n === 7}));
if(!hasAccess) { if(!hasAccess) {
throw makeRuntimeRejectMsg(workerScript, accessDenied); throw makeRuntimeRejectMsg(workerScript, accessDenied);

@ -13,692 +13,7 @@ import {arrayToString} from "../utils/helpers/arrayToString
import {isValidIPAddress} from "../utils/helpers/isValidIPAddress"; import {isValidIPAddress} from "../utils/helpers/isValidIPAddress";
import {isString} from "../utils/helpers/isString"; import {isString} from "../utils/helpers/isString";
var Promise = require("bluebird"); export function evaluateImport(exp, workerScript, checkingRam=false) {
Promise.config({
warnings: false,
longStackTraces: false,
cancellation: true,
monitoring: false
});
/* Evaluator
* Evaluates/Interprets the Abstract Syntax Tree generated by Acorns parser
*
* Returns a promise
*/
function evaluate(exp, workerScript) {
return Promise.delay(Settings.CodeInstructionRunTime).then(function() {
var env = workerScript.env;
if (env.stopFlag) {return Promise.reject(workerScript);}
if (exp == null) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Error: NULL expression", exp));
}
if (env.stopFlag) {return Promise.reject(workerScript);}
switch (exp.type) {
case "BlockStatement":
case "Program":
var evaluateProgPromise = evaluateProg(exp, workerScript, 0); //TODO: make every block/program use individual enviroment
return evaluateProgPromise.then(function(w) {
return Promise.resolve(workerScript);
}).catch(function(e) {
if (e.constructor === Array && e.length === 2 && e[0] === "RETURNSTATEMENT") {
return Promise.reject(e);
} else if (isString(e)) {
workerScript.errorMessage = e;
return Promise.reject(workerScript);
} else if (e instanceof WorkerScript) {
return Promise.reject(e);
} else {
return Promise.reject(workerScript);
}
});
break;
case "Literal":
return Promise.resolve(exp.value);
break;
case "Identifier":
//Javascript constructor() method can be used as an exploit to run arbitrary code
if (exp.name == "constructor") {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Illegal usage of constructor() method. If you have your own function named 'constructor', you must re-name it.", exp));
}
if (!(exp.name in env.vars)){
return Promise.reject(makeRuntimeRejectMsg(workerScript, "variable " + exp.name + " not defined", exp));
}
return Promise.resolve(env.get(exp.name))
break;
case "ExpressionStatement":
return evaluate(exp.expression, workerScript);
break;
case "ArrayExpression":
var argPromises = exp.elements.map(function(arg) {
return evaluate(arg, workerScript);
});
return Promise.all(argPromises).then(function(array) {
return Promise.resolve(array)
});
break;
case "CallExpression":
return evaluate(exp.callee, workerScript).then(function(func) {
return Promise.map(exp.arguments, function(arg) {
return evaluate(arg, workerScript);
}).then(function(args) {
if (func instanceof Node) { //Player-defined function
//Create new Environment for the function
//Should be automatically garbage collected...
var funcEnv = env.extend();
//Define function arguments in this new environment
for (var i = 0; i < func.params.length; ++i) {
var arg;
if (i >= args.length) {
arg = null;
} else {
arg = args[i];
}
funcEnv.def(func.params[i].name, arg);
}
//Create a new WorkerScript for this function evaluation
var funcWorkerScript = new WorkerScript(workerScript.scriptRef);
funcWorkerScript.serverIp = workerScript.serverIp;
funcWorkerScript.env = funcEnv;
workerScript.fnWorker = funcWorkerScript;
return evaluate(func.body, funcWorkerScript).then(function(res) {
//If the function finished successfuly, that means there
//was no return statement since a return statement rejects. So resolve to null
workerScript.fnWorker = null;
return Promise.resolve(null);
}).catch(function(e) {
if (e.constructor === Array && e.length === 2 && e[0] === "RETURNSTATEMENT") {
//Return statement from function
return Promise.resolve(e[1]);
workerScript.fnWorker = null;
} else if (isString(e)) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, e));
} else if (e instanceof WorkerScript) {
//Parse out the err message from the WorkerScript and re-reject
var errorMsg = e.errorMessage;
var errorTextArray = errorMsg.split("|");
if (errorTextArray.length === 4) {
errorMsg = errorTextArray[3];
return Promise.reject(makeRuntimeRejectMsg(workerScript, errorMsg));
} else {
if (env.stopFlag) {
return Promise.reject(workerScript);
} else {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Error in one of your functions. Could not identify which function"));
}
}
} else if (e instanceof Error) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, e.toString()));
}
});
} else if (exp.callee.type === "MemberExpression"){
return evaluate(exp.callee.object, workerScript).then(function(object) {
try {
if (func === "NETSCRIPTFOREACH") {
return evaluateForeach(object, args, workerScript).then(function(res) {
return Promise.resolve(res);
}).catch(function(e) {
return Promise.reject(e);
});
}
var res = func.apply(object,args);
return Promise.resolve(res);
} catch (e) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, e, exp));
}
});
} else {
try {
var out = func.apply(null,args);
if (out instanceof Promise){
return out.then(function(res) {
return Promise.resolve(res)
}).catch(function(e) {
if (isScriptErrorMessage(e)) {
//Functions don't have line number appended in error message, so add it
var num = getErrorLineNumber(exp, workerScript);
e += " (Line " + num + ")";
}
return Promise.reject(e);
});
} else {
return Promise.resolve(out);
}
} catch (e) {
if (isScriptErrorMessage(e)) {
if (isScriptErrorMessage(e)) {
//Functions don't have line number appended in error message, so add it
var num = getErrorLineNumber(exp, workerScript);
e += " (Line " + num + ")";
}
return Promise.reject(e);
} else {
return Promise.reject(makeRuntimeRejectMsg(workerScript, e, exp));
}
}
}
});
});
break;
case "MemberExpression":
return evaluate(exp.object, workerScript).then(function(object) {
if (exp.computed){
return evaluate(exp.property, workerScript).then(function(index) {
if (index >= object.length) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Invalid index for arrays", exp));
}
return Promise.resolve(object[index]);
}).catch(function(e) {
if (e instanceof WorkerScript || isScriptErrorMessage(e)) {
return Promise.reject(e);
} else {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Invalid MemberExpression", exp));
}
});
} else {
if (exp.property.name === "constructor") {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Illegal usage of constructor() method. If you have your own function named 'constructor', you must re-name it.", exp));
}
if (object != null && object instanceof Array && exp.property.name === "forEach") {
return "NETSCRIPTFOREACH";
}
try {
return Promise.resolve(object[exp.property.name])
} catch (e) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Failed to get property: " + e.toString(), exp));
}
}
});
break;
case "LogicalExpression":
case "BinaryExpression":
return evalBinary(exp, workerScript);
break;
case "UnaryExpression":
return evalUnary(exp, workerScript);
break;
case "AssignmentExpression":
return evalAssignment(exp, workerScript);
break;
case "VariableDeclaration":
return evalVariableDeclaration(exp, workerScript);
break;
case "UpdateExpression":
if (exp.argument.type==="Identifier"){
if (exp.argument.name in env.vars){
if (exp.operator === "++" || exp.operator === "--") {
switch (exp.operator) {
case "++":
env.set(exp.argument.name,env.get(exp.argument.name)+1);
break;
case "--":
env.set(exp.argument.name,env.get(exp.argument.name)-1);
break;
default: break;
}
return Promise.resolve(env.get(exp.argument.name));
}
//Not sure what prefix UpdateExpressions there would be besides ++/--
if (exp.prefix){
return Promise.resolve(env.get(exp.argument.name))
}
switch (exp.operator){
default:
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Unrecognized token: " + exp.type + ". You are trying to use code that is currently unsupported", exp));
}
return Promise.resolve(env.get(exp.argument.name))
} else {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "variable " + exp.argument.name + " not defined", exp));
}
} else {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "argument must be an identifier", exp));
}
break;
case "EmptyStatement":
return Promise.resolve(false);
break;
case "ReturnStatement":
return evaluate(exp.argument, workerScript).then(function(res) {
return Promise.reject(["RETURNSTATEMENT", res]);
});
break;
case "BreakStatement":
return Promise.reject("BREAKSTATEMENT");
break;
case "ContinueStatement":
return Promise.reject("CONTINUESTATEMENT");
break;
case "IfStatement":
return evaluateIf(exp, workerScript);
break;
case "SwitchStatement":
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Switch statements are not yet implemented in Netscript", exp));
break;
case "WhileStatement":
return evaluateWhile(exp, workerScript).then(function(res) {
return Promise.resolve(res);
}).catch(function(e) {
if (e == "BREAKSTATEMENT" ||
(e instanceof WorkerScript && e.errorMessage == "BREAKSTATEMENT")) {
return Promise.resolve("whileLoopBroken");
} else {
return Promise.reject(e);
}
});
break;
case "ForStatement":
return evaluate(exp.init, workerScript).then(function(expInit) {
return evaluateFor(exp, workerScript);
}).then(function(forLoopRes) {
return Promise.resolve("forLoopDone");
}).catch(function(e) {
if (e == "BREAKSTATEMENT" ||
(e instanceof WorkerScript && e.errorMessage == "BREAKSTATEMENT")) {
return Promise.resolve("forLoopBroken");
} else {
return Promise.reject(e);
}
});
break;
case "FunctionDeclaration":
if (exp.id && exp.id.name) {
env.set(exp.id.name, exp);
return Promise.resolve(true);
} else {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Invalid function declaration", exp));
}
break;
case "ImportDeclaration":
return evaluateImport(exp, workerScript).then(function(res) {
return Promise.resolve(res);
}).catch(function(e) {
return Promise.reject(e);
});
break;
case "ThrowStatement":
return evaluate(exp.argument, workerScript).then(function(res) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, res));
});
break;
default:
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Unrecognized token: " + exp.type + ". This is currently unsupported in Netscript", exp));
break;
} //End switch
}).catch(function(e) {
return Promise.reject(e);
}); // End Promise
}
function evalBinary(exp, workerScript){
return evaluate(exp.left, workerScript).then(function(expLeft) {
//Short circuiting
if (expLeft && exp.operator === "||") {
return Promise.resolve(expLeft);
}
if (!expLeft && exp.operator === "&&") {
return Promise.resolve(expLeft);
}
return evaluate(exp.right, workerScript).then(function(expRight) {
switch (exp.operator){
case "===":
case "==":
return Promise.resolve(expLeft===expRight);
break;
case "!==":
case "!=":
return Promise.resolve(expLeft!==expRight);
break;
case "<":
return Promise.resolve(expLeft<expRight);
break;
case "<=":
return Promise.resolve(expLeft<=expRight);
break;
case ">":
return Promise.resolve(expLeft>expRight);
break;
case ">=":
return Promise.resolve(expLeft>=expRight);
break;
case "+":
return Promise.resolve(expLeft+expRight);
break;
case "-":
return Promise.resolve(expLeft-expRight);
break;
case "*":
return Promise.resolve(expLeft*expRight);
break;
case "/":
if (expRight === 0) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "ERROR: Divide by zero"));
} else {
return Promise.resolve(expLeft/expRight);
}
break;
case "%":
return Promise.resolve(expLeft%expRight);
break;
case "in":
return Promise.resolve(expLeft in expRight);
break;
case "instanceof":
return Promise.resolve(expLeft instanceof expRight);
break;
case "||":
return Promise.resolve(expLeft || expRight);
break;
case "&&":
return Promise.resolve(expLeft && expRight);
break;
default:
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Unsupported operator: " + exp.operator));
}
});
});
}
function evalUnary(exp, workerScript){
var env = workerScript.env;
if (env.stopFlag) {return Promise.reject(workerScript);}
return evaluate(exp.argument, workerScript).then(function(res) {
if (exp.operator == "!") {
return Promise.resolve(!res);
} else if (exp.operator == "-") {
if (isNaN(res)) {
return Promise.resolve(res);
} else {
return Promise.resolve(-1 * res);
}
} else {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Unimplemented unary operator: " + exp.operator));
}
});
}
//Takes in a MemberExpression that should represent a Netscript array (possible multidimensional)
//The return value is an array of the form:
// [0th index (leftmost), array name, 1st index, 2nd index, ...]
function getArrayElement(exp, workerScript) {
var indices = [];
return evaluate(exp.property, workerScript).then(function(idx) {
if (isNaN(idx)) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Invalid access to array. Index is not a number: " + idx));
} else {
if (exp.object.name === undefined && exp.object.object) {
return getArrayElement(exp.object, workerScript).then(function(res) {
res.push(idx);
indices = res;
return Promise.resolve(indices);
}).catch(function(e) {
return Promise.reject(e);
});
} else {
indices.push(idx);
indices.push(exp.object.name);
return Promise.resolve(indices);
}
}
});
}
function evalAssignment(exp, workerScript) {
var env = workerScript.env;
if (env.stopFlag) {return Promise.reject(workerScript);}
if (exp.left.type != "Identifier" && exp.left.type != "MemberExpression") {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Cannot assign to " + JSON.stringify(exp.left)));
}
if (exp.operator !== "=" && !(exp.left.name in env.vars)){
return Promise.reject(makeRuntimeRejectMsg(workerScript, "variable " + exp.left.name + " not defined"));
}
return evaluate(exp.right, workerScript).then(function(expRight) {
if (exp.left.type == "MemberExpression") {
if (!exp.left.computed) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Cannot assign to an object's property. This is currently unsupported in Netscript", exp));
}
//Assign to array element
//Array object designed by exp.left.object.name
//Index designated by exp.left.property
return getArrayElement(exp.left, workerScript).then(function(res) {
if (!(res instanceof Array) || res.length < 2) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Error evaluating array assignment. This is (probably) a bug please report to game dev"));
}
//The array name is the second value
var arrName = res.splice(1, 1);
arrName = arrName[0];
var res;
try {
res = env.setArrayElement(arrName, res, expRight);
} catch (e) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, e));
}
return Promise.resolve(res);
}).catch(function(e) {
return Promise.reject(e);
});
} else {
//Other assignments
try {
var assign;
switch (exp.operator) {
case "=":
assign = expRight; break;
case "+=":
assign = env.get(exp.left.name) + expRight; break;
case "-=":
assign = env.get(exp.left.name) - expRight; break;
case "*=":
assign = env.get(exp.left.name) * expRight; break;
case "/=":
assign = env.get(exp.left.name) / expRight; break;
case "%=":
assign = env.get(exp.left.name) % expRight; break;
default:
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Bitwise assignment is not implemented"));
}
env.set(exp.left.name, assign);
return Promise.resolve(assign);
} catch (e) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Failed to set environment variable: " + e.toString()));
}
}
});
}
function evalVariableDeclaration(exp, workerScript) {
var env = workerScript.env;
if (env.stopFlag) {return Promise.reject(workerScript);}
if (!(exp.declarations instanceof Array)) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Variable declarations parsed incorrectly. This may be a syntax error"));
}
if (exp.kind !== "var") {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Only 'var' declarations are currently allowed (let, const, etc. are not allowed)"));
}
return Promise.all(exp.declarations.map((decl)=>{
evalVariableDeclarator(decl, workerScript);
})).then(function(res) {
return Promise.resolve(res);
});
}
//A Variable Declaration contains an array of Variable Declarators
function evalVariableDeclarator(exp, workerScript) {
var env = workerScript.env;
if (exp.type !== "VariableDeclarator") {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Invalid AST Node passed into evalVariableDeclarator: " + exp.type));
}
if (exp.init == null) {
env.set(exp.id.name, null);
return Promise.resolve(null);
} else {
return evaluate(exp.init, workerScript).then(function(initValue) {
env.set(exp.id.name, initValue);
});
}
}
function evaluateIf(exp, workerScript, i) {
var env = workerScript.env;
return evaluate(exp.test, workerScript).then(function(condRes) {
if (condRes) {
return evaluate(exp.consequent, workerScript).then(function(res) {
return Promise.resolve(true);
}, function(e) {
return Promise.reject(e);
});
} else if (exp.alternate) {
return evaluate(exp.alternate, workerScript).then(function(res) {
return Promise.resolve(true);
}, function(e) {
return Promise.reject(e);
});
} else {
return Promise.resolve("endIf");
}
});
}
//Evaluate the looping part of a for loop (Initialization block is NOT done in here)
function evaluateFor(exp, workerScript) {
var env = workerScript.env;
if (env.stopFlag) {return Promise.reject(workerScript);}
return new Promise(function(resolve, reject) {
function recurse() {
//Don't return a promise so the promise chain is broken on each recursion (saving memory)
evaluate(exp.test, workerScript).then(function(resCond) {
if (resCond) {
return evaluate(exp.body, workerScript).then(function(res) {
return evaluate(exp.update, workerScript);
}).catch(function(e) {
if (e == "CONTINUESTATEMENT" ||
(e instanceof WorkerScript && e.errorMessage == "CONTINUESTATEMENT")) {
//Continue statement, recurse to next iteration
return evaluate(exp.update, workerScript).then(function(resPostloop) {
return evaluateFor(exp, workerScript);
}).then(function(foo) {
return Promise.resolve("endForLoop");
}).catch(function(e) {
return Promise.reject(e);
});
} else {
return Promise.reject(e);
}
}).then(recurse, reject).catch(function(e) {
return Promise.reject(e);
});
} else {
resolve();
}
}).catch(function(e) {
reject(e);
});
}
recurse();
});
}
function evaluateForeach(arr, args, workerScript) {
console.log("evaluateForeach called");
if (!(arr instanceof Array)) {
return Promise.reject("Invalid array passed into forEach");
}
if (!(args instanceof Array) && args.length != 1) {
return Promise.reject("Invalid argument passed into forEach");
}
var func = args[0];
if (typeof func !== "function") {
return Promise.reject("Invalid function passed into forEach");
}
console.log(func);
return new Promise(function(resolve, reject) {
//Don't return a promise so the promise chain is broken on each recursion
function recurse(i) {
console.log("recurse() called with i: " + i);
if (i >= arr.length) {
resolve();
} else {
return Promise.delay(Settings.CodeInstructionRunTime).then(function() {
console.log("About to apply function");
var res = func.apply(null, [arr[i]]);
console.log("Applied function");
++i;
Promise.resolve(res).then(function(val) {
recurse(i);
}, reject).catch(function(e) {
return Promise.reject(e);
});
});
}
}
recurse(0);
});
}
function evaluateWhile(exp, workerScript) {
var env = workerScript.env;
if (env.stopFlag) {return Promise.reject(workerScript);}
return new Promise(function (resolve, reject) {
function recurse() {
//Don't return a promise so the promise chain is broken on each recursion (saving memory)
evaluate(exp.test, workerScript).then(function(resCond) {
if (resCond) {
return evaluate(exp.body, workerScript).catch(function(e) {
if (e == "CONTINUESTATEMENT" ||
(e instanceof WorkerScript && e.errorMessage == "CONTINUESTATEMENT")) {
//Continue statement, recurse
return evaluateWhile(exp, workerScript).then(function(foo) {
return Promise.resolve("endWhileLoop");
}, function(e) {
return Promise.reject(e);
});
} else {
return Promise.reject(e);
}
}).then(recurse, reject).catch(function(e) {
return Promise.reject(e);
});
} else {
resolve();
}
}).catch(function(e) {
reject(e);
});
}
recurse();
});
}
function evaluateProg(exp, workerScript, index) {
var env = workerScript.env;
if (env.stopFlag) {return Promise.reject(workerScript);}
if (index >= exp.body.length) {
return Promise.resolve("progFinished");
} else {
//Evaluate this line of code in the prog
//After the code finishes evaluating, evaluate the next line recursively
return evaluate(exp.body[index], workerScript).then(function(res) {
return evaluateProg(exp, workerScript, index + 1);
}).then(function(res) {
return Promise.resolve(workerScript);
}).catch(function(e) {
return Promise.reject(e);
});
}
}
function evaluateImport(exp, workerScript, checkingRam=false) {
//When its checking RAM, it exports an array of nodes for each imported function //When its checking RAM, it exports an array of nodes for each imported function
var ramCheckRes = []; var ramCheckRes = [];
@ -808,7 +123,7 @@ function evaluateImport(exp, workerScript, checkingRam=false) {
return Promise.resolve(true); return Promise.resolve(true);
} }
function killNetscriptDelay(workerScript) { export function killNetscriptDelay(workerScript) {
if (workerScript instanceof WorkerScript) { if (workerScript instanceof WorkerScript) {
if (workerScript.delay) { if (workerScript.delay) {
clearTimeout(workerScript.delay); clearTimeout(workerScript.delay);
@ -817,7 +132,7 @@ function killNetscriptDelay(workerScript) {
} }
} }
function netscriptDelay(time, workerScript) { export function netscriptDelay(time, workerScript) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
workerScript.delay = setTimeout(()=>{ workerScript.delay = setTimeout(()=>{
workerScript.delay = null; workerScript.delay = null;
@ -827,7 +142,7 @@ function netscriptDelay(time, workerScript) {
}); });
} }
function makeRuntimeRejectMsg(workerScript, msg, exp=null) { export function makeRuntimeRejectMsg(workerScript, msg, exp=null) {
var lineNum = ""; var lineNum = "";
if (exp != null) { if (exp != null) {
var num = getErrorLineNumber(exp, workerScript); var num = getErrorLineNumber(exp, workerScript);
@ -837,7 +152,7 @@ function makeRuntimeRejectMsg(workerScript, msg, exp=null) {
} }
//Run a script from inside a script using run() command //Run a script from inside a script using run() command
function runScriptFromScript(server, scriptname, args, workerScript, threads=1) { export function runScriptFromScript(server, scriptname, args, workerScript, threads=1) {
//Check if the script is already running //Check if the script is already running
var runningScriptObj = findRunningScript(scriptname, args, server); var runningScriptObj = findRunningScript(scriptname, args, server);
if (runningScriptObj != null) { if (runningScriptObj != null) {
@ -887,8 +202,8 @@ function runScriptFromScript(server, scriptname, args, workerScript, threads=1)
return Promise.resolve(false); return Promise.resolve(false);
} }
function getErrorLineNumber(exp, workerScript) { export function getErrorLineNumber(exp, workerScript) {
var code = workerScript.scriptRef.scriptRef.code; var code = workerScript.scriptRef.codeCode();
//Split code up to the start of the node //Split code up to the start of the node
try { try {
@ -899,7 +214,7 @@ function getErrorLineNumber(exp, workerScript) {
} }
} }
function isScriptErrorMessage(msg) { export function isScriptErrorMessage(msg) {
if (!isString(msg)) {return false;} if (!isString(msg)) {return false;}
let splitMsg = msg.split("|"); let splitMsg = msg.split("|");
if (splitMsg.length != 4){ if (splitMsg.length != 4){
@ -911,6 +226,3 @@ function isScriptErrorMessage(msg) {
} }
return true; return true;
} }
export {makeRuntimeRejectMsg, netscriptDelay, runScriptFromScript, evaluate,
isScriptErrorMessage, killNetscriptDelay, evaluateImport};

@ -51,6 +51,7 @@ import {StockMarket, StockSymbols, SymbolToStockMap,
sellStock, updateStockPlayerPosition, sellStock, updateStockPlayerPosition,
shortStock, sellShort, OrderTypes, shortStock, sellShort, OrderTypes,
PositionTypes, placeOrder, cancelOrder} from "./StockMarket/StockMarket"; PositionTypes, placeOrder, cancelOrder} from "./StockMarket/StockMarket";
import {numeralWrapper} from "./ui/numeralFormat";
import {post} from "./ui/postToTerminal"; import {post} from "./ui/postToTerminal";
import {TextFile, getTextFile, createTextFile} from "./TextFile"; import {TextFile, getTextFile, createTextFile} from "./TextFile";
@ -72,9 +73,10 @@ import {arrayToString} from "../utils/helpers/array
import {createRandomIp} from "../utils/IPAddress"; import {createRandomIp} from "../utils/IPAddress";
import {formatNumber, isHTML} from "../utils/StringHelperFunctions"; import {formatNumber, isHTML} from "../utils/StringHelperFunctions";
import {isString} from "../utils/helpers/isString"; import {isString} from "../utils/helpers/isString";
import {yesNoBoxClose, yesNoBoxGetYesButton,
yesNoBoxGetNoButton, yesNoBoxCreate, import { createElement } from "../utils/uiHelpers/createElement";
yesNoBoxOpen} from "../utils/YesNoBox"; import { createPopup } from "../utils/uiHelpers/createPopup";
import { removeElementById } from "../utils/uiHelpers/removeElementById";
var hasCorporationSF = false, //Source-File 3 var hasCorporationSF = false, //Source-File 3
hasSingularitySF = false, //Source-File 4 hasSingularitySF = false, //Source-File 4
@ -2453,6 +2455,14 @@ function NetscriptFunctions(workerScript) {
return runningScriptObj.onlineExpGained / runningScriptObj.onlineRunningTime; 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() { getTimeSinceLastAug : function() {
if (workerScript.checkingRam) { if (workerScript.checkingRam) {
return updateStaticRam("getTimeSinceLastAug", CONSTANTS.ScriptGetHackTimeRamCost); return updateStaticRam("getTimeSinceLastAug", CONSTANTS.ScriptGetHackTimeRamCost);
@ -2462,24 +2472,33 @@ function NetscriptFunctions(workerScript) {
}, },
prompt : function(txt) { prompt : function(txt) {
if (workerScript.checkingRam) {return 0;} if (workerScript.checkingRam) {return 0;}
if (yesNoBoxOpen) {
workerScript.scriptRef.log("ERROR: confirm() failed because a pop-up dialog box is already open");
return false;
}
if (!isString(txt)) {txt = String(txt);} if (!isString(txt)) {txt = String(txt);}
var yesBtn = yesNoBoxGetYesButton(), noBtn = yesNoBoxGetNoButton();
yesBtn.innerHTML = "Yes"; // The id for this popup will consist of the first 20 characters of the prompt string..
noBtn.innerHTML = "No"; // 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) { return new Promise(function(resolve, reject) {
yesBtn.addEventListener("click", ()=>{ const yesBtn = createElement("button", {
yesNoBoxClose(); class: "popup-box-button",
resolve(true); innerText: "Yes",
clickListener: () => {
removeElementById(popupId);
resolve(true);
},
}); });
noBtn.addEventListener("click", ()=>{
yesNoBoxClose(); const noBtn = createElement("button", {
resolve(false); 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) { wget : async function(url, target, ip=workerScript.serverIp) {
@ -4702,8 +4721,17 @@ function NetscriptFunctions(workerScript) {
} }
let data = contract.getData(); let data = contract.getData();
if (data.constructor === Array) { if (data.constructor === Array) {
// Pass a copy // For two dimensional arrays, we have to copy the internal arrays using
return data.slice(); // slice() as well. As of right now, no contract has arrays that have
// more than two dimensions
const copy = data.slice();
for (let i = 0; i < copy.length; ++i) {
if (data[i].constructor === Array) {
copy[i] = data[i].slice();
}
}
return copy;
} else { } else {
return data; return data;
} }

@ -28,8 +28,8 @@ const walk = require("acorn/dist/walk");
function WorkerScript(runningScriptObj) { function WorkerScript(runningScriptObj) {
this.name = runningScriptObj.filename; this.name = runningScriptObj.filename;
this.running = false; this.running = false;
this.serverIp = null; this.serverIp = runningScriptObj.server;
this.code = runningScriptObj.scriptRef.code; this.code = runningScriptObj.getCode();
this.env = new Environment(this); this.env = new Environment(this);
this.env.set("args", runningScriptObj.args.slice()); this.env.set("args", runningScriptObj.args.slice());
this.output = ""; this.output = "";
@ -219,6 +219,8 @@ function startNetscript1Script(workerScript) {
let fnPromise = entry.apply(null, fnArgs); let fnPromise = entry.apply(null, fnArgs);
fnPromise.then(function(res) { fnPromise.then(function(res) {
cb(res); cb(res);
}).catch(function(e) {
// Do nothing?
}); });
} }
int.setProperty(scope, name, int.createAsyncFunction(tempWrapper)); int.setProperty(scope, name, int.createAsyncFunction(tempWrapper));
@ -278,7 +280,7 @@ function startNetscript1Script(workerScript) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
function runInterpreter() { function runInterpreter() {
try { try {
if (workerScript.env.stopFlag) {return reject(workerScript);} if (workerScript.env.stopFlag) { return reject(workerScript); }
if (interpreter.step()) { if (interpreter.step()) {
window.setTimeout(runInterpreter, Settings.CodeInstructionRunTime); window.setTimeout(runInterpreter, Settings.CodeInstructionRunTime);
@ -498,7 +500,7 @@ function runScriptsLoop() {
p = startNetscript2Script(workerScripts[i]); p = startNetscript2Script(workerScripts[i]);
} else { } else {
p = startNetscript1Script(workerScripts[i]); 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 //Once the code finishes (either resolved or rejected, doesnt matter), set its
@ -539,7 +541,6 @@ function runScriptsLoop() {
} }
w.running = false; w.running = false;
w.env.stopFlag = true; w.env.stopFlag = true;
} else if (isScriptErrorMessage(w)) { } else if (isScriptErrorMessage(w)) {
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.log("ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " + w.toString()); console.log("ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " + w.toString());
@ -588,7 +589,7 @@ function addWorkerScript(runningScriptObj, server) {
} else { } else {
runningScriptObj.threads = 1; runningScriptObj.threads = 1;
} }
var ramUsage = roundToTwo(runningScriptObj.scriptRef.ramUsage * threads); var ramUsage = roundToTwo(runningScriptObj.getRamUsage() * threads);
var ramAvailable = server.maxRam - server.ramUsed; var ramAvailable = server.maxRam - server.ramUsed;
if (ramUsage > ramAvailable) { if (ramUsage > ramAvailable) {
dialogBoxCreate("Not enough RAM to run script " + runningScriptObj.filename + " with args " + dialogBoxCreate("Not enough RAM to run script " + runningScriptObj.filename + " with args " +
@ -601,7 +602,6 @@ function addWorkerScript(runningScriptObj, server) {
//Create the WorkerScript //Create the WorkerScript
var s = new WorkerScript(runningScriptObj); var s = new WorkerScript(runningScriptObj);
s.serverIp = server.ip;
s.ramUsage = ramUsage; s.ramUsage = ramUsage;
//Add the WorkerScript to the Active Scripts list //Add the WorkerScript to the Active Scripts list

@ -24,6 +24,7 @@ export interface IPlayer {
queuedAugmentations: IPlayerOwnedAugmentation[]; queuedAugmentations: IPlayerOwnedAugmentation[];
resleeves: Resleeve[]; resleeves: Resleeve[];
sleeves: Sleeve[]; sleeves: Sleeve[];
sleevesFromCovenant: number;
sourceFiles: IPlayerOwnedSourceFile[]; sourceFiles: IPlayerOwnedSourceFile[];
// Stats // Stats

@ -0,0 +1,24 @@
// Interface that represents either the player (PlayerObject) or
// a Sleeve. Used for functions that need to take in both.
export interface IPlayerOrSleeve {
// Stats
hacking_skill: number;
strength: number;
defense: number;
dexterity: number;
agility: number;
charisma: number;
intelligence: number;
// Experience
hacking_exp: number;
strength_exp: number;
defense_exp: number;
dexterity_exp: number;
agility_exp: number;
charisma_exp: number;
// Multipliers
crime_success_mult: number;
}

@ -41,6 +41,7 @@ export abstract class Person {
dexterity: number = 1; dexterity: number = 1;
agility: number = 1; agility: number = 1;
charisma: number = 1; charisma: number = 1;
intelligence: number = 1;
hp: number = 10; hp: number = 10;
max_hp: number = 10; max_hp: number = 10;

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

@ -184,7 +184,7 @@ export class Sleeve extends Person {
this.resetTaskStatus(); this.resetTaskStatus();
return retValue; return retValue;
} }
if (Math.random() < crime.successRate(p)) { if (Math.random() < crime.successRate(this)) {
// Success // Success
const successGainRates: ITaskTracker = createTaskTracker(); const successGainRates: ITaskTracker = createTaskTracker();

@ -0,0 +1,48 @@
/**
* Implements the purchasing of extra Duplicate Sleeves from The Covenant
*/
import { Sleeve } from "./Sleeve";
import { IPlayer } from "../IPlayer";
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { yesNoBoxCreate,
yesNoBoxClose,
yesNoBoxGetYesButton,
yesNoBoxGetNoButton } from "../../../utils/YesNoBox";
export const MaxSleevesFromCovenant: number = 5;
export function createPurchaseSleevesFromCovenantPopup(p: IPlayer) {
if (p.sleevesFromCovenant >= MaxSleevesFromCovenant) { return; }
// First sleeve purchased costs the base amount. Then, the price of
// each successive one increases by the same amount
const baseCostPerExtraSleeve: number = 10e12;
const cost: number = (p.sleevesFromCovenant + 1) * baseCostPerExtraSleeve;
const yesBtn = yesNoBoxGetYesButton();
const noBtn = yesNoBoxGetNoButton();
yesBtn!.addEventListener("click", () => {
if (p.canAfford(cost)) {
p.loseMoney(cost);
p.sleevesFromCovenant += 1;
p.sleeves.push(new Sleeve());
yesNoBoxClose();
} else {
dialogBoxCreate("You cannot afford to purchase a Duplicate Sleeve", false);
}
});
noBtn!.addEventListener("click", () => {
yesNoBoxClose();
});
const txt = `Would you like to purchase an additional Duplicate Sleeve from The Covenant for ` +
`${numeralWrapper.formatMoney(cost)}?<br><br>` +
`These Duplicate Sleeves are permanent. You can purchase a total of 5 Duplicate ` +
`Sleeves from The Covenant`;
yesNoBoxCreate(txt);
}

@ -652,10 +652,12 @@ function setSleeveTask(sleeve: Sleeve, elems: ISleeveUIElems): boolean {
res = sleeve.workoutAtGym(playerRef!, detailValue2, detailValue); res = sleeve.workoutAtGym(playerRef!, detailValue2, detailValue);
break; break;
case "Shock Recovery": case "Shock Recovery":
sleeve.finishTask(playerRef!);
sleeve.currentTask = SleeveTaskType.Recovery; sleeve.currentTask = SleeveTaskType.Recovery;
res = true; res = true;
break; break;
case "Synchronize": case "Synchronize":
sleeve.finishTask(playerRef!);
sleeve.currentTask = SleeveTaskType.Sync; sleeve.currentTask = SleeveTaskType.Sync;
res = true; res = true;
break; break;
@ -715,7 +717,7 @@ function updateSleeveTaskDescription(sleeve: Sleeve, elems: ISleeveUIElems): voi
elems.taskDescription!.innerText = `This sleeve is currently doing ${detailValue2} for ${sleeve.currentTaskLocation}.`; elems.taskDescription!.innerText = `This sleeve is currently doing ${detailValue2} for ${sleeve.currentTaskLocation}.`;
break; break;
case "Commit Crime": case "Commit Crime":
elems.taskDescription!.innerText = `This sleeve is currently attempting to ${Crimes[detailValue].type} (Success Rate: ${numeralWrapper.formatPercentage(Crimes[detailValue].successRate(playerRef))}).`; elems.taskDescription!.innerText = `This sleeve is currently attempting to ${Crimes[detailValue].type} (Success Rate: ${numeralWrapper.formatPercentage(Crimes[detailValue].successRate(sleeve))}).`;
break; break;
case "Take University Course": case "Take University Course":
elems.taskDescription!.innerText = `This sleeve is currently studying/taking a course at ${sleeve.currentTaskLocation}.`; elems.taskDescription!.innerText = `This sleeve is currently studying/taking a course at ${sleeve.currentTaskLocation}.`;

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

@ -650,7 +650,8 @@ async function calculateRamUsage(codeCopy) {
var workerScript = new WorkerScript({ var workerScript = new WorkerScript({
filename:"foo", filename:"foo",
scriptRef: {code:""}, scriptRef: {code:""},
args:[] args:[],
getCode: function() { return ""; }
}); });
workerScript.checkingRam = true; //Netscript functions will return RAM usage workerScript.checkingRam = true; //Netscript functions will return RAM usage
workerScript.serverIp = currServ.ip; workerScript.serverIp = currServ.ip;
@ -778,10 +779,9 @@ Reviver.constructors.Script = Script;
//Called when the game is loaded. Loads all running scripts (from all servers) //Called when the game is loaded. Loads all running scripts (from all servers)
//into worker scripts so that they will start running //into worker scripts so that they will start running
function loadAllRunningScripts() { function loadAllRunningScripts() {
var count = 0;
var total = 0; var total = 0;
let skipScriptLoad = (window.location.href.toLowerCase().indexOf("?noscripts") !== -1); let skipScriptLoad = (window.location.href.toLowerCase().indexOf("?noscripts") !== -1);
if (skipScriptLoad) {console.log("Skipping the load of any scripts during startup");} if (skipScriptLoad) { console.info("Skipping the load of any scripts during startup"); }
for (var property in AllServers) { for (var property in AllServers) {
if (AllServers.hasOwnProperty(property)) { if (AllServers.hasOwnProperty(property)) {
var server = AllServers[property]; var server = AllServers[property];
@ -799,8 +799,6 @@ function loadAllRunningScripts() {
server.runningScripts.length = 0; server.runningScripts.length = 0;
} else { } else {
for (var j = 0; j < server.runningScripts.length; ++j) { for (var j = 0; j < server.runningScripts.length; ++j) {
count++;
server.runningScripts[j].scriptRef.module = "";
addWorkerScript(server.runningScripts[j], server); addWorkerScript(server.runningScripts[j], server);
//Offline production //Offline production
@ -809,8 +807,8 @@ function loadAllRunningScripts() {
} }
} }
} }
return total; return total;
console.log("Loaded " + count.toString() + " running scripts");
} }
function scriptCalculateOfflineProduction(runningScriptObj) { function scriptCalculateOfflineProduction(runningScriptObj) {
@ -827,7 +825,7 @@ function scriptCalculateOfflineProduction(runningScriptObj) {
//Data map: [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken] //Data map: [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken]
//Grow // Grow
for (var ip in runningScriptObj.dataMap) { for (var ip in runningScriptObj.dataMap) {
if (runningScriptObj.dataMap.hasOwnProperty(ip)) { if (runningScriptObj.dataMap.hasOwnProperty(ip)) {
if (runningScriptObj.dataMap[ip][2] == 0 || runningScriptObj.dataMap[ip][2] == null) {continue;} if (runningScriptObj.dataMap[ip][2] == 0 || runningScriptObj.dataMap[ip][2] == null) {continue;}
@ -841,6 +839,7 @@ function scriptCalculateOfflineProduction(runningScriptObj) {
} }
} }
// Money from hacking
var totalOfflineProduction = 0; var totalOfflineProduction = 0;
for (var ip in runningScriptObj.dataMap) { for (var ip in runningScriptObj.dataMap) {
if (runningScriptObj.dataMap.hasOwnProperty(ip)) { if (runningScriptObj.dataMap.hasOwnProperty(ip)) {
@ -862,19 +861,19 @@ function scriptCalculateOfflineProduction(runningScriptObj) {
} }
} }
//Offline EXP gain // Offline EXP gain
//A script's offline production will always be at most half of its online production. // A script's offline production will always be at most half of its online production.
var expGain = 0.5 * (runningScriptObj.onlineExpGained / runningScriptObj.onlineRunningTime) * timePassed; var expGain = 0.5 * (runningScriptObj.onlineExpGained / runningScriptObj.onlineRunningTime) * timePassed;
expGain *= confidence; expGain *= confidence;
Player.gainHackingExp(expGain); Player.gainHackingExp(expGain);
//Update script stats // Update script stats
runningScriptObj.offlineMoneyMade += totalOfflineProduction; runningScriptObj.offlineMoneyMade += totalOfflineProduction;
runningScriptObj.offlineRunningTime += timePassed; runningScriptObj.offlineRunningTime += timePassed;
runningScriptObj.offlineExpGained += expGain; runningScriptObj.offlineExpGained += expGain;
//Fortify a server's security based on how many times it was hacked // Fortify a server's security based on how many times it was hacked
for (var ip in runningScriptObj.dataMap) { for (var ip in runningScriptObj.dataMap) {
if (runningScriptObj.dataMap.hasOwnProperty(ip)) { if (runningScriptObj.dataMap.hasOwnProperty(ip)) {
if (runningScriptObj.dataMap[ip][1] == 0 || runningScriptObj.dataMap[ip][1] == null) {continue;} if (runningScriptObj.dataMap[ip][1] == 0 || runningScriptObj.dataMap[ip][1] == null) {continue;}
@ -887,7 +886,7 @@ function scriptCalculateOfflineProduction(runningScriptObj) {
} }
} }
//Weaken // Weaken
for (var ip in runningScriptObj.dataMap) { for (var ip in runningScriptObj.dataMap) {
if (runningScriptObj.dataMap.hasOwnProperty(ip)) { if (runningScriptObj.dataMap.hasOwnProperty(ip)) {
if (runningScriptObj.dataMap[ip][3] == 0 || runningScriptObj.dataMap[ip][3] == null) {continue;} if (runningScriptObj.dataMap[ip][3] == 0 || runningScriptObj.dataMap[ip][3] == null) {continue;}
@ -916,11 +915,11 @@ function findRunningScript(filename, args, server) {
} }
function RunningScript(script, args) { function RunningScript(script, args) {
if (script == null || script == undefined) {return;} if (script == null || script == undefined) { return; }
this.filename = script.filename; this.filename = script.filename;
this.args = args; this.args = args;
this.scriptRef = script;
this.server = script.server; //IP Address only this.server = script.server; //IP Address only
this.ramUsage = script.ramUsage;
this.logs = []; //Script logging. Array of strings, with each element being a log entry this.logs = []; //Script logging. Array of strings, with each element being a log entry
this.logUpd = false; this.logUpd = false;
@ -935,8 +934,40 @@ function RunningScript(script, args) {
this.threads = 1; this.threads = 1;
//[MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken] // Holds a map of all servers, where server = key and the value for each
this.dataMap = new AllServersMap([0, 0, 0, 0], true); // server is an array of four numbers. The four numbers represent:
// [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken]
// This data is used for offline progress
this.dataMap = {};
}
RunningScript.prototype.getCode = function() {
const server = AllServers[this.server];
if (server == null) { return ""; }
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === this.filename) {
return server.scripts[i].code;
}
}
return "";
}
RunningScript.prototype.getRamUsage = function() {
if (this.ramUsage != null && this.ramUsage > 0) { return this.ramUsage; } // Use cached value
const server = AllServers[this.server];
if (server == null) { return 0; }
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === this.filename) {
// Cache the ram usage for the next call
this.ramUsage = server.scripts[i].ramUsage;
return this.ramUsage;
}
}
return 0;
} }
RunningScript.prototype.log = function(txt) { RunningScript.prototype.log = function(txt) {
@ -966,9 +997,8 @@ RunningScript.prototype.clearLog = function() {
//Update the moneyStolen and numTimesHack maps when hacking //Update the moneyStolen and numTimesHack maps when hacking
RunningScript.prototype.recordHack = function(serverIp, moneyGained, n=1) { RunningScript.prototype.recordHack = function(serverIp, moneyGained, n=1) {
if (this.dataMap == null) { if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) {
//[MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken] this.dataMap[serverIp] = [0, 0, 0, 0];
this.dataMap = new AllServersMap([0, 0, 0, 0], true);
} }
this.dataMap[serverIp][0] += moneyGained; this.dataMap[serverIp][0] += moneyGained;
this.dataMap[serverIp][1] += n; this.dataMap[serverIp][1] += n;
@ -976,18 +1006,16 @@ RunningScript.prototype.recordHack = function(serverIp, moneyGained, n=1) {
//Update the grow map when calling grow() //Update the grow map when calling grow()
RunningScript.prototype.recordGrow = function(serverIp, n=1) { RunningScript.prototype.recordGrow = function(serverIp, n=1) {
if (this.dataMap == null) { if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) {
//[MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken] this.dataMap[serverIp] = [0, 0, 0, 0];
this.dataMap = new AllServersMap([0, 0, 0, 0], true);
} }
this.dataMap[serverIp][2] += n; this.dataMap[serverIp][2] += n;
} }
//Update the weaken map when calling weaken() { //Update the weaken map when calling weaken() {
RunningScript.prototype.recordWeaken = function(serverIp, n=1) { RunningScript.prototype.recordWeaken = function(serverIp, n=1) {
if (this.dataMap == null) { if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) {
//[MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken] this.dataMap[serverIp] = [0, 0, 0, 0];
this.dataMap = new AllServersMap([0, 0, 0, 0], true);
} }
this.dataMap[serverIp][3] += n; this.dataMap[serverIp][3] += n;
} }
@ -1003,33 +1031,5 @@ RunningScript.fromJSON = function(value) {
Reviver.constructors.RunningScript = RunningScript; Reviver.constructors.RunningScript = RunningScript;
//Creates an object that creates a map/dictionary with the IP of each existing server as
//a key. Initializes every key with a specified value that can either by a number or an array
function AllServersMap(arr=false, filterOwned=false) {
for (var ip in AllServers) {
if (AllServers.hasOwnProperty(ip)) {
if (filterOwned && (AllServers[ip].purchasedByPlayer || AllServers[ip].hostname === "home")) {
continue;
}
if (arr) {
this[ip] = [0, 0, 0, 0];
} else {
this[ip] = 0;
}
}
}
}
AllServersMap.prototype.toJSON = function() {
return Generic_toJSON("AllServersMap", this);
}
AllServersMap.fromJSON = function(value) {
return Generic_fromJSON(AllServersMap, value.data);
}
Reviver.constructors.AllServersMap = AllServersMap;
export {loadAllRunningScripts, findRunningScript, export {loadAllRunningScripts, findRunningScript,
RunningScript, Script, AllServersMap, scriptEditorInit, isScriptFilename}; RunningScript, Script, scriptEditorInit, isScriptFilename};

@ -33,8 +33,10 @@ export function getPurchaseServerLimit() {
} }
export function getPurchaseServerMaxRam() { export function getPurchaseServerMaxRam() {
// TODO ensure this is a power of 2? const ram = Math.round(CONSTANTS.PurchasedServerMaxRam * BitNodeMultipliers.PurchasedServerMaxRam);
return Math.round(CONSTANTS.PurchasedServerMaxRam * BitNodeMultipliers.PurchasedServerMaxRam);
// Round this to the nearest power of 2
return 1 << 31 - Math.clz32(ram);
} }
// Manually purchase a server (NOT through Netscript) // Manually purchase a server (NOT through Netscript)

@ -640,6 +640,7 @@ function processStockPrices(numCycles=1) {
} else { } else {
stock.otlkMag -= otlkMagChange; stock.otlkMag -= otlkMagChange;
} }
if (stock.otlkMag > 50) { stock.otlkMag = 50; } // Cap so the "forecast" is between 0 and 100
if (stock.otlkMag < 0) { if (stock.otlkMag < 0) {
stock.otlkMag *= -1; stock.otlkMag *= -1;
stock.b = !stock.b; stock.b = !stock.b;

File diff suppressed because it is too large Load Diff

@ -30,6 +30,14 @@ function removeBracketsFromArrayString(str: string) {
return strCpy; return strCpy;
} }
function removeQuotesFromString(str: string) {
let strCpy: string = str;
if (strCpy.startsWith('"') || strCpy.startsWith("'")) { strCpy = strCpy.slice(1); }
if (strCpy.endsWith('"') || strCpy.endsWith("'")) { strCpy = strCpy.slice(0, -1); }
return strCpy;
}
function convert2DArrayToString(arr: any[][]) { function convert2DArrayToString(arr: any[][]) {
const components: string[] = []; const components: string[] = [];
arr.forEach((e: any) => { arr.forEach((e: any) => {
@ -73,7 +81,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
desc: (n: number[]) => { desc: (n: number[]) => {
return ["Given the following integer array, find the contiguous subarray", return ["Given the following integer array, find the contiguous subarray",
"(containing at least one number) which has the largest sum and return that sum.", "(containing at least one number) which has the largest sum and return that sum.",
"'Sum' refers to the sum of all the numbers in the subarray.", "'Sum' refers to the sum of all the numbers in the subarray.\n",
`${n.toString()}`].join(" "); `${n.toString()}`].join(" ");
}, },
difficulty: 1, difficulty: 1,
@ -152,8 +160,8 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
}, },
difficulty: 2, difficulty: 2,
gen: () => { gen: () => {
const m: number = getRandomInt(1, 10); const m: number = getRandomInt(1, 15);
const n: number = getRandomInt(1, 10); const n: number = getRandomInt(1, 15);
const matrix: number[][] = []; const matrix: number[][] = [];
matrix.length = m; matrix.length = m;
for (let i: number = 0; i < m; ++i) { for (let i: number = 0; i < m; ++i) {
@ -493,4 +501,422 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return release2.toString() === ans; return release2.toString() === ans;
}, },
}, },
{
desc: (data: any[]) => {
const k: number = (<number>data[0]);
const prices: number[] = (<number[]>data[1]);
return ["You are given the following array with two elements:\n\n",
`[${k}, [${prices}]]\n\n`,
"The first element is an integer k. The second element is an",
"array of stock prices (which are numbers) where the i-th element",
"represents the stock price on day i.\n\n",
"Determine the maximum possible profit you can earn using at most",
"k transactions. A transaction is defined as buying and then selling",
"one share of the stock. Note that you cannot engage in multiple",
"transactions at once. In other words, you must sell the stock before",
"you can buy it again.\n\n",
"If no profit can be made, then the answer should be 0."].join(" ");
},
difficulty: 8,
gen: () => {
const k: number = getRandomInt(2, 10);
const len: number = getRandomInt(1, 50);
const prices: number[] = [];
prices.length = len;
for (let i = 0; i < len; ++i) {
prices[i] = getRandomInt(1, 200);
}
return [k, prices];
},
name: "Algorithmic Stock Trader IV",
numTries: 10,
solver: (data: any[], ans: string) => {
const k: number = (<number>data[0]);
const prices: number[] = (<number[]>data[1]);
const len = prices.length;
if (len < 2) { return (parseInt(ans) === 0); }
if (k > len / 2) {
let res: number = 0;
for (let i = 1; i < len; ++i) {
res += Math.max(prices[i] - prices[i-1], 0);
}
return (parseInt(ans) === res);
}
const hold: number[] = [];
const rele: number[] = [];
hold.length = k + 1;
rele.length = k + 1;
for (let i = 0; i <= k; ++i) {
hold[i] = Number.MIN_SAFE_INTEGER;
rele[i] = 0;
}
let cur: number;
for (let i = 0; i < len; ++i) {
cur = prices[i];
for (let j = k; j > 0; --j) {
rele[j] = Math.max(rele[j], hold[j] + cur);
hold[j] = Math.max(hold[j], rele[j-1] - cur);
}
}
return (parseInt(ans) === rele[k]);
},
},
{
desc: (data: number[][]) => {
function createTriangleRecurse(data: number[][], level: number = 0): string {
const numLevels: number = data.length;
if (level >= numLevels) { return ""; }
const numSpaces = numLevels - level + 1;
let str: string = ["&nbsp;".repeat(numSpaces), "[", data[level].toString(), "]"].join("");
if (level < numLevels - 1) {
str += ",";
}
return str + "\n" + createTriangleRecurse(data, level+1);
}
function createTriangle(data: number[][]) {
return ["[\n", createTriangleRecurse(data), "]"].join("");
}
const triangle = createTriangle(data);
return ["Given a triangle, find the minimum path sum from top to bottom. In each step",
"of the path, you may only move to adjacent numbers in the row below.",
"The triangle is represented as a 2D array of numbers:\n\n",
`${triangle}\n\n`,
"Example: If you are given the following triangle:\n\n" +
"[\n",
"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[2],\n",
"&nbsp;&nbsp;&nbsp;&nbsp;[3,4],\n",
"&nbsp;&nbsp;&nbsp;[6,5,7],\n",
"&nbsp;&nbsp;[4,1,8,3]\n",
"]\n\n",
"The minimum path sum is 11 (2 -> 3 -> 5 -> 1)."].join(" ");
},
difficulty: 5,
gen: () => {
const triangle: number[][] = [];
const levels: number = getRandomInt(1, 12);
triangle.length = levels;
for (let row = 0; row < levels; ++row) {
triangle[row] = [];
triangle[row].length = row + 1;
for (let i = 0; i < triangle[row].length; ++i) {
triangle[row][i] = getRandomInt(1, 9);
}
}
return triangle;
},
name: "Minimum Path Sum in a Triangle",
numTries: 10,
solver: (data: number[][], ans: string) => {
let n: number = data.length;
let dp: number[] = data[n-1];
for (let i = n-2; i > -1; --i) {
for (let j = 0; j < data[i].length; ++j) {
dp[j] = Math.min(dp[j], dp[j + 1]) + data[i][j];
}
}
return dp[0] === parseInt(ans);
},
},
{
desc: (data: number[]) => {
const numRows = data[0];
const numColumns = data[1];
return ["You are in a grid with",
`${numRows} rows and ${numColumns} columns, and you are`,
"positioned in the top-left corner of that grid. You are trying to",
"reach the bottom-right corner of the grid, but you can only",
"move down or right on each step. Determine how many",
"unique paths there are from start to finish.\n\n",
"NOTE: The data returned for this contract is an array",
"with the number or rows and columns:\n\n",
`[${numRows}, ${numColumns}]`].join(" ");
},
difficulty: 3,
gen: () => {
const numRows: number = getRandomInt(1, 14);
const numColumns: number = getRandomInt(1, 14);
return [numRows, numColumns];
},
name: "Unique Paths in a Grid I",
numTries: 10,
solver: (data: number[], ans: string) => {
let n: number = data[0]; // Number of rows
let m: number = data[1]; // Number of columns
let currentRow: number[] = [];
currentRow.length = n;
for (let i = 0; i < n; i++) {
currentRow[i] = 1;
}
for (let row = 1; row < m; row++) {
for (let i = 1; i < n; i++) {
currentRow[i] += currentRow[i - 1];
}
}
return parseInt(ans) === currentRow[n - 1];
},
},
{
desc: (data: number[][]) => {
let gridString: string = "";
for (const line of data) {
gridString += `${line.toString()},\n`;
}
return ["You are located in the top-left corner of the following grid:\n\n",
`${gridString}\n`,
"You are trying reach the bottom-right corner of the grid, but you can only",
"move down or right on each step. Furthermore, there are obstacles on the grid",
"that you cannot move onto. These obstacles are denoted by '1', while empty",
"spaces are denoted by 0.\n\n",
"Determine how many unique paths there are from start to finish.\n\n",
"NOTE: The data returned for this contract is an 2D array of numbers representing the grid."].join(" ");
},
difficulty: 5,
gen: () => {
const numRows: number = getRandomInt(1, 12);
const numColumns: number = getRandomInt(1, 12);
const grid: number[][] = [];
grid.length = numRows;
for (let i = 0; i < numRows; ++i) {
grid[i] = [];
grid[i].length = numColumns;
grid[i].fill(0);
}
for (let r = 0; r < numRows; ++r) {
for (let c = 0; c < numColumns; ++c) {
if (r === 0 && c === 0) { continue; }
if (r === numRows - 1 && c === numColumns - 1) { continue; }
// 15% chance of an element being an obstacle
if (Math.random() < 0.15) {
grid[r][c] = 1;
}
}
}
return grid;
},
name: "Unique Paths in a Grid II",
numTries: 10,
solver: (data: number[][], ans: string) => {
let obstacleGrid: number[][] = [];
obstacleGrid.length = data.length;
for (let i = 0; i < obstacleGrid.length; ++i) {
obstacleGrid[i] = data[i].slice();
}
for (let i = 0; i < obstacleGrid.length; i++) {
for (let j = 0; j < obstacleGrid[0].length; j++) {
if (obstacleGrid[i][j] == 1) {
obstacleGrid[i][j] = 0;
} else if (i==0 && j==0) {
obstacleGrid[0][0] = 1;
} else {
obstacleGrid[i][j] = (i > 0 ? obstacleGrid[i-1][j] : 0) + ( j > 0 ? obstacleGrid[i][j-1] : 0);
}
}
}
return (obstacleGrid[obstacleGrid.length -1][obstacleGrid[0].length-1] === parseInt(ans));
},
},
{
desc: (data: string) => {
return ["Given the following string:\n\n",
`${data}\n\n`,
"remove the minimum number of invalid parentheses in order to validate",
"the string. If there are multiple minimal ways to validate the string,",
"provide all of the possible results. The answer should be provided",
"as an array of strings. If it is impossible to validate the string",
"the result should be an array with only an empty string.\n\n",
"IMPORTANT: The string may contain letters, not just parentheses.",
`Examples:\n`,
`"()())()" -> ["()()()", "(())()"]\n`,
`"(a)())()" -> ["(a)()()", "(a())()"]\n`,
`")( -> [""]`].join(" ");
},
difficulty: 10,
gen: () => {
const len: number = getRandomInt(2, 20);
let chars: string[] = [];
chars.length = len;
// 80% chance of the first parenthesis being (
Math.random() < 0.8 ? chars[0] = "(" : chars[0] = ")";
for (let i = 1; i < len; ++i) {
const roll = Math.random();
if (roll < 0.4) {
chars[i] = "(";
} else if (roll < 0.8) {
chars[i] = ")";
} else {
chars[i] = "a";
}
}
return chars.join("");
},
name: "Sanitize Parentheses in Expression",
numTries: 10,
solver: (data: string, ans: string) => {
let left = 0;
let right = 0;
let res: string[] = [];
for (let i = 0; i < data.length; ++i) {
if (data[i] === '(') {
++left;
} else if (data[i] === ')') {
(left > 0) ? --left : ++right;
}
}
function dfs(pair: number, index: number, left: number, right: number, s: string, solution: string, res: string[]) {
if (s.length === index) {
if (left === 0 && right === 0 && pair === 0) {
for(var i = 0; i < res.length; i++) {
if(res[i] === solution) { return; }
}
res.push(solution);
}
return;
}
if (s[index] === '(') {
if (left > 0) {
dfs(pair, index + 1, left - 1, right, s, solution, res);
}
dfs(pair + 1, index + 1, left, right, s, solution + s[index], res);
} else if (s[index] === ')') {
if (right > 0) dfs(pair, index + 1, left, right - 1, s, solution, res);
if (pair > 0) dfs(pair - 1, index + 1, left, right, s, solution + s[index], res);
} else {
dfs(pair, index + 1, left, right, s, solution + s[index], res);
}
}
dfs(0, 0, left, right, data, "", res);
const sanitizedPlayerAns = removeBracketsFromArrayString(ans)
.replace(/\s/g, "");
const playerAnsArray: string[] = sanitizedPlayerAns.split(",");
if (playerAnsArray.length !== res.length) { return false; }
for (const resultInAnswer of res) {
if (!playerAnsArray.includes(resultInAnswer)) { return false; }
}
return true;
},
},
{
desc: (data: any[]) => {
const digits: string = data[0];
const target: number = data[1];
return ["You are given the following string which contains only digits between 0 and 9:\n\n",
`${digits}\n\n`,
`You are also given a target number of ${target}. Return all possible ways`,
"you can add the +, -, and * operators to the string such that it evaluates",
"to the target number.\n\n",
"The provided answer should be an array of strings containing the valid expressions.",
"The data provided by this problem is an array with two elements. The first element",
"is the string of digits, while the second element is the target number:\n\n",
`["${digits}", ${target}]\n\n`,
"Examples:\n\n",
`Input: digits = "123", target = 6\n`,
`Output: ["1+2+3", "1*2*3"]\n\n`,
`Input: digits = "105", target = 5\n`,
`Output: ["1*0+5", "10-5"]`].join(" ");
},
difficulty: 10,
gen: () => {
const numDigits = getRandomInt(4, 12);
const digitsArray: string[] = [];
digitsArray.length = numDigits;
for (let i = 0; i < digitsArray.length; ++i) {
if (i === 0) {
digitsArray[i] = String(getRandomInt(1, 9));
} else {
digitsArray[i] = String(getRandomInt(0, 9));
}
}
const target: number = getRandomInt(-100, 100);
const digits: string = digitsArray.join("");
return [digits, target];
},
name: "Find All Valid Math Expressions",
numTries: 10,
solver: (data: any[], ans: string) => {
const num: string = data[0];
const target: number = data[1];
function helper(res: string[], path: string, num: string, target: number, pos: number, evaluated: number, multed: number) {
if (pos === num.length) {
if (target === evaluated) {
res.push(path);
}
return;
}
for (let i = pos; i < num.length; ++i) {
if (i != pos && num[pos] == '0') { break; }
let cur = parseInt(num.substring(pos, i+1));
if (pos === 0) {
helper(res, path + cur, num, target, i + 1, cur, cur);
} else {
helper(res, path + "+" + cur, num, target, i + 1, evaluated + cur, cur);
helper(res, path + "-" + cur, num, target, i + 1, evaluated - cur, -cur);
helper(res, path + "*" + cur, num, target, i + 1, evaluated - multed + multed * cur, multed * cur);
}
}
}
const sanitizedPlayerAns: string = removeBracketsFromArrayString(ans);
const sanitizedPlayerAnsArr: string[] = sanitizedPlayerAns.split(",");
for (let i = 0; i < sanitizedPlayerAnsArr.length; ++i) {
sanitizedPlayerAnsArr[i] = removeQuotesFromString(sanitizedPlayerAnsArr[i]);
}
if (num == null || num.length === 0) {
if (sanitizedPlayerAnsArr.length === 0) { return true; }
if (sanitizedPlayerAnsArr.length === 1 && sanitizedPlayerAnsArr[0] === "") { return true; }
return false;
}
let result: string[] = [];
helper(result, "", num, target, 0, 0, 0);
for (const expr of result) {
if (!sanitizedPlayerAnsArr.includes(expr)) {
return false;
}
}
return true;
},
},
]; ];

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

@ -8,12 +8,16 @@ export function post(input: string) {
postContent(input); postContent(input);
} }
export function postError(input: string) {
postContent(`ERROR: ${input}`, { color: "#ff2929" });
}
/** /**
* Adds some output to the terminal with an identifier of "hack-progress-bar" * Adds some output to the terminal with an identifier of "hack-progress-bar"
* @param input Text or HTML to output to the terminal * @param input Text or HTML to output to the terminal
*/ */
export function hackProgressBarPost(input: string) { export function hackProgressBarPost(input: string) {
postContent(input, "hack-progress-bar"); postContent(input, { id: "hack-progress-bar" });
} }
/** /**
@ -21,14 +25,19 @@ export function hackProgressBarPost(input: string) {
* @param input Text or HTML to output to the terminal * @param input Text or HTML to output to the terminal
*/ */
export function hackProgressPost(input: string) { export function hackProgressPost(input: string) {
postContent(input, "hack-progress"); postContent(input, { id: "hack-progress" });
} }
function postContent(input: string, id?: string) { interface IPostContentConfig {
id?: string; // Replaces class, if specified
color?: string; // Additional class for terminal-line. Does NOT replace
}
function postContent(input: string, config: IPostContentConfig = {}) {
// tslint:disable-next-line:max-line-length // tslint:disable-next-line:max-line-length
const style: string = `color: var(--my-font-color); background-color:var(--my-background-color);${id === undefined ? " white-space:pre-wrap;" : ""}`; const style: string = `color: ${config.color != null ? config.color : "var(--my-font-color)"}; background-color:var(--my-background-color);${config.id === undefined ? " white-space:pre-wrap;" : ""}`;
// tslint:disable-next-line:max-line-length // tslint:disable-next-line:max-line-length
const content: string = `<tr class="posted"><td ${id === undefined ? 'class="terminal-line"' : `id="${id}"`} style="${style}">${input}</td></tr>`; const content: string = `<tr class="posted"><td ${config.id === undefined ? `class="terminal-line"` : `id="${config.id}"`} style="${style}">${input}</td></tr>`;
const inputElement: HTMLElement = getElementById("terminal-input"); const inputElement: HTMLElement = getElementById("terminal-input");
inputElement.insertAdjacentHTML("beforebegin", content); inputElement.insertAdjacentHTML("beforebegin", content);
scrollTerminalToBottom(); scrollTerminalToBottom();

@ -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 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" e.g. 10000 -> "10 seconds"
120000 -> "0 0 hours 2 minutes and 0 seconds" 120000 -> "2 minutes and 0 seconds"
*/ */
function convertTimeMsToTimeElapsedString(time: number): string { function convertTimeMsToTimeElapsedString(time: number): string {
const millisecondsPerSecond: number = 1000; const millisecondsPerSecond: number = 1000;

@ -1,6 +1,19 @@
/** /**
* Returns the input array as a comma separated string. * 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[]) { 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(", ")}]`;
} }

@ -12,6 +12,7 @@ export const KEY: IMap<number> = {
DOWNARROW: 40, DOWNARROW: 40,
E: 69, E: 69,
ENTER: 13, ENTER: 13,
ESC: 27,
F: 70, F: 70,
H: 72, H: 72,
J: 74, J: 74,

@ -13,8 +13,6 @@ interface ICreatePopupCloseButtonOptions {
export function createPopupCloseButton(popup: Element | string, options: ICreatePopupCloseButtonOptions) { export function createPopupCloseButton(popup: Element | string, options: ICreatePopupCloseButtonOptions) {
let button: HTMLButtonElement; let button: HTMLButtonElement;
// TODO event listener works with escape. Add and remove event listener
// from document
function closePopupWithEscFn(e: any): void { function closePopupWithEscFn(e: any): void {
if (e.keyCode === 27) { if (e.keyCode === 27) {
button.click(); button.click();