diff --git a/doc/source/netscript/basicfunctions/grow.rst b/doc/source/netscript/basicfunctions/grow.rst
index ddd828c78..026e825cd 100644
--- a/doc/source/netscript/basicfunctions/grow.rst
+++ b/doc/source/netscript/basicfunctions/grow.rst
@@ -1,10 +1,10 @@
grow() Netscript Function
=========================
-.. js:function:: grow(hostname/ip)
+.. js:function:: grow(hostname/ip[, opts={}])
:param string hostname/ip: IP or hostname of the target server to grow
- :param object options: Optional parameters for configuring function behavior. Properties:
+ :param object opts: Optional parameters for configuring function behavior. Properties:
* threads (*number*) - Number of threads to use for this function.
Must be less than or equal to the number of threads the script is running with.
diff --git a/doc/source/netscript/basicfunctions/hack.rst b/doc/source/netscript/basicfunctions/hack.rst
index 6f49954c1..569fb323d 100644
--- a/doc/source/netscript/basicfunctions/hack.rst
+++ b/doc/source/netscript/basicfunctions/hack.rst
@@ -1,10 +1,10 @@
hack() Netscript Function
=========================
-.. js:function:: hack(hostname/ip)
+.. js:function:: hack(hostname/ip[, opts={}])
:param string hostname/ip: IP or hostname of the target server to hack
- :param object options: Optional parameters for configuring function behavior. Properties:
+ :param object opts: Optional parameters for configuring function behavior. Properties:
* threads (*number*) - Number of threads to use for this function.
Must be less than or equal to the number of threads the script is running with.
diff --git a/doc/source/netscript/basicfunctions/weaken.rst b/doc/source/netscript/basicfunctions/weaken.rst
index 4d13e1a1a..2d67ddccc 100644
--- a/doc/source/netscript/basicfunctions/weaken.rst
+++ b/doc/source/netscript/basicfunctions/weaken.rst
@@ -1,10 +1,10 @@
weaken() Netscript Function
===========================
-.. js:function:: weaken(hostname/ip, options={})
+.. js:function:: weaken(hostname/ip[, opts={}])
:param string hostname/ip: IP or hostname of the target server to weaken
- :param object options: Optional parameters for configuring function behavior. Properties:
+ :param object opts: Optional parameters for configuring function behavior. Properties:
* threads (*number*) - Number of threads to use for this function.
Must be less than or equal to the number of threads the script is running with.
diff --git a/doc/source/netscript/codingcontractapi/attempt.rst b/doc/source/netscript/codingcontractapi/attempt.rst
index 342349909..64e4bc531 100644
--- a/doc/source/netscript/codingcontractapi/attempt.rst
+++ b/doc/source/netscript/codingcontractapi/attempt.rst
@@ -1,13 +1,20 @@
attempt() Netscript Function
============================
-.. js:function:: attempt(answer, fn[, hostname/ip=current ip])
+.. js:function:: attempt(answer, fn[, hostname/ip=current ip, opts={}])
:param answer: Solution for the contract
:param string fn: Filename of the contract
:param string hostname/ip: Hostname or IP of the server containing the contract.
Optional. Defaults to current server if not provided
+ :param object opts: Optional parameters for configuring function behavior. Properties:
+
+ * returnReward (*boolean*) If truthy, then the function will return a string
+ that states the contract's reward when it is successfully solved.
Attempts to solve the Coding Contract with the provided solution.
- :returns: Boolean indicating whether the solution was correct
+ :returns: Boolean indicating whether the solution was correct. If the :code:`returnReward`
+ option is configured, then the function will instead return a string. If the
+ contract is successfully solved, the string will contain a description of the
+ contract's reward. Otherwise, it will be an empty string.
diff --git a/src/Augmentation/AugmentationHelpers.js b/src/Augmentation/AugmentationHelpers.js
index ef7649a85..7110b70c8 100644
--- a/src/Augmentation/AugmentationHelpers.js
+++ b/src/Augmentation/AugmentationHelpers.js
@@ -2311,6 +2311,13 @@ function displaySourceFiles(listElement, sourceFiles) {
}
}
+export function isRepeatableAug(aug) {
+ const augName = (aug instanceof Augmentation) ? aug.name : aug;
+
+ if (augName === AugmentationNames.NeuroFluxGovernor) { return true; }
+
+ return false;
+}
export {installAugmentations,
initAugmentations, applyAugmentation, augmentationExists,
diff --git a/src/Bladeburner.js b/src/Bladeburner.js
index 0a964a48c..0f661d4b6 100644
--- a/src/Bladeburner.js
+++ b/src/Bladeburner.js
@@ -4173,7 +4173,7 @@ function initBladeburner() {
"results would be catastrophic.
" +
"We do not have the power or jurisdiction to shutdown this down " +
"through legal or political means, so we must resort to a covert " +
- "operation. Your goal is to destroy this technology and eliminate" +
+ "operation. Your goal is to destroy this technology and eliminate " +
"anyone who was involved in its creation.",
baseDifficulty:15e3, reqdRank:30e3,
rankGain:750, rankLoss:60, hpLoss:1000,
diff --git a/src/CodingContracts.ts b/src/CodingContracts.ts
index 73c5f1423..f571d6c04 100644
--- a/src/CodingContracts.ts
+++ b/src/CodingContracts.ts
@@ -17,9 +17,6 @@ import { createElement } from "../utils/uiHelpers/createElement";
import { createPopup } from "../utils/uiHelpers/createPopup";
import { removeElementById } from "../utils/uiHelpers/removeElementById";
-
-
-
/* tslint:disable:no-magic-numbers completed-docs max-classes-per-file no-console */
/* Represents different types of problems that a Coding Contract can have */
diff --git a/src/Constants.ts b/src/Constants.ts
index 26849d811..3e3c0c33c 100644
--- a/src/Constants.ts
+++ b/src/Constants.ts
@@ -234,11 +234,16 @@ export let CONSTANTS: IMap = {
** This is just a temporary patch until the mechanic gets re-worked
* hack(), grow(), and weaken() functions now take optional arguments for number of threads to use (by MasonD)
-
+ * codingcontract.attempt() now takes an optional argument that allows you to configure the function to return a contract's reward
* Adjusted RAM costs of Netscript Singularity functions (mostly increased)
+ * Adjusted RAM cost of codingcontract.getNumTriesRemaining() Netscript function
* Netscript Singularity functions no longer cost extra RAM outside of BitNode-4
* Corporation employees no longer have an "age" stat
* Gang Wanted level gain rate capped at 100 (per employee)
+ * Script startup/kill is now processed every 3 seconds, instead of 6 seconds
+ * getHackTime(), getGrowTime(), and getWeakenTime() now return Infinity if called on a Hacknet Server
+ * Money/Income tracker now displays money lost from hospitalizations
+ * Exported saves now have a unique filename based on current BitNode and timestamp
* Bug Fix: Corporation employees stats should no longer become negative
* Bug Fix: Fixed sleeve.getInformation() throwing error in certain scenarios
* Bug Fix: Coding contracts should no longer generate on the w0r1d_d43m0n server
@@ -253,5 +258,7 @@ export let CONSTANTS: IMap = {
* Bug Fix: Terminal 'wget' command now correctly evaluates directory paths
* Bug Fix: wget(), write(), and scp() Netscript functions now fail if an invalid filepath is passed in
* Bug Fix: Having Corporation warehouses at full capacity should no longer freeze game in certain conditions
+ * Bug Fix: Prevented an exploit that allows you to buy multiple copies of an Augmentation by holding the 'Enter' button
+ * Bug Fix: gang.getOtherGangInformation() now properly returns a deep copy
`
}
diff --git a/src/Faction/FactionHelpers.jsx b/src/Faction/FactionHelpers.jsx
index a05bb9838..ffe51c06c 100644
--- a/src/Faction/FactionHelpers.jsx
+++ b/src/Faction/FactionHelpers.jsx
@@ -4,6 +4,7 @@ import ReactDOM from "react-dom";
import { FactionRoot } from "./ui/Root";
import { Augmentations } from "../Augmentation/Augmentations";
+import { isRepeatableAug } from "../Augmentation/AugmentationHelpers";
import { PlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
@@ -93,7 +94,12 @@ export function purchaseAugmentationBoxCreate(aug, fac) {
const yesBtn = yesNoBoxGetYesButton();
yesBtn.innerHTML = "Purchase";
yesBtn.addEventListener("click", function() {
+ if (!isRepeatableAug(aug) && Player.hasAugmentation(aug)) {
+ return;
+ }
+
purchaseAugmentation(aug, fac);
+ yesNoBoxClose();
});
const noBtn = yesNoBoxGetNoButton();
@@ -204,7 +210,6 @@ export function purchaseAugmentation(aug, fac, sing=false) {
"Please report this to the game developer with an explanation of how to " +
"reproduce this.");
}
- yesNoBoxClose();
}
export function getNextNeurofluxLevel() {
diff --git a/src/Gang.js b/src/Gang.js
index db42bd646..eac2a22c6 100644
--- a/src/Gang.js
+++ b/src/Gang.js
@@ -700,7 +700,7 @@ GangMember.prototype.calculateWantedLevelGain = function(gang) {
// Put an arbitrary cap on this to prevent wanted level from rising too fast if the
// denominator is very small. Might want to rethink formula later
- return Math.max(100, calc);
+ return Math.min(100, calc);
}
}
diff --git a/src/Hacknet/HacknetHelpers.jsx b/src/Hacknet/HacknetHelpers.jsx
index 086b27111..e1066c9be 100644
--- a/src/Hacknet/HacknetHelpers.jsx
+++ b/src/Hacknet/HacknetHelpers.jsx
@@ -61,7 +61,7 @@ export function purchaseHacknet() {
}
}
/* END INTERACTIVE TUTORIAL */
-
+
const numOwned = Player.hacknetNodes.length;
if (hasHacknetServers()) {
const cost = getCostOfNextHacknetServer();
@@ -350,7 +350,7 @@ export function purchaseCoreUpgrade(node, levels=1) {
export function purchaseCacheUpgrade(node, levels=1) {
const sanitizedLevels = Math.round(levels);
- const cost = node.calculateCoreUpgradeCost(sanitizedLevels);
+ const cost = node.calculateCacheUpgradeCost(sanitizedLevels);
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
return false;
}
diff --git a/src/Netscript/RamCostGenerator.ts b/src/Netscript/RamCostGenerator.ts
index 0b1ae2bf8..a4d22c157 100644
--- a/src/Netscript/RamCostGenerator.ts
+++ b/src/Netscript/RamCostGenerator.ts
@@ -269,7 +269,7 @@ export const RamCosts: IMap = {
getContractType: () => RamCostConstants.ScriptCodingContractBaseRamCost / 2,
getData: () => RamCostConstants.ScriptCodingContractBaseRamCost / 2,
getDescription: () => RamCostConstants.ScriptCodingContractBaseRamCost / 2,
- getNumTriesRemaining: () => RamCostConstants.ScriptCodingContractBaseRamCost / 2,
+ getNumTriesRemaining: () => RamCostConstants.ScriptCodingContractBaseRamCost / 5,
},
// Duplicate Sleeve API
diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js
index 56a642a91..1bd9f3d7f 100644
--- a/src/NetscriptFunctions.js
+++ b/src/NetscriptFunctions.js
@@ -2142,29 +2142,23 @@ function NetscriptFunctions(workerScript) {
},
getHackTime: function(ip, hack, int) {
updateDynamicRam("getHackTime", getRamCost("getHackTime"));
- var server = getServer(ip);
- if (server == null) {
- workerScript.scriptRef.log("getHackTime() failed. Invalid IP or hostname passed in: " + ip);
- throw makeRuntimeRejectMsg(workerScript, "getHackTime() failed. Invalid IP or hostname passed in: " + ip);
- }
+ const server = safeGetServer(ip, "getHackTime");
+ if (failOnHacknetServer(server, "getHackTime")) { return Infinity; }
+
return calculateHackingTime(server, hack, int); // Returns seconds
},
getGrowTime: function(ip, hack, int) {
updateDynamicRam("getGrowTime", getRamCost("getGrowTime"));
- var server = getServer(ip);
- if (server == null) {
- workerScript.scriptRef.log("getGrowTime() failed. Invalid IP or hostname passed in: " + ip);
- throw makeRuntimeRejectMsg(workerScript, "getGrowTime() failed. Invalid IP or hostname passed in: " + ip);
- }
+ const server = safeGetServer(ip, "getGrowTime");
+ if (failOnHacknetServer(server, "getGrowTime")) { return Infinity; }
+
return calculateGrowTime(server, hack, int); // Returns seconds
},
getWeakenTime: function(ip, hack, int) {
updateDynamicRam("getWeakenTime", getRamCost("getWeakenTime"));
- var server = getServer(ip);
- if (server == null) {
- workerScript.scriptRef.log("getWeakenTime() failed. Invalid IP or hostname passed in: " + ip);
- throw makeRuntimeRejectMsg(workerScript, "getWeakenTime() failed. Invalid IP or hostname passed in: " + ip);
- }
+ const server = safeGetServer(ip, "getWeakenTime");
+ if (failOnHacknetServer(server, "getWeakenTime")) { return Infinity; }
+
return calculateWeakenTime(server, hack, int); // Returns seconds
},
getScriptIncome: function(scriptname, ip) {
@@ -3453,7 +3447,13 @@ function NetscriptFunctions(workerScript) {
nsGang.checkGangApiAccess(workerScript, "getOtherGangInformation");
try {
- return Object.assign(AllGangs);
+ // We have to make a deep copy
+ const cpy = {};
+ for (const gang in AllGangs) {
+ cpy[gang] = Object.assign({}, AllGangs[gang]);
+ }
+
+ return cpy;
} catch(e) {
throw makeRuntimeRejectMsg(workerScript, nsGang.unknownGangApiExceptionMessage("getOtherGangInformation", e));
}
@@ -4137,7 +4137,7 @@ function NetscriptFunctions(workerScript) {
// Coding Contract API
codingcontract: {
- attempt: function(answer, fn, ip=workerScript.serverIp) {
+ attempt: function(answer, fn, ip=workerScript.serverIp, { returnReward } = {}) {
updateDynamicRam("attempt", getRamCost("codingcontract", "attempt"));
const contract = getCodingContract(fn, ip);
if (contract == null) {
@@ -4163,7 +4163,7 @@ function NetscriptFunctions(workerScript) {
const reward = Player.gainCodingContractReward(contract.reward, contract.getDifficulty());
workerScript.log(`Successfully completed Coding Contract ${fn}. Reward: ${reward}`);
serv.removeContract(fn);
- return true;
+ return returnReward ? reward : true;
} else {
++contract.tries;
if (contract.tries >= contract.getMaxNumTries()) {
@@ -4172,7 +4172,8 @@ function NetscriptFunctions(workerScript) {
} else {
workerScript.log(`Coding Contract ${fn} failed. ${contract.getMaxNumTries() - contract.tries} attempts remaining`);
}
- return false;
+
+ return returnReward ? "" : false;
}
},
getContractType: function(fn, ip=workerScript.serverIp) {
diff --git a/src/NetscriptWorker.js b/src/NetscriptWorker.js
index b60c8c944..9c519e8b1 100644
--- a/src/NetscriptWorker.js
+++ b/src/NetscriptWorker.js
@@ -413,34 +413,34 @@ function processNetscript1Imports(code, workerScript) {
return res;
}
-//Loop through workerScripts and run every script that is not currently running
+// Loop through workerScripts and run every script that is not currently running
export function runScriptsLoop() {
- var scriptDeleted = false;
+ let scriptDeleted = false;
- //Delete any scripts that finished or have been killed. Loop backwards bc removing items screws up indexing
- for (var i = workerScripts.length - 1; i >= 0; i--) {
+ // Delete any scripts that finished or have been killed. Loop backwards bc removing items screws up indexing
+ for (let i = workerScripts.length - 1; i >= 0; i--) {
if (workerScripts[i].running == false && workerScripts[i].env.stopFlag == true) {
scriptDeleted = true;
- //Delete script from the runningScripts array on its host serverIp
- var ip = workerScripts[i].serverIp;
- var name = workerScripts[i].name;
+ // Delete script from the runningScripts array on its host serverIp
+ const ip = workerScripts[i].serverIp;
+ const name = workerScripts[i].name;
- //recalculate ram used
+ // Recalculate ram used
AllServers[ip].ramUsed = 0;
- for(let j = 0; j < workerScripts.length; j++) {
- if(workerScripts[j].serverIp !== ip) {
- continue
+ for (let j = 0; j < workerScripts.length; j++) {
+ if (workerScripts[j].serverIp !== ip) {
+ continue;
}
- if(j === i) { // not this one
- continue
+ if (j === i) { // not this one
+ continue;
}
AllServers[ip].ramUsed += workerScripts[j].ramUsage;
}
- //Delete script from Active Scripts
+ // Delete script from Active Scripts
deleteActiveScriptsItem(workerScripts[i]);
- for (var j = 0; j < AllServers[ip].runningScripts.length; j++) {
+ for (let j = 0; j < AllServers[ip].runningScripts.length; j++) {
if (AllServers[ip].runningScripts[j].filename == name &&
compareArrays(AllServers[ip].runningScripts[j].args, workerScripts[i].args)) {
AllServers[ip].runningScripts.splice(j, 1);
@@ -448,16 +448,16 @@ export function runScriptsLoop() {
}
}
- //Delete script from workerScripts
+ // Delete script from workerScripts
workerScripts.splice(i, 1);
}
}
- if (scriptDeleted) {updateActiveScriptsItems();} //Force Update
+ if (scriptDeleted) { updateActiveScriptsItems(); } // Force Update
- //Run any scripts that haven't been started
- for (var i = 0; i < workerScripts.length; i++) {
- //If it isn't running, start the script
+ // Run any scripts that haven't been started
+ for (let i = 0; i < workerScripts.length; i++) {
+ // If it isn't running, start the script
if (workerScripts[i].running == false && workerScripts[i].env.stopFlag == false) {
let p = null; // p is the script's result promise.
if (workerScripts[i].name.endsWith(".js") || workerScripts[i].name.endsWith(".ns")) {
@@ -467,8 +467,8 @@ export function runScriptsLoop() {
if (!(p instanceof Promise)) { continue; }
}
- //Once the code finishes (either resolved or rejected, doesnt matter), set its
- //running status to false
+ // Once the code finishes (either resolved or rejected, doesnt matter), set its
+ // running status to false
p.then(function(w) {
console.log("Stopping script " + w.name + " because it finished running naturally");
w.running = false;
@@ -480,9 +480,9 @@ export function runScriptsLoop() {
console.error("Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + w.toString());
return;
} else if (w.constructor === Array && w.length === 2 && w[0] === "RETURNSTATEMENT") {
- //Script ends with a return statement
+ // Script ends with a return statement
console.log("Script returning with value: " + w[1]);
- //TODO maybe do something with this in the future
+ // TODO maybe do something with this in the future
return;
} else if (w instanceof WorkerScript) {
if (isScriptErrorMessage(w.errorMessage)) {
@@ -517,7 +517,7 @@ export function runScriptsLoop() {
}
}
- setTimeoutRef(runScriptsLoop, 6000);
+ setTimeoutRef(runScriptsLoop, 3e3);
}
/**
diff --git a/src/PersonObjects/Player/PlayerObject.js b/src/PersonObjects/Player/PlayerObject.js
index b97f41f7c..fd3044bf5 100644
--- a/src/PersonObjects/Player/PlayerObject.js
+++ b/src/PersonObjects/Player/PlayerObject.js
@@ -1,3 +1,4 @@
+import * as augmentationMethods from "./PlayerObjectAugmentationMethods";
import * as bladeburnerMethods from "./PlayerObjectBladeburnerMethods";
import * as corporationMethods from "./PlayerObjectCorporationMethods";
import * as gangMethods from "./PlayerObjectGangMethods";
@@ -209,7 +210,8 @@ Object.assign(
serverMethods,
bladeburnerMethods,
corporationMethods,
- gangMethods
+ gangMethods,
+ augmentationMethods
);
PlayerObject.prototype.toJSON = function() {
diff --git a/src/PersonObjects/Player/PlayerObjectAugmentationMethods.ts b/src/PersonObjects/Player/PlayerObjectAugmentationMethods.ts
new file mode 100644
index 000000000..bb5cdd708
--- /dev/null
+++ b/src/PersonObjects/Player/PlayerObjectAugmentationMethods.ts
@@ -0,0 +1,20 @@
+/**
+ * Augmentation-related methods for the Player class (PlayerObject)
+ */
+import { IPlayer } from "../IPlayer";
+
+import { Augmentation } from "../../Augmentation/Augmentation";
+
+export function hasAugmentation(this: IPlayer, aug: string | Augmentation): boolean {
+ const augName: string = (aug instanceof Augmentation) ? aug.name : aug;
+
+ for (const owned of this.augmentations) {
+ if (owned.name === augName) { return true; }
+ }
+
+ for (const owned of this.queuedAugmentations) {
+ if (owned.name === augName) { return true; }
+ }
+
+ return false;
+}
diff --git a/src/PersonObjects/Player/PlayerObjectGeneralMethods.js b/src/PersonObjects/Player/PlayerObjectGeneralMethods.js
index 56ebdd0af..c263175d5 100644
--- a/src/PersonObjects/Player/PlayerObjectGeneralMethods.js
+++ b/src/PersonObjects/Player/PlayerObjectGeneralMethods.js
@@ -219,6 +219,11 @@ export function prestigeSourceFile() {
}
}
+ const characterMenuHeader = document.getElementById("character-menu-header");
+ if (characterMenuHeader instanceof HTMLElement) {
+ characterMenuHeader.click(); characterMenuHeader.click();
+ }
+
this.isWorking = false;
this.currentWorkFactionName = "";
this.currentWorkFactionDescription = "";
@@ -1567,7 +1572,9 @@ export function hospitalize() {
);
}
- this.loseMoney(this.max_hp * CONSTANTS.HospitalCostPerHp);
+ const cost = this.max_hp * CONSTANTS.HospitalCostPerHp
+ this.loseMoney(cost);
+ this.recordMoneySource(-1 * cost, "hospitalization");
this.hp = this.max_hp;
}
diff --git a/src/SaveObject.js b/src/SaveObject.js
index 354fcf696..22d5ac4d4 100755
--- a/src/SaveObject.js
+++ b/src/SaveObject.js
@@ -26,6 +26,7 @@ import {
loadSpecialServerIps,
SpecialServerIps
} from "./Server/SpecialServerIps";
+import { SourceFileFlags } from "./SourceFile/SourceFileFlags";
import { loadStockMarket, StockMarket } from "./StockMarket/StockMarket";
import { createStatusText } from "./ui/createStatusText";
@@ -541,23 +542,12 @@ function loadImportedGame(saveObj, saveString) {
}
BitburnerSaveObject.prototype.exportGame = function() {
- this.PlayerSave = JSON.stringify(Player);
- this.AllServersSave = JSON.stringify(AllServers);
- this.CompaniesSave = JSON.stringify(Companies);
- this.FactionsSave = JSON.stringify(Factions);
- this.SpecialServerIpsSave = JSON.stringify(SpecialServerIps);
- this.AliasesSave = JSON.stringify(Aliases);
- this.GlobalAliasesSave = JSON.stringify(GlobalAliases);
- this.MessagesSave = JSON.stringify(Messages);
- this.StockMarketSave = JSON.stringify(StockMarket);
- this.SettingsSave = JSON.stringify(Settings);
- this.VersionSave = JSON.stringify(CONSTANTS.Version);
- if (Player.inGang()) {
- this.AllGangsSave = JSON.stringify(AllGangs);
- }
+ const saveString = this.getSaveString();
- var saveString = btoa(unescape(encodeURIComponent(JSON.stringify(this))));
- var filename = "bitburnerSave.json";
+ // Save file name is based on current timestamp and BitNode
+ const epochTime = Math.round(Date.now() / 1000);
+ const bn = Player.bitNodeN;
+ const filename = `bitburnerSave_BN${bn}x${SourceFileFlags[bn]}_${epochTime}.json`;
var file = new Blob([saveString], {type: 'text/plain'});
if (window.navigator.msSaveOrOpenBlob) {// IE10+
window.navigator.msSaveOrOpenBlob(file, filename);
@@ -565,7 +555,7 @@ BitburnerSaveObject.prototype.exportGame = function() {
var a = document.createElement("a"),
url = URL.createObjectURL(file);
a.href = url;
- a.download = "bitburnerSave.json";
+ a.download = filename;
document.body.appendChild(a);
a.click();
setTimeoutRef(function() {
diff --git a/src/Terminal/DirectoryHelpers.ts b/src/Terminal/DirectoryHelpers.ts
index ae2d425b7..0957b18d0 100644
--- a/src/Terminal/DirectoryHelpers.ts
+++ b/src/Terminal/DirectoryHelpers.ts
@@ -37,8 +37,8 @@ export function removeTrailingSlash(s: string): string {
*/
export function isValidFilename(filename: string): boolean {
// Allows alphanumerics, hyphens, underscores.
- // Must have a file exntesion
- const regex = /^[.a-zA-Z0-9_-]+[.][.a-zA-Z0-9_-]+$/;
+ // Must have a file extension
+ const regex = /^[.a-zA-Z0-9_-]+[.][.a-zA-Z0-9]+$/;
// match() returns null if no match is found
return filename.match(regex) != null;
@@ -98,6 +98,7 @@ export function isValidDirectoryPath(path: string): boolean {
* proper formatting. It dose NOT check if the file actually exists on Terminal
*/
export function isValidFilePath(path: string): boolean {
+ if (path == null || typeof path !== "string") { return false; }
let t_path = path;
// Impossible for filename to have less than length of 3
diff --git a/src/engine.jsx b/src/engine.jsx
index dcc2da58d..f171f9686 100644
--- a/src/engine.jsx
+++ b/src/engine.jsx
@@ -1179,6 +1179,7 @@ const Engine = {
initAugmentations();
initMessages();
initLiterature();
+ updateSourceFileFlags(Player);
// Open main menu accordions for new game
const hackingHdr = document.getElementById("hacking-menu-header");
diff --git a/src/utils/MoneySourceTracker.ts b/src/utils/MoneySourceTracker.ts
index 4ed0a09c2..318407e66 100644
--- a/src/utils/MoneySourceTracker.ts
+++ b/src/utils/MoneySourceTracker.ts
@@ -1,6 +1,6 @@
/**
* This is an object that is used to keep track of where all of the player's
- * money is coming from
+ * money is coming from (or going to)
*/
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
@@ -17,6 +17,7 @@ export class MoneySourceTracker {
gang: number = 0;
hacking: number = 0;
hacknetnode: number = 0;
+ hospitalization: number = 0;
infiltration: number = 0;
stock: number = 0;
total: number = 0;
diff --git a/test/Terminal/DirectoryTests.js b/test/Terminal/DirectoryTests.js
new file mode 100644
index 000000000..c62b28f14
--- /dev/null
+++ b/test/Terminal/DirectoryTests.js
@@ -0,0 +1,291 @@
+import * as dirHelpers from "../../src/Terminal/DirectoryHelpers";
+
+import { expect } from "chai";
+
+console.log("Beginning Terminal Directory Tests");
+
+describe("Terminal Directory Tests", function() {
+ describe("removeLeadingSlash()", function() {
+ const removeLeadingSlash = dirHelpers.removeLeadingSlash;
+
+ it("should remove first slash in a string", function() {
+ expect(removeLeadingSlash("/")).to.equal("");
+ expect(removeLeadingSlash("/foo.txt")).to.equal("foo.txt");
+ expect(removeLeadingSlash("/foo/file.txt")).to.equal("foo/file.txt");
+ });
+
+ it("should only remove one slash", function() {
+ expect(removeLeadingSlash("///")).to.equal("//");
+ expect(removeLeadingSlash("//foo")).to.equal("/foo");
+ });
+
+ it("should do nothing for a string that doesn't start with a slash", function() {
+ expect(removeLeadingSlash("foo.txt")).to.equal("foo.txt");
+ expect(removeLeadingSlash("foo/test.txt")).to.equal("foo/test.txt");
+ });
+
+ it("should not fail on an empty string", function() {
+ expect(removeLeadingSlash.bind(null, "")).to.not.throw();
+ expect(removeLeadingSlash("")).to.equal("");
+ });
+ });
+
+ describe("removeTrailingSlash()", function() {
+ const removeTrailingSlash = dirHelpers.removeTrailingSlash;
+
+ it("should remove last slash in a string", function() {
+ expect(removeTrailingSlash("/")).to.equal("");
+ expect(removeTrailingSlash("foo.txt/")).to.equal("foo.txt");
+ expect(removeTrailingSlash("foo/file.txt/")).to.equal("foo/file.txt");
+ });
+
+ it("should only remove one slash", function() {
+ expect(removeTrailingSlash("///")).to.equal("//");
+ expect(removeTrailingSlash("foo//")).to.equal("foo/");
+ });
+
+ it("should do nothing for a string that doesn't end with a slash", function() {
+ expect(removeTrailingSlash("foo.txt")).to.equal("foo.txt");
+ expect(removeTrailingSlash("foo/test.txt")).to.equal("foo/test.txt");
+ });
+
+ it("should not fail on an empty string", function() {
+ expect(removeTrailingSlash.bind(null, "")).to.not.throw();
+ expect(removeTrailingSlash("")).to.equal("");
+ });
+ });
+
+ describe("isValidFilename()", function() {
+ const isValidFilename = dirHelpers.isValidFilename;
+
+ it("should return true for valid filenames", function() {
+ expect(isValidFilename("test.txt")).to.equal(true);
+ expect(isValidFilename("123.script")).to.equal(true);
+ expect(isValidFilename("foo123.b")).to.equal(true);
+ expect(isValidFilename("my_script.script")).to.equal(true);
+ expect(isValidFilename("my-script.script")).to.equal(true);
+ expect(isValidFilename("_foo.lit")).to.equal(true);
+ expect(isValidFilename("mult.periods.script")).to.equal(true);
+ expect(isValidFilename("mult.per-iods.again.script")).to.equal(true);
+ });
+
+ it("should return false for invalid filenames", function() {
+ expect(isValidFilename("foo")).to.equal(false);
+ expect(isValidFilename("my script.script")).to.equal(false);
+ expect(isValidFilename("a^.txt")).to.equal(false);
+ expect(isValidFilename("b#.lit")).to.equal(false);
+ expect(isValidFilename("lib().js")).to.equal(false);
+ expect(isValidFilename("foo.script_")).to.equal(false);
+ expect(isValidFilename("foo._script")).to.equal(false);
+ expect(isValidFilename("foo.hyphened-ext")).to.equal(false);
+ expect(isValidFilename("")).to.equal(false);
+ });
+ });
+
+ describe("isValidDirectoryName()", function() {
+ const isValidDirectoryName = dirHelpers.isValidDirectoryName;
+
+ it("should return true for valid directory names", function() {
+ expect(isValidDirectoryName("a")).to.equal(true);
+ expect(isValidDirectoryName("foo")).to.equal(true);
+ expect(isValidDirectoryName("foo-dir")).to.equal(true);
+ expect(isValidDirectoryName("foo_dir")).to.equal(true);
+ expect(isValidDirectoryName(".a")).to.equal(true);
+ expect(isValidDirectoryName("1")).to.equal(true);
+ expect(isValidDirectoryName("a1")).to.equal(true);
+ expect(isValidDirectoryName(".a1")).to.equal(true);
+ expect(isValidDirectoryName("._foo")).to.equal(true);
+ expect(isValidDirectoryName("_foo")).to.equal(true);
+ });
+
+ it("should return false for invalid directory names", function() {
+ expect(isValidDirectoryName("")).to.equal(false);
+ expect(isValidDirectoryName("foo.dir")).to.equal(false);
+ expect(isValidDirectoryName("1.")).to.equal(false);
+ expect(isValidDirectoryName("foo.")).to.equal(false);
+ expect(isValidDirectoryName("dir#")).to.equal(false);
+ expect(isValidDirectoryName("dir!")).to.equal(false);
+ expect(isValidDirectoryName("dir*")).to.equal(false);
+ expect(isValidDirectoryName(".")).to.equal(false);
+ });
+ });
+
+ describe("isValidDirectoryPath()", function() {
+ const isValidDirectoryPath = dirHelpers.isValidDirectoryPath;
+
+ it("should return false for empty strings", function() {
+ expect(isValidDirectoryPath("")).to.equal(false);
+ });
+
+ it("should return true only for the forward slash if the string has length 1", function() {
+ expect(isValidDirectoryPath("/")).to.equal(true);
+ expect(isValidDirectoryPath(" ")).to.equal(false);
+ expect(isValidDirectoryPath(".")).to.equal(false);
+ expect(isValidDirectoryPath("a")).to.equal(false);
+ });
+
+ it("should return true for valid directory paths", function() {
+ expect(isValidDirectoryPath("/a")).to.equal(true);
+ expect(isValidDirectoryPath("/dir/a")).to.equal(true);
+ expect(isValidDirectoryPath("/dir/foo")).to.equal(true);
+ expect(isValidDirectoryPath("/.dir/foo-dir")).to.equal(true);
+ expect(isValidDirectoryPath("/.dir/foo_dir")).to.equal(true);
+ expect(isValidDirectoryPath("/.dir/.a")).to.equal(true);
+ expect(isValidDirectoryPath("/dir1/1")).to.equal(true);
+ expect(isValidDirectoryPath("/dir1/a1")).to.equal(true);
+ expect(isValidDirectoryPath("/dir1/.a1")).to.equal(true);
+ expect(isValidDirectoryPath("/dir_/._foo")).to.equal(true);
+ expect(isValidDirectoryPath("/dir-/_foo")).to.equal(true);
+ });
+
+ it("should return false if the path does not have a leading slash", function() {
+ expect(isValidDirectoryPath("a")).to.equal(false);
+ expect(isValidDirectoryPath("dir/a")).to.equal(false);
+ expect(isValidDirectoryPath("dir/foo")).to.equal(false);
+ expect(isValidDirectoryPath(".dir/foo-dir")).to.equal(false);
+ expect(isValidDirectoryPath(".dir/foo_dir")).to.equal(false);
+ expect(isValidDirectoryPath(".dir/.a")).to.equal(false);
+ expect(isValidDirectoryPath("dir1/1")).to.equal(false);
+ expect(isValidDirectoryPath("dir1/a1")).to.equal(false);
+ expect(isValidDirectoryPath("dir1/.a1")).to.equal(false);
+ expect(isValidDirectoryPath("dir_/._foo")).to.equal(false);
+ expect(isValidDirectoryPath("dir-/_foo")).to.equal(false);
+ });
+
+ it("should accept dot notation", function() {
+ expect(isValidDirectoryPath("/dir/./a")).to.equal(true);
+ expect(isValidDirectoryPath("/dir/../foo")).to.equal(true);
+ expect(isValidDirectoryPath("/.dir/./foo-dir")).to.equal(true);
+ expect(isValidDirectoryPath("/.dir/../foo_dir")).to.equal(true);
+ expect(isValidDirectoryPath("/.dir/./.a")).to.equal(true);
+ expect(isValidDirectoryPath("/dir1/1/.")).to.equal(true);
+ expect(isValidDirectoryPath("/dir1/a1/..")).to.equal(true);
+ expect(isValidDirectoryPath("/dir1/.a1/..")).to.equal(true);
+ expect(isValidDirectoryPath("/dir_/._foo/.")).to.equal(true);
+ expect(isValidDirectoryPath("/./dir-/_foo")).to.equal(true);
+ expect(isValidDirectoryPath("/../dir-/_foo")).to.equal(true);
+ });
+ });
+
+ describe("isValidFilePath()", function() {
+ const isValidFilePath = dirHelpers.isValidFilePath;
+
+ it("should return false for strings that are too short", function() {
+ expect(isValidFilePath("/a")).to.equal(false);
+ expect(isValidFilePath("a.")).to.equal(false);
+ expect(isValidFilePath(".a")).to.equal(false);
+ expect(isValidFilePath("/.")).to.equal(false);
+ });
+
+ it("should return true for arguments that are just filenames", function() {
+ expect(isValidFilePath("test.txt")).to.equal(true);
+ expect(isValidFilePath("123.script")).to.equal(true);
+ expect(isValidFilePath("foo123.b")).to.equal(true);
+ expect(isValidFilePath("my_script.script")).to.equal(true);
+ expect(isValidFilePath("my-script.script")).to.equal(true);
+ expect(isValidFilePath("_foo.lit")).to.equal(true);
+ expect(isValidFilePath("mult.periods.script")).to.equal(true);
+ expect(isValidFilePath("mult.per-iods.again.script")).to.equal(true);
+ });
+
+ it("should return true for valid filepaths", function() {
+ expect(isValidFilePath("/foo/test.txt")).to.equal(true);
+ expect(isValidFilePath("/../123.script")).to.equal(true);
+ expect(isValidFilePath("/./foo123.b")).to.equal(true);
+ expect(isValidFilePath("/dir/my_script.script")).to.equal(true);
+ expect(isValidFilePath("/dir1/dir2/dir3/my-script.script")).to.equal(true);
+ expect(isValidFilePath("/dir1/dir2/././../_foo.lit")).to.equal(true);
+ expect(isValidFilePath("/.dir1/./../.dir2/mult.periods.script")).to.equal(true);
+ expect(isValidFilePath("/_dir/../dir2/mult.per-iods.again.script")).to.equal(true);
+ });
+
+ it("should return false for strings that end with a slash", function() {
+ expect(isValidFilePath("/foo/")).to.equal(false);
+ expect(isValidFilePath("foo.txt/")).to.equal(false);
+ expect(isValidFilePath("/")).to.equal(false);
+ expect(isValidFilePath("/_dir/")).to.equal(false);
+ });
+
+ it("should return false for invalid arguments", function() {
+ expect(isValidFilePath(null)).to.equal(false);
+ expect(isValidFilePath()).to.equal(false);
+ expect(isValidFilePath(5)).to.equal(false);
+ expect(isValidFilePath({})).to.equal(false);
+ })
+ });
+
+ describe("getFirstParentDirectory()", function() {
+ const getFirstParentDirectory = dirHelpers.getFirstParentDirectory;
+
+ it("should return the first parent directory in a filepath", function() {
+ expect(getFirstParentDirectory("/dir1/foo.txt")).to.equal("dir1/");
+ expect(getFirstParentDirectory("/dir1/dir2/dir3/dir4/foo.txt")).to.equal("dir1/");
+ expect(getFirstParentDirectory("/_dir1/dir2/foo.js")).to.equal("_dir1/");
+ });
+
+ it("should return '/' if there is no first parent directory", function() {
+ expect(getFirstParentDirectory("")).to.equal("/");
+ expect(getFirstParentDirectory(" ")).to.equal("/");
+ expect(getFirstParentDirectory("/")).to.equal("/");
+ expect(getFirstParentDirectory("//")).to.equal("/");
+ expect(getFirstParentDirectory("foo.script")).to.equal("/");
+ expect(getFirstParentDirectory("/foo.txt")).to.equal("/");
+ });
+ });
+
+ describe("getAllParentDirectories()", function() {
+ const getAllParentDirectories = dirHelpers.getAllParentDirectories;
+
+ it("should return all parent directories in a filepath", function() {
+ expect(getAllParentDirectories("/")).to.equal("/");
+ expect(getAllParentDirectories("/home/var/foo.txt")).to.equal("/home/var/");
+ expect(getAllParentDirectories("/home/var/")).to.equal("/home/var/");
+ expect(getAllParentDirectories("/home/var/test/")).to.equal("/home/var/test/");
+ });
+
+ it("should return an empty string if there are no parent directories", function() {
+ expect(getAllParentDirectories("foo.txt")).to.equal("");
+ });
+ });
+
+ describe("isInRootDirectory()", function() {
+ const isInRootDirectory = dirHelpers.isInRootDirectory;
+
+ it("should return true for filepaths that refer to a file in the root directory", function() {
+ expect(isInRootDirectory("a.b")).to.equal(true);
+ expect(isInRootDirectory("foo.txt")).to.equal(true);
+ expect(isInRootDirectory("/foo.txt")).to.equal(true);
+ });
+
+ it("should return false for filepaths that refer to a file that's NOT in the root directory", function() {
+ expect(isInRootDirectory("/dir/foo.txt")).to.equal(false);
+ expect(isInRootDirectory("dir/foo.txt")).to.equal(false);
+ expect(isInRootDirectory("/./foo.js")).to.equal(false);
+ expect(isInRootDirectory("../foo.js")).to.equal(false);
+ expect(isInRootDirectory("/dir1/dir2/dir3/foo.txt")).to.equal(false);
+ });
+
+ it("should return false for invalid inputs (inputs that aren't filepaths)", function() {
+ expect(isInRootDirectory(null)).to.equal(false);
+ expect(isInRootDirectory(undefined)).to.equal(false);
+ expect(isInRootDirectory("")).to.equal(false);
+ expect(isInRootDirectory(" ")).to.equal(false);
+ expect(isInRootDirectory("a")).to.equal(false);
+ expect(isInRootDirectory("/dir")).to.equal(false);
+ expect(isInRootDirectory("/dir/")).to.equal(false);
+ expect(isInRootDirectory("/dir/foo")).to.equal(false);
+ });
+ });
+
+ describe("evaluateDirectoryPath()", function() {
+ const evaluateDirectoryPath = dirHelpers.evaluateDirectoryPath;
+
+ // TODO
+ });
+
+ describe("evaluateFilePath()", function() {
+ const evaluateFilePath = dirHelpers.evaluateFilePath;
+
+ // TODO
+ })
+});
diff --git a/test/index.js b/test/index.js
index 3724bbc70..68045825f 100644
--- a/test/index.js
+++ b/test/index.js
@@ -1,3 +1,4 @@
export * from "./Netscript/DynamicRamCalculationTests";
export * from "./Netscript/StaticRamCalculationTests";
export * from "./StockMarketTests";
+export * from "./Terminal/DirectoryTests";
diff --git a/utils/YesNoBox.ts b/utils/YesNoBox.ts
index 0a6175c08..467da1fa4 100644
--- a/utils/YesNoBox.ts
+++ b/utils/YesNoBox.ts
@@ -14,7 +14,7 @@ export let yesNoBoxOpen: boolean = false;
const yesNoBoxContainer: HTMLElement | null = document.getElementById("yes-no-box-container");
const yesNoBoxTextElement: HTMLElement | null = document.getElementById("yes-no-box-text");
-export function yesNoBoxHotkeyHandler(e: KeyboardEvent) {
+function yesNoBoxHotkeyHandler(e: KeyboardEvent) {
if (e.keyCode === KEY.ESC) {
yesNoBoxClose();
} else if (e.keyCode === KEY.ENTER) {