From fef7aaba8fe587c800e56d27c371f11345a77c17 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Tue, 14 May 2019 01:35:37 -0700 Subject: [PATCH] Adding more directory-related unit tests. Several more bug fixes and QoL improvements --- doc/source/netscript/basicfunctions/grow.rst | 4 +- doc/source/netscript/basicfunctions/hack.rst | 4 +- .../netscript/basicfunctions/weaken.rst | 4 +- .../netscript/codingcontractapi/attempt.rst | 11 +- src/Augmentation/AugmentationHelpers.js | 7 + src/Bladeburner.js | 2 +- src/CodingContracts.ts | 3 - src/Constants.ts | 9 +- src/Faction/FactionHelpers.jsx | 7 +- src/Gang.js | 2 +- src/Hacknet/HacknetHelpers.jsx | 4 +- src/Netscript/RamCostGenerator.ts | 2 +- src/NetscriptFunctions.js | 39 +-- src/NetscriptWorker.js | 50 +-- src/PersonObjects/Player/PlayerObject.js | 4 +- .../Player/PlayerObjectAugmentationMethods.ts | 20 ++ .../Player/PlayerObjectGeneralMethods.js | 9 +- src/SaveObject.js | 24 +- src/Terminal/DirectoryHelpers.ts | 5 +- src/engine.jsx | 1 + src/utils/MoneySourceTracker.ts | 3 +- test/Terminal/DirectoryTests.js | 291 ++++++++++++++++++ test/index.js | 1 + utils/YesNoBox.ts | 2 +- 24 files changed, 423 insertions(+), 85 deletions(-) create mode 100644 src/PersonObjects/Player/PlayerObjectAugmentationMethods.ts create mode 100644 test/Terminal/DirectoryTests.js 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) {