diff --git a/src/ActiveScriptsUI.js b/src/ActiveScriptsUI.js index cb86c0b1a..1cdc66b26 100644 --- a/src/ActiveScriptsUI.js +++ b/src/ActiveScriptsUI.js @@ -1,7 +1,7 @@ import {workerScripts, killWorkerScript} from "./NetscriptWorker"; -import {Player} from "./Player"; -import {getServer} from "./Server"; +import { Player } from "./Player"; +import { getServer } from "./Server/ServerHelpers"; import {numeralWrapper} from "./ui/numeralFormat"; import {dialogBoxCreate} from "../utils/DialogBox"; import {createAccordionElement} from "../utils/uiHelpers/createAccordionElement"; diff --git a/src/Augmentation/AugmentationHelpers.js b/src/Augmentation/AugmentationHelpers.js index b1cd629df..af405e93e 100644 --- a/src/Augmentation/AugmentationHelpers.js +++ b/src/Augmentation/AugmentationHelpers.js @@ -12,9 +12,9 @@ import { addWorkerScript } from "../NetscriptWorker"; import { Player } from "../Player"; import { prestigeAugmentation } from "../Prestige"; import { saveObject } from "../SaveObject"; -import { Script, - RunningScript} from "../Script"; -import { Server } from "../Server"; +import { RunningScript } from "../Script/RunningScript"; +import { Script } from "../Script/Script"; +import { Server } from "../Server/Server"; import { OwnedAugmentationsOrderSetting } from "../Settings/SettingEnums"; import { Settings } from "../Settings/Settings"; diff --git a/src/CodingContractGenerator.js b/src/CodingContractGenerator.js index 8cf9fdda2..1538703bd 100644 --- a/src/CodingContractGenerator.js +++ b/src/CodingContractGenerator.js @@ -3,8 +3,8 @@ import { CodingContract, CodingContractTypes } from "./CodingContracts"; import { Factions } from "./Faction/Factions"; import { Player } from "./Player"; -import { GetServerByHostname, - AllServers } from "./Server"; +import { AllServers } from "./Server/Server"; +import { GetServerByHostname } from "./Server/ServerHelpers"; import { getRandomInt } from "../utils/helpers/getRandomInt"; diff --git a/src/DarkWeb/DarkWeb.js b/src/DarkWeb/DarkWeb.js index 394e31982..b8a5cdf89 100644 --- a/src/DarkWeb/DarkWeb.js +++ b/src/DarkWeb/DarkWeb.js @@ -1,7 +1,7 @@ import { DarkWebItems } from "./DarkWebItems"; import { Player } from "../Player"; -import { SpecialServerIps } from "../SpecialServerIps"; +import { SpecialServerIps } from "../Server/SpecialServerIps"; import { post } from "../ui/postToTerminal"; import { isValidIPAddress } from "../../utils/helpers/isValidIPAddress"; diff --git a/src/DevMenu.js b/src/DevMenu.js index 03b72f108..95591491c 100644 --- a/src/DevMenu.js +++ b/src/DevMenu.js @@ -8,7 +8,7 @@ import { Company } from "./Company/Company"; import { Programs } from "./Programs/Programs"; import { Factions } from "./Faction/Factions"; import { Player } from "./Player"; -import { AllServers } from "./Server"; +import { AllServers } from "./Server/AllServers"; import { hackWorldDaemon } from "./RedPill"; import { StockMarket, SymbolToStockMap } from "./StockMarket/StockMarket"; diff --git a/src/Fconf.js b/src/Fconf/Fconf.js similarity index 95% rename from src/Fconf.js rename to src/Fconf/Fconf.js index 6f125c26c..f40bcc8ce 100644 --- a/src/Fconf.js +++ b/src/Fconf/Fconf.js @@ -1,16 +1,7 @@ -import {parse, Node} from "../utils/acorn"; -import {dialogBoxCreate} from "../utils/DialogBox"; +import { FconfSettings } from "./FconfSettings"; -var FconfSettings = { - ENABLE_BASH_HOTKEYS: false, - ENABLE_TIMESTAMPS: false, - MAIN_MENU_STYLE: "default", - THEME_BACKGROUND_COLOR: "#000000", - THEME_FONT_COLOR: "#66ff33", - THEME_HIGHLIGHT_COLOR: "#ffffff", - THEME_PROMPT_COLOR: "#f92672", - WRAP_INPUT: false, -} +import { parse, Node } from "../../utils/acorn"; +import { dialogBoxCreate } from "../../utils/DialogBox"; var FconfComments = { ENABLE_BASH_HOTKEYS: "Improved Bash emulation mode. Setting this to 1 enables several\n" + diff --git a/src/Fconf/FconfSettings.ts b/src/Fconf/FconfSettings.ts new file mode 100644 index 000000000..f1c08bc73 --- /dev/null +++ b/src/Fconf/FconfSettings.ts @@ -0,0 +1,10 @@ +export const FconfSettings = { + ENABLE_BASH_HOTKEYS: false, + ENABLE_TIMESTAMPS: false, + MAIN_MENU_STYLE: "default", + THEME_BACKGROUND_COLOR: "#000000", + THEME_FONT_COLOR: "#66ff33", + THEME_HIGHLIGHT_COLOR: "#ffffff", + THEME_PROMPT_COLOR: "#f92672", + WRAP_INPUT: false, +} diff --git a/src/Hacking.js b/src/Hacking.js index 452d38120..893ca3779 100644 --- a/src/Hacking.js +++ b/src/Hacking.js @@ -1,6 +1,6 @@ import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers"; import { Player } from "./Player"; -import { Server } from "./Server"; +import { Server } from "./Server/Server"; /** * Returns the chance the player has to successfully hack a server diff --git a/src/Location.js b/src/Location.js index b00adc353..42caaa0ba 100644 --- a/src/Location.js +++ b/src/Location.js @@ -11,13 +11,16 @@ import {beginInfiltration} from "./Infiltration"; import {hasBladeburnerSF} from "./NetscriptFunctions"; import {Locations} from "./Locations"; import {Player} from "./Player"; -import {Server, AllServers, AddToAllServers} from "./Server"; +import { AllServers } from "./Server/AllServers"; +import { Server } from "./Server/Server"; +import { AddToAllServers } from "./Server/ServerHelpers"; import { getPurchaseServerCost, purchaseServer, - purchaseRamForHomeComputer} from "./ServerPurchases"; + purchaseRamForHomeComputer } from "./Server/ServerPurchases"; import {Settings} from "./Settings/Settings"; import { SourceFileFlags } from "./SourceFile/SourceFileFlags"; -import {SpecialServerNames, SpecialServerIps} from "./SpecialServerIps"; +import { SpecialServerNames, + SpecialServerIps } from "./Server/SpecialServerIps"; import {numeralWrapper} from "./ui/numeralFormat"; diff --git a/src/Message/Message.ts b/src/Message/Message.ts index 1c56ecd8b..90e24aa2c 100644 --- a/src/Message/Message.ts +++ b/src/Message/Message.ts @@ -12,7 +12,7 @@ export class Message { filename: string = ""; // The text contains in the Message - msg: string = "": + msg: string = ""; // Flag indicating whether this Message has been received by the player recvd: boolean = false; diff --git a/src/Message/MessageHelpers.js b/src/Message/MessageHelpers.js index 8fc056fb9..4845e6a30 100644 --- a/src/Message/MessageHelpers.js +++ b/src/Message/MessageHelpers.js @@ -6,7 +6,7 @@ import { Programs } from "../Programs/Programs"; import { inMission } from "../Missions"; import { Player } from "../Player"; import { redPillFlag } from "../RedPill"; -import { GetServerByHostname } from "../Server"; +import { GetServerByHostname } from "../Server/ServerHelpers"; import { Settings } from "../Settings/Settings"; import { dialogBoxCreate, dialogBoxOpened} from "../../utils/DialogBox"; diff --git a/src/NetscriptEvaluator.js b/src/NetscriptEvaluator.js index 8e144ac15..c301f2b76 100644 --- a/src/NetscriptEvaluator.js +++ b/src/NetscriptEvaluator.js @@ -3,10 +3,12 @@ import { CONSTANTS } from "./Constants"; import { Player } from "./Player"; import { Environment } from "./NetscriptEnvironment"; import { WorkerScript, addWorkerScript} from "./NetscriptWorker"; -import { Server, getServer} from "./Server"; +import { Server } from "./Server/Server"; +import { getServer } from "./Server/ServerHelpers"; import { Settings } from "./Settings/Settings"; -import { Script, findRunningScript, - RunningScript } from "./Script"; +import { RunningScript } from "./Script/RunningScript"; +import { Script } from "./Script/Script"; +import { findRunningScript } from "./Script/ScriptHelpers"; import { setTimeoutRef } from "./utils/SetTimeoutRef"; diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 9b7e6a56a..ddc833cf6 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -31,20 +31,26 @@ import { joinFaction, import { getCostOfNextHacknetNode, purchaseHacknet } from "./HacknetNode"; import {Locations} from "./Locations"; -import {Message, Messages} from "./Message"; +import { Message } from "./Message/Message"; +import { Messages } from "./Message/MessageHelpers"; import {inMission} from "./Missions"; import {Player} from "./Player"; import { Programs } from "./Programs/Programs"; -import {Script, findRunningScript, RunningScript, - isScriptFilename} from "./Script"; -import {Server, getServer, AddToAllServers, - AllServers, processSingleServerGrowth, - GetServerByHostname, numCycleForGrowth} from "./Server"; +import { Script } from "./Script/Script"; +import { findRunningScript } from "./Script/ScriptHelpers"; +import { isScriptFilename } from "./Script/ScriptHelpersTS"; +import { AllServers, + AddToAllServers } from "./Server/AllServers"; +import { Server } from "./Server/Server"; +import { GetServerByHostname, + getServer, + numCycleForGrowth, + processSingleServerGrowth } from "./Server/ServerHelpers"; import { getPurchaseServerCost, getPurchaseServerLimit, - getPurchaseServerMaxRam } from "./ServerPurchases"; + getPurchaseServerMaxRam } from "./Server/ServerPurchases"; import {Settings} from "./Settings/Settings"; -import {SpecialServerIps} from "./SpecialServerIps"; +import {SpecialServerIps} from "./Server/SpecialServerIps"; import {Stock} from "./StockMarket/Stock"; import {StockMarket, StockSymbols, SymbolToStockMap, initStockMarket, initSymbolToStockMap, buyStock, @@ -305,9 +311,9 @@ function NetscriptFunctions(workerScript) { for (var i = 0; i < server.serversOnNetwork.length; i++) { var entry; if (hostnames) { - entry = server.getServerOnNetwork(i).hostname; + entry = getServerOnNetwork(server, i).hostname; } else { - entry = server.getServerOnNetwork(i).ip; + entry = getServerOnNetwork(server, i).ip; } if (entry == null) { continue; @@ -483,7 +489,7 @@ function NetscriptFunctions(workerScript) { if (workerScript.env.stopFlag) {return Promise.reject(workerScript);} const moneyBefore = server.moneyAvailable <= 0 ? 1 : server.moneyAvailable; server.moneyAvailable += (1 * threads); //It can be grown even if it has no money - var growthPercentage = processSingleServerGrowth(server, 450 * threads); + var growthPercentage = processSingleServerGrowth(server, 450 * threads, Player); const moneyAfter = server.moneyAvailable; workerScript.scriptRef.recordGrow(server.ip, threads); var expGain = calculateHackingExpGain(server) * threads; @@ -512,7 +518,7 @@ function NetscriptFunctions(workerScript) { throw makeRuntimeRejectMsg(workerScript, `Invalid growth argument passed into growthAnalyze: ${growth}. Must be numeric`); } - return numCycleForGrowth(server, Number(growth)); + return numCycleForGrowth(server, Number(growth), Player); }, weaken : function(ip) { if (workerScript.checkingRam) { diff --git a/src/NetscriptWorker.js b/src/NetscriptWorker.js index 936dc82f6..e698166f8 100644 --- a/src/NetscriptWorker.js +++ b/src/NetscriptWorker.js @@ -11,7 +11,7 @@ import {evaluate, isScriptErrorMessage, import {NetscriptFunctions} from "./NetscriptFunctions"; import {executeJSScript} from "./NetscriptJSEvaluator"; import {NetscriptPort} from "./NetscriptPort"; -import {AllServers} from "./Server"; +import { AllServers } from "./Server/AllServers"; import {Settings} from "./Settings/Settings"; import { setTimeoutRef } from "./utils/SetTimeoutRef"; diff --git a/src/PersonObjects/IPlayer.ts b/src/PersonObjects/IPlayer.ts index 948b72e22..c41247a2e 100644 --- a/src/PersonObjects/IPlayer.ts +++ b/src/PersonObjects/IPlayer.ts @@ -20,6 +20,7 @@ export interface IPlayer { city: string; companyName: string; corporation: any; + currentServer: string; factions: string[]; hacknetNodes: any[]; hasWseAccount: boolean; diff --git a/src/Player.js b/src/Player.js index 25c19af5f..a95234937 100644 --- a/src/Player.js +++ b/src/Player.js @@ -24,9 +24,11 @@ import {Gang, resetGangs} from "./Gang"; import {Locations} from "./Locations"; import {hasBn11SF, hasWallStreetSF,hasAISF} from "./NetscriptFunctions"; import { Sleeve } from "./PersonObjects/Sleeve/Sleeve"; -import {AllServers, Server, AddToAllServers} from "./Server"; +import { AllServers } from "./Server/AllServers"; +import { Server } from "./Server/Server"; +import { AddToAllServers } from "./Server/ServerHelpers"; import {Settings} from "./Settings/Settings"; -import {SpecialServerIps, SpecialServerNames} from "./SpecialServerIps"; +import {SpecialServerIps, SpecialServerNames} from "./Server/SpecialServerIps"; import {SourceFiles, applySourceFile} from "./SourceFile"; import { SourceFileFlags } from "./SourceFile/SourceFileFlags"; import Decimal from "decimal.js"; diff --git a/src/Prestige.js b/src/Prestige.js index 3e46c03f1..1b3ba168d 100755 --- a/src/Prestige.js +++ b/src/Prestige.js @@ -16,20 +16,25 @@ import { Factions, import { joinFaction } from "./Faction/FactionHelpers"; import {deleteGangDisplayContent} from "./Gang"; import {Locations} from "./Location"; -import {initMessages, Messages, Message} from "./Message"; +import { initMessages, + Messages, + Message } from "./Message/MessageHelpers"; import {initSingularitySFFlags, hasWallStreetSF}from "./NetscriptFunctions"; import {WorkerScript, workerScripts, prestigeWorkerScripts} from "./NetscriptWorker"; import {Player} from "./Player"; -import {AllServers, AddToAllServers, - initForeignServers, Server, - prestigeAllServers, - prestigeHomeComputer} from "./Server"; +import { AllServers } from "./Server/AllServers"; +import { Server } from "./Server/Server" +import { AddToAllServers, + initForeignServers, + prestigeAllServers, + prestigeHomeComputer } from "./Server/ServerHelpers"; import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags"; -import {SpecialServerIps, SpecialServerIpsMap, - prestigeSpecialServerIps, - SpecialServerNames} from "./SpecialServerIps"; +import { SpecialServerIps, + SpecialServerIpsMap, + prestigeSpecialServerIps, + SpecialServerNames} from "./Server/SpecialServerIps"; import {initStockMarket, initSymbolToStockMap, stockMarketContentCreated, setStockMarketContentCreated} from "./StockMarket/StockMarket"; @@ -89,7 +94,7 @@ function prestigeAugmentation() { } //Re-create foreign servers - initForeignServers(); + initForeignServers(Player.getHomeComputer()); //Darkweb is purchase-able document.getElementById("location-purchase-tor").setAttribute("class", "a-link-button"); @@ -194,7 +199,7 @@ function prestigeSourceFile() { prestigeHomeComputer(homeComp); //Re-create foreign servers - initForeignServers(); + initForeignServers(Player.getHomeComputer()); var srcFile1Owned = false; for (var i = 0; i < Player.sourceFiles.length; ++i) { diff --git a/src/SaveObject.js b/src/SaveObject.js index 5667b1efb..e59c0f777 100755 --- a/src/SaveObject.js +++ b/src/SaveObject.js @@ -7,15 +7,18 @@ import {Engine} from "./engine"; import { Factions, loadFactions } from "./Faction/Factions"; import { processPassiveFactionRepGain } from "./Faction/FactionHelpers"; -import {FconfSettings, loadFconf} from "./Fconf"; +import { loadFconf } from "./Fconf/Fconf"; +import { FconfSettings } from "./Fconf/FconfSettings"; import {loadAllGangs, AllGangs} from "./Gang"; import {processAllHacknetNodeEarnings} from "./HacknetNode"; -import {loadMessages, initMessages, Messages} from "./Message"; +import { loadMessages, initMessages, Messages } from "./Message/MessageHelpers"; import {Player, loadPlayer} from "./Player"; -import {loadAllRunningScripts} from "./Script"; -import {AllServers, loadAllServers} from "./Server"; -import {Settings} from "./Settings/Settings"; -import {loadSpecialServerIps, SpecialServerIps} from "./SpecialServerIps"; +import { loadAllRunningScripts } from "./Script/ScriptHelpers"; +import { AllServers } from "./Server/AllServers"; +import { loadAllServers } from "./Server/ServerHelpers"; +import { Settings } from "./Settings/Settings"; +import { loadSpecialServerIps, + SpecialServerIps } from "./Server/SpecialServerIps"; import {loadStockMarket, StockMarket} from "./StockMarket/StockMarket"; import { setTimeoutRef } from "./utils/SetTimeoutRef"; diff --git a/src/Script/RamCalculations.d.ts b/src/Script/RamCalculations.d.ts new file mode 100644 index 000000000..0a886c842 --- /dev/null +++ b/src/Script/RamCalculations.d.ts @@ -0,0 +1 @@ +export declare function calculateRamUsage(codeCopy: string): number; diff --git a/src/Script/RamCalculations.js b/src/Script/RamCalculations.js new file mode 100644 index 000000000..643845a8e --- /dev/null +++ b/src/Script/RamCalculations.js @@ -0,0 +1,409 @@ +// Calculate a script's RAM usage +const walk = require("acorn/dist/walk"); // Importing this doesn't work for some reason. + +import { CONSTANTS } from "../Constants"; +import {evaluateImport} from "../NetscriptEvaluator"; +import { WorkerScript } from "../NetscriptWorker"; +import { Player } from "../Player"; +import {parse, Node} from "../../utils/acorn"; + +// These special strings are used to reference the presence of a given logical +// construct within a user script. +const specialReferenceIF = "__SPECIAL_referenceIf"; +const specialReferenceFOR = "__SPECIAL_referenceFor"; +const specialReferenceWHILE = "__SPECIAL_referenceWhile"; + +// The global scope of a script is registered under this key during parsing. +const memCheckGlobalKey = ".__GLOBAL__"; + +// Calcluates the amount of RAM a script uses. Uses parsing and AST walking only, +// rather than NetscriptEvaluator. This is useful because NetscriptJS code does +// not work under NetscriptEvaluator. +async function parseOnlyRamCalculate(server, code, workerScript) { + try { + // Maps dependent identifiers to their dependencies. + // + // The initial identifier is __SPECIAL_INITIAL_MODULE__.__GLOBAL__. + // It depends on all the functions declared in the module, all the global scopes + // of its imports, and any identifiers referenced in this global scope. Each + // function depends on all the identifiers referenced internally. + // We walk the dependency graph to calculate RAM usage, given that some identifiers + // reference Netscript functions which have a RAM cost. + let dependencyMap = {}; + + // Scripts we've parsed. + const completedParses = new Set(); + + // Scripts we've discovered that need to be parsed. + const parseQueue = []; + + // Parses a chunk of code with a given module name, and updates parseQueue and dependencyMap. + function parseCode(code, moduleName) { + const result = parseOnlyCalculateDeps(code, moduleName); + completedParses.add(moduleName); + + // Add any additional modules to the parse queue; + for (let i = 0; i < result.additionalModules.length; ++i) { + if (!completedParses.has(result.additionalModules[i])) { + parseQueue.push(result.additionalModules[i]); + } + } + + // Splice all the references in. + //Spread syntax not supported in edge, use Object.assign instead + //dependencyMap = {...dependencyMap, ...result.dependencyMap}; + dependencyMap = Object.assign(dependencyMap, result.dependencyMap); + } + + const initialModule = "__SPECIAL_INITIAL_MODULE__"; + parseCode(code, initialModule); + + while (parseQueue.length > 0) { + // Get the code from the server. + const nextModule = parseQueue.shift(); + + let code; + if (nextModule.startsWith("https://") || nextModule.startsWith("http://")) { + try { + const module = await eval('import(nextModule)'); + code = ""; + for (const prop in module) { + if (typeof module[prop] === 'function') { + code += module[prop].toString() + ";\n"; + } + } + } catch(e) { + console.error(`Error dynamically importing module from ${nextModule} for RAM calculations: ${e}`); + return -1; + } + } else { + const script = server.getScript(nextModule.startsWith("./") ? nextModule.slice(2) : nextModule); + if (!script) { + console.warn("Invalid script"); + return -1; // No such script on the server. + } + code = script.code; + } + + parseCode(code, nextModule); + } + + // Finally, walk the reference map and generate a ram cost. The initial set of keys to scan + // are those that start with __SPECIAL_INITIAL_MODULE__. + let ram = CONSTANTS.ScriptBaseRamCost; + const unresolvedRefs = Object.keys(dependencyMap).filter(s => s.startsWith(initialModule)); + const resolvedRefs = new Set(); + while (unresolvedRefs.length > 0) { + const ref = unresolvedRefs.shift(); + + // Check if this is one of the special keys, and add the appropriate ram cost if so. + if (ref === "hacknet" && !resolvedRefs.has("hacknet")) { + ram += CONSTANTS.ScriptHacknetNodesRamCost; + } + if (ref === "document" && !resolvedRefs.has("document")) { + ram += CONSTANTS.ScriptDomRamCost; + } + if (ref === "window" && !resolvedRefs.has("window")) { + ram += CONSTANTS.ScriptDomRamCost; + } + + resolvedRefs.add(ref); + + if (ref.endsWith(".*")) { + // A prefix reference. We need to find all matching identifiers. + const prefix = ref.slice(0, ref.length - 2); + for (let ident of Object.keys(dependencyMap).filter(k => k.startsWith(prefix))) { + for (let dep of dependencyMap[ident] || []) { + if (!resolvedRefs.has(dep)) unresolvedRefs.push(dep); + } + } + } else { + // An exact reference. Add all dependencies of this ref. + for (let dep of dependencyMap[ref] || []) { + if (!resolvedRefs.has(dep)) unresolvedRefs.push(dep); + } + } + + // Check if this ident is a function in the workerscript env. If it is, then we need to + // get its RAM cost. We do this by calling it, which works because the running script + // is in checkingRam mode. + // + // TODO it would be simpler to just reference a dictionary. + try { + function applyFuncRam(func) { + if (typeof func === "function") { + try { + let res; + if (func.constructor.name === "AsyncFunction") { + res = 0; // Async functions will always be 0 RAM + } else { + res = func.apply(null, []); + } + if (typeof res === "number") { + return res; + } + return 0; + } catch(e) { + console.log("ERROR applying function: " + e); + return 0; + } + } else { + return 0; + } + } + + //Special logic for namespaces (Bladeburner, CodingCOntract) + var func; + if (ref in workerScript.env.vars.bladeburner) { + func = workerScript.env.vars.bladeburner[ref]; + } else if (ref in workerScript.env.vars.codingcontract) { + func = workerScript.env.vars.codingcontract[ref]; + } else if (ref in workerScript.env.vars.gang) { + func = workerScript.env.vars.gang[ref]; + } else { + func = workerScript.env.get(ref); + } + ram += applyFuncRam(func); + } catch (error) {continue;} + } + return ram; + + } catch (error) { + // console.info("parse or eval error: ", error); + // This is not unexpected. The user may be editing a script, and it may be in + // a transitory invalid state. + return -1; + } +} + +// Parses one script and calculates its ram usage, for the global scope and each function. +// Returns a cost map and a dependencyMap for the module. Returns a reference map to be joined +// onto the main reference map, and a list of modules that need to be parsed. +function parseOnlyCalculateDeps(code, currentModule) { + const ast = parse(code, {sourceType:"module", ecmaVersion: 8}); + + // Everything from the global scope goes in ".". Everything else goes in ".function", where only + // the outermost layer of functions counts. + const globalKey = currentModule + memCheckGlobalKey; + const dependencyMap = {}; + dependencyMap[globalKey] = new Set(); + + // If we reference this internal name, we're really referencing that external name. + // Filled when we import names from other modules. + let internalToExternal = {}; + + var additionalModules = []; + + // References get added pessimistically. They are added for thisModule.name, name, and for + // any aliases. + function addRef(key, name) { + const s = dependencyMap[key] || (dependencyMap[key] = new Set()); + if (name in internalToExternal) { + s.add(internalToExternal[name]); + } + s.add(currentModule + "." + name); + s.add(name); // For builtins like hack. + } + + //A list of identifiers that resolve to "native Javascript code" + const objectPrototypeProperties = Object.getOwnPropertyNames(Object.prototype); + + // If we discover a dependency identifier, state.key is the dependent identifier. + // walkDeeper is for doing recursive walks of expressions in composites that we handle. + function commonVisitors() { + return { + Identifier: (node, st, walkDeeper) => { + if (objectPrototypeProperties.includes(node.name)) {return;} + addRef(st.key, node.name); + }, + WhileStatement: (node, st, walkDeeper) => { + addRef(st.key, specialReferenceWHILE); + node.test && walkDeeper(node.test, st); + node.body && walkDeeper(node.body, st); + }, + DoWhileStatement: (node, st, walkDeeper) => { + addRef(st.key, specialReferenceWHILE); + node.test && walkDeeper(node.test, st); + node.body && walkDeeper(node.body, st); + }, + ForStatement: (node, st, walkDeeper) => { + addRef(st.key, specialReferenceFOR); + node.init && walkDeeper(node.init, st); + node.test && walkDeeper(node.test, st); + node.update && walkDeeper(node.update, st); + node.body && walkDeeper(node.body, st); + }, + IfStatement: (node, st, walkDeeper) => { + addRef(st.key, specialReferenceIF); + node.test && walkDeeper(node.test, st); + node.consequent && walkDeeper(node.consequent, st); + node.alternate && walkDeeper(node.alternate, st); + }, + MemberExpression: (node, st, walkDeeper) => { + node.object && walkDeeper(node.object, st); + node.property && walkDeeper(node.property, st); + }, + } + } + + //Spread syntax not supported in Edge yet, use Object.assign + /* + walk.recursive(ast, {key: globalKey}, { + ImportDeclaration: (node, st, walkDeeper) => { + const importModuleName = node.source.value; + additionalModules.push(importModuleName); + + // This module's global scope refers to that module's global scope, no matter how we + // import it. + dependencyMap[st.key].add(importModuleName + memCheckGlobalKey); + + for (let i = 0; i < node.specifiers.length; ++i) { + const spec = node.specifiers[i]; + if (spec.imported !== undefined && spec.local !== undefined) { + // We depend on specific things. + internalToExternal[spec.local.name] = importModuleName + "." + spec.imported.name; + } else { + // We depend on everything. + dependencyMap[st.key].add(importModuleName + ".*"); + } + } + }, + FunctionDeclaration: (node, st, walkDeeper) => { + // Don't use walkDeeper, because we are changing the visitor set. + const key = currentModule + "." + node.id.name; + walk.recursive(node, {key: key}, commonVisitors()); + }, + ...commonVisitors() + }); + */ + walk.recursive(ast, {key: globalKey}, Object.assign({ + ImportDeclaration: (node, st, walkDeeper) => { + const importModuleName = node.source.value; + additionalModules.push(importModuleName); + + // This module's global scope refers to that module's global scope, no matter how we + // import it. + dependencyMap[st.key].add(importModuleName + memCheckGlobalKey); + + for (let i = 0; i < node.specifiers.length; ++i) { + const spec = node.specifiers[i]; + if (spec.imported !== undefined && spec.local !== undefined) { + // We depend on specific things. + internalToExternal[spec.local.name] = importModuleName + "." + spec.imported.name; + } else { + // We depend on everything. + dependencyMap[st.key].add(importModuleName + ".*"); + } + } + }, + FunctionDeclaration: (node, st, walkDeeper) => { + // Don't use walkDeeper, because we are changing the visitor set. + const key = currentModule + "." + node.id.name; + walk.recursive(node, {key: key}, commonVisitors()); + }, + }, commonVisitors())); + + return {dependencyMap: dependencyMap, additionalModules: additionalModules}; +} + +export async function calculateRamUsage(codeCopy) { + //Create a temporary/mock WorkerScript and an AST from the code + var currServ = Player.getCurrentServer(); + var workerScript = new WorkerScript({ + filename:"foo", + scriptRef: {code:""}, + args:[], + getCode: function() { return ""; } + }); + workerScript.checkingRam = true; //Netscript functions will return RAM usage + workerScript.serverIp = currServ.ip; + + try { + return await parseOnlyRamCalculate(currServ, codeCopy, workerScript); + } catch (e) { + console.log("Failed to parse ram using new method. Falling back.", e); + } + + // Try the old way. + + try { + var ast = parse(codeCopy, {sourceType:"module"}); + } catch(e) { + return -1; + } + + //Search through AST, scanning for any 'Identifier' nodes for functions, or While/For/If nodes + var queue = [], ramUsage = CONSTANTS.ScriptBaseRamCost; + var whileUsed = false, forUsed = false, ifUsed = false; + queue.push(ast); + while (queue.length != 0) { + var exp = queue.shift(); + switch (exp.type) { + case "ImportDeclaration": + //Gets an array of all imported functions as AST expressions + //and pushes them on the queue. + var res = evaluateImport(exp, workerScript, true); + for (var i = 0; i < res.length; ++i) { + queue.push(res[i]); + } + break; + case "BlockStatement": + case "Program": + for (var i = 0; i < exp.body.length; ++i) { + if (exp.body[i] instanceof Node) { + queue.push(exp.body[i]); + } + } + break; + case "WhileStatement": + if (!whileUsed) { + ramUsage += CONSTANTS.ScriptWhileRamCost; + whileUsed = true; + } + break; + case "ForStatement": + if (!forUsed) { + ramUsage += CONSTANTS.ScriptForRamCost; + forUsed = true; + } + break; + case "IfStatement": + if (!ifUsed) { + ramUsage += CONSTANTS.ScriptIfRamCost; + ifUsed = true; + } + break; + case "Identifier": + if (exp.name in workerScript.env.vars) { + var func = workerScript.env.get(exp.name); + if (typeof func === "function") { + try { + var res = func.apply(null, []); + if (typeof res === "number") { + ramUsage += res; + } + } catch(e) { + console.log("ERROR applying function: " + e); + } + } + } + break; + default: + break; + } + + for (var prop in exp) { + if (exp.hasOwnProperty(prop)) { + if (exp[prop] instanceof Node) { + queue.push(exp[prop]); + } + } + } + } + + //Special case: hacknetnodes array + if (codeCopy.includes("hacknet")) { + ramUsage += CONSTANTS.ScriptHacknetNodesRamCost; + } + return ramUsage; +} diff --git a/src/Script/RunningScript.ts b/src/Script/RunningScript.ts index c20e3e5f5..9da52d47b 100644 --- a/src/Script/RunningScript.ts +++ b/src/Script/RunningScript.ts @@ -1,10 +1,16 @@ // Class representing a Script instance that is actively running. // A Script can have multiple active instances -import { Script } from "./Script"; -import { IMap } from "../types"; +import { Script } from "./Script"; +import { FconfSettings } from "../Fconf/FconfSettings"; +import { AllServers } from "../Server/AllServers"; +import { Settings } from "../Settings/Settings"; +import { IMap } from "../types"; +import { post } from "../ui/postToTerminal"; + import { Generic_fromJSON, Generic_toJSON, - Reviver } from "../../utils/JSONReviver"; + Reviver } from "../../utils/JSONReviver"; +import { getTimestamp } from "../../utils/helpers/getTimestamp"; export class RunningScript { // Initializes a RunningScript Object from a JSON save state @@ -67,7 +73,7 @@ export class RunningScript { this.ramUsage = script.ramUsage; } - RunningScript.prototype.getCode = function() { + getCode(): string { const server = AllServers[this.server]; if (server == null) { return ""; } for (let i = 0; i < server.scripts.length; ++i) { @@ -79,7 +85,7 @@ export class RunningScript { return ""; } - RunningScript.prototype.getRamUsage = function() { + getRamUsage(): number { if (this.ramUsage != null && this.ramUsage > 0) { return this.ramUsage; } // Use cached value const server = AllServers[this.server]; @@ -96,7 +102,7 @@ export class RunningScript { return 0; } - RunningScript.prototype.log = function(txt) { + log(txt: string): void { if (this.logs.length > Settings.MaxLogCapacity) { //Delete first element and add new log entry to the end. //TODO Eventually it might be better to replace this with circular array @@ -111,18 +117,18 @@ export class RunningScript { this.logUpd = true; } - RunningScript.prototype.displayLog = function() { + displayLog(): void { for (var i = 0; i < this.logs.length; ++i) { post(this.logs[i]); } } - RunningScript.prototype.clearLog = function() { + clearLog(): void { this.logs.length = 0; } - //Update the moneyStolen and numTimesHack maps when hacking - RunningScript.prototype.recordHack = function(serverIp, moneyGained, n=1) { + // Update the moneyStolen and numTimesHack maps when hacking + recordHack(serverIp: string, moneyGained: number, n: number=1) { if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) { this.dataMap[serverIp] = [0, 0, 0, 0]; } @@ -130,16 +136,16 @@ export class RunningScript { this.dataMap[serverIp][1] += n; } - //Update the grow map when calling grow() - RunningScript.prototype.recordGrow = function(serverIp, n=1) { + // Update the grow map when calling grow() + recordGrow(serverIp: string, n: number=1) { if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) { this.dataMap[serverIp] = [0, 0, 0, 0]; } this.dataMap[serverIp][2] += n; } - //Update the weaken map when calling weaken() { - RunningScript.prototype.recordWeaken = function(serverIp, n=1) { + // Update the weaken map when calling weaken() { + recordWeaken(serverIp: string, n: number=1) { if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) { this.dataMap[serverIp] = [0, 0, 0, 0]; } diff --git a/src/Script/Script.ts b/src/Script/Script.ts index fa64ed0d8..92f854f40 100644 --- a/src/Script/Script.ts +++ b/src/Script/Script.ts @@ -1,8 +1,11 @@ // Class representing a script file // This does NOT represent a script that is actively running and // being evaluated. See RunningScript for that +import { calculateRamUsage } from "./RamCalculations"; +import { IPlayer } from "../PersonObjects/IPlayer"; import { Page, routing } from "../ui/navigationTracking"; + import { setTimeoutRef } from "../utils/SetTimeoutRef"; import { Generic_fromJSON, Generic_toJSON, @@ -61,10 +64,9 @@ export class Script { } // Save a script FROM THE SCRIPT EDITOR - saveScript(): void { + saveScript(code: string, p: IPlayer): void { if (routing.isOn(Page.ScriptEditor)) { //Update code and filename - const code = getCurrentEditor().getCode(); this.code = code.replace(/^\s+|\s+$/g, ''); const filenameElem: HTMLInputElement | null = document.getElementById("script-editor-filename") as HTMLInputElement; @@ -75,7 +77,7 @@ export class Script { this.filename = filenameElem!.value; // Server - this.server = Player.currentServer; + this.server = p.currentServer; //Calculate/update ram usage, execution time, etc. this.updateRamUsage(); @@ -85,7 +87,7 @@ export class Script { } // Updates the script's RAM usage based on its code - async updateRamUsage(): void { + async updateRamUsage() { // TODO Commented this out because I think its unnecessary // DOuble check/Test // var codeCopy = this.code.repeat(1); diff --git a/src/Script/ScriptHelpers.js b/src/Script/ScriptHelpers.js index 45a6ebb99..821dce013 100644 --- a/src/Script/ScriptHelpers.js +++ b/src/Script/ScriptHelpers.js @@ -1,40 +1,33 @@ -// Importing this doesn't work for some reason. -const walk = require("acorn/dist/walk"); +import { calculateRamUsage } from "./RamCalculations"; +import { isScriptFilename } from "./ScriptHelpersTS"; -import {CONSTANTS} from "./Constants"; -import {Engine} from "./engine"; -import {FconfSettings, parseFconfSettings} from "./Fconf"; +import {CONSTANTS} from "../Constants"; +import {Engine} from "../engine"; +import { parseFconfSettings } from "../Fconf/Fconf"; +import { FconfSettings } from "../Fconf/FconfSettings"; import {iTutorialSteps, iTutorialNextStep, - ITutorial} from "./InteractiveTutorial"; -import {evaluateImport} from "./NetscriptEvaluator"; -import {NetscriptFunctions} from "./NetscriptFunctions"; -import {addWorkerScript, WorkerScript} from "./NetscriptWorker"; -import {Player} from "./Player"; -import { AceEditor } from "./ScriptEditor/Ace"; -import { CodeMirrorEditor } from "./ScriptEditor/CodeMirror"; -import {AllServers, processSingleServerGrowth} from "./Server"; -import { Settings } from "./Settings/Settings"; -import { EditorSetting } from "./Settings/SettingEnums"; -import {post} from "./ui/postToTerminal"; -import {TextFile} from "./TextFile"; -import {parse, Node} from "../utils/acorn"; -import {Page, routing} from "./ui/navigationTracking"; -import {numeralWrapper} from "./ui/numeralFormat"; -import { setTimeoutRef } from "./utils/SetTimeoutRef"; -import {dialogBoxCreate} from "../utils/DialogBox"; -import {Reviver, Generic_toJSON, - Generic_fromJSON} from "../utils/JSONReviver"; -import {compareArrays} from "../utils/helpers/compareArrays"; -import {createElement} from "../utils/uiHelpers/createElement"; -import {getTimestamp} from "../utils/helpers/getTimestamp"; -import {roundToTwo} from "../utils/helpers/roundToTwo"; + ITutorial} from "../InteractiveTutorial"; +import { addWorkerScript } from "../NetscriptWorker"; +import { Player } from "../Player"; +import { AceEditor } from "../ScriptEditor/Ace"; +import { CodeMirrorEditor } from "../ScriptEditor/CodeMirror"; +import { AllServers } from "../Server/AllServers"; +import { processSingleServerGrowth } from "../Server/ServerHelpers"; +import { Settings } from "../Settings/Settings"; +import { EditorSetting } from "../Settings/SettingEnums"; +import {TextFile} from "../TextFile"; -function isScriptFilename(f) { - return f.endsWith(".js") || f.endsWith(".script") || f.endsWith(".ns"); -} +import {Page, routing} from "../ui/navigationTracking"; +import {numeralWrapper} from "../ui/numeralFormat"; + +import {dialogBoxCreate} from "../../utils/DialogBox"; +import {Reviver, Generic_toJSON, + Generic_fromJSON} from "../../utils/JSONReviver"; +import {compareArrays} from "../../utils/helpers/compareArrays"; +import {createElement} from "../../utils/uiHelpers/createElement"; var scriptEditorRamCheck = null, scriptEditorRamText = null; -function scriptEditorInit() { +export function scriptEditorInit() { // Wrapper container that holds all the buttons below the script editor const wrapper = document.getElementById("script-editor-buttons-wrapper"); if (wrapper == null) { @@ -233,7 +226,7 @@ function saveAndCloseScriptEditor() { let s = Player.getCurrentServer(); for (var i = 0; i < s.scripts.length; i++) { if (filename == s.scripts[i].filename) { - s.scripts[i].saveScript(); + s.scripts[i].saveScript(getCurrentEditor().getCode(), Player); Engine.loadTerminalContent(); return iTutorialNextStep(); } @@ -241,7 +234,7 @@ function saveAndCloseScriptEditor() { //If the current script does NOT exist, create a new one let script = new Script(); - script.saveScript(); + script.saveScript(getCurrentEditor().getCode(), Player); s.scripts.push(script); return iTutorialNextStep(); @@ -269,7 +262,7 @@ function saveAndCloseScriptEditor() { //If the current script already exists on the server, overwrite it for (var i = 0; i < s.scripts.length; i++) { if (filename == s.scripts[i].filename) { - s.scripts[i].saveScript(); + s.scripts[i].saveScript(getCurrentEditor().getCode(), Player); Engine.loadTerminalContent(); return; } @@ -277,7 +270,7 @@ function saveAndCloseScriptEditor() { //If the current script does NOT exist, create a new one var script = new Script(); - script.saveScript(); + script.saveScript(getCurrentEditor().getCode(), Player); s.scripts.push(script); } else if (filename.endsWith(".txt")) { for (var i = 0; i < s.textFiles.length; ++i) { @@ -308,410 +301,9 @@ function checkValidFilename(filename) { return false; } -// These special strings are used to reference the presence of a given logical -// construct within a user script. -const specialReferenceIF = "__SPECIAL_referenceIf"; -const specialReferenceFOR = "__SPECIAL_referenceFor"; -const specialReferenceWHILE = "__SPECIAL_referenceWhile"; - -// The global scope of a script is registered under this key during parsing. -const memCheckGlobalKey = ".__GLOBAL__"; - -// Calcluates the amount of RAM a script uses. Uses parsing and AST walking only, -// rather than NetscriptEvaluator. This is useful because NetscriptJS code does -// not work under NetscriptEvaluator. -async function parseOnlyRamCalculate(server, code, workerScript) { - try { - // Maps dependent identifiers to their dependencies. - // - // The initial identifier is __SPECIAL_INITIAL_MODULE__.__GLOBAL__. - // It depends on all the functions declared in the module, all the global scopes - // of its imports, and any identifiers referenced in this global scope. Each - // function depends on all the identifiers referenced internally. - // We walk the dependency graph to calculate RAM usage, given that some identifiers - // reference Netscript functions which have a RAM cost. - let dependencyMap = {}; - - // Scripts we've parsed. - const completedParses = new Set(); - - // Scripts we've discovered that need to be parsed. - const parseQueue = []; - - // Parses a chunk of code with a given module name, and updates parseQueue and dependencyMap. - function parseCode(code, moduleName) { - const result = parseOnlyCalculateDeps(code, moduleName); - completedParses.add(moduleName); - - // Add any additional modules to the parse queue; - for (let i = 0; i < result.additionalModules.length; ++i) { - if (!completedParses.has(result.additionalModules[i])) { - parseQueue.push(result.additionalModules[i]); - } - } - - // Splice all the references in. - //Spread syntax not supported in edge, use Object.assign instead - //dependencyMap = {...dependencyMap, ...result.dependencyMap}; - dependencyMap = Object.assign(dependencyMap, result.dependencyMap); - } - - const initialModule = "__SPECIAL_INITIAL_MODULE__"; - parseCode(code, initialModule); - - while (parseQueue.length > 0) { - // Get the code from the server. - const nextModule = parseQueue.shift(); - - let code; - if (nextModule.startsWith("https://") || nextModule.startsWith("http://")) { - try { - const module = await eval('import(nextModule)'); - code = ""; - for (const prop in module) { - if (typeof module[prop] === 'function') { - code += module[prop].toString() + ";\n"; - } - } - } catch(e) { - console.error(`Error dynamically importing module from ${nextModule} for RAM calculations: ${e}`); - return -1; - } - } else { - const script = server.getScript(nextModule.startsWith("./") ? nextModule.slice(2) : nextModule); - if (!script) { - console.warn("Invalid script"); - return -1; // No such script on the server. - } - code = script.code; - } - - parseCode(code, nextModule); - } - - // Finally, walk the reference map and generate a ram cost. The initial set of keys to scan - // are those that start with __SPECIAL_INITIAL_MODULE__. - let ram = CONSTANTS.ScriptBaseRamCost; - const unresolvedRefs = Object.keys(dependencyMap).filter(s => s.startsWith(initialModule)); - const resolvedRefs = new Set(); - while (unresolvedRefs.length > 0) { - const ref = unresolvedRefs.shift(); - - // Check if this is one of the special keys, and add the appropriate ram cost if so. - if (ref === "hacknet" && !resolvedRefs.has("hacknet")) { - ram += CONSTANTS.ScriptHacknetNodesRamCost; - } - if (ref === "document" && !resolvedRefs.has("document")) { - ram += CONSTANTS.ScriptDomRamCost; - } - if (ref === "window" && !resolvedRefs.has("window")) { - ram += CONSTANTS.ScriptDomRamCost; - } - - resolvedRefs.add(ref); - - if (ref.endsWith(".*")) { - // A prefix reference. We need to find all matching identifiers. - const prefix = ref.slice(0, ref.length - 2); - for (let ident of Object.keys(dependencyMap).filter(k => k.startsWith(prefix))) { - for (let dep of dependencyMap[ident] || []) { - if (!resolvedRefs.has(dep)) unresolvedRefs.push(dep); - } - } - } else { - // An exact reference. Add all dependencies of this ref. - for (let dep of dependencyMap[ref] || []) { - if (!resolvedRefs.has(dep)) unresolvedRefs.push(dep); - } - } - - // Check if this ident is a function in the workerscript env. If it is, then we need to - // get its RAM cost. We do this by calling it, which works because the running script - // is in checkingRam mode. - // - // TODO it would be simpler to just reference a dictionary. - try { - function applyFuncRam(func) { - if (typeof func === "function") { - try { - let res; - if (func.constructor.name === "AsyncFunction") { - res = 0; // Async functions will always be 0 RAM - } else { - res = func.apply(null, []); - } - if (typeof res === "number") { - return res; - } - return 0; - } catch(e) { - console.log("ERROR applying function: " + e); - return 0; - } - } else { - return 0; - } - } - - //Special logic for namespaces (Bladeburner, CodingCOntract) - var func; - if (ref in workerScript.env.vars.bladeburner) { - func = workerScript.env.vars.bladeburner[ref]; - } else if (ref in workerScript.env.vars.codingcontract) { - func = workerScript.env.vars.codingcontract[ref]; - } else if (ref in workerScript.env.vars.gang) { - func = workerScript.env.vars.gang[ref]; - } else { - func = workerScript.env.get(ref); - } - ram += applyFuncRam(func); - } catch (error) {continue;} - } - return ram; - - } catch (error) { - // console.info("parse or eval error: ", error); - // This is not unexpected. The user may be editing a script, and it may be in - // a transitory invalid state. - return -1; - } -} - -// Parses one script and calculates its ram usage, for the global scope and each function. -// Returns a cost map and a dependencyMap for the module. Returns a reference map to be joined -// onto the main reference map, and a list of modules that need to be parsed. -function parseOnlyCalculateDeps(code, currentModule) { - const ast = parse(code, {sourceType:"module", ecmaVersion: 8}); - - // Everything from the global scope goes in ".". Everything else goes in ".function", where only - // the outermost layer of functions counts. - const globalKey = currentModule + memCheckGlobalKey; - const dependencyMap = {}; - dependencyMap[globalKey] = new Set(); - - // If we reference this internal name, we're really referencing that external name. - // Filled when we import names from other modules. - let internalToExternal = {}; - - var additionalModules = []; - - // References get added pessimistically. They are added for thisModule.name, name, and for - // any aliases. - function addRef(key, name) { - const s = dependencyMap[key] || (dependencyMap[key] = new Set()); - if (name in internalToExternal) { - s.add(internalToExternal[name]); - } - s.add(currentModule + "." + name); - s.add(name); // For builtins like hack. - } - - //A list of identifiers that resolve to "native Javascript code" - const objectPrototypeProperties = Object.getOwnPropertyNames(Object.prototype); - - // If we discover a dependency identifier, state.key is the dependent identifier. - // walkDeeper is for doing recursive walks of expressions in composites that we handle. - function commonVisitors() { - return { - Identifier: (node, st, walkDeeper) => { - if (objectPrototypeProperties.includes(node.name)) {return;} - addRef(st.key, node.name); - }, - WhileStatement: (node, st, walkDeeper) => { - addRef(st.key, specialReferenceWHILE); - node.test && walkDeeper(node.test, st); - node.body && walkDeeper(node.body, st); - }, - DoWhileStatement: (node, st, walkDeeper) => { - addRef(st.key, specialReferenceWHILE); - node.test && walkDeeper(node.test, st); - node.body && walkDeeper(node.body, st); - }, - ForStatement: (node, st, walkDeeper) => { - addRef(st.key, specialReferenceFOR); - node.init && walkDeeper(node.init, st); - node.test && walkDeeper(node.test, st); - node.update && walkDeeper(node.update, st); - node.body && walkDeeper(node.body, st); - }, - IfStatement: (node, st, walkDeeper) => { - addRef(st.key, specialReferenceIF); - node.test && walkDeeper(node.test, st); - node.consequent && walkDeeper(node.consequent, st); - node.alternate && walkDeeper(node.alternate, st); - }, - MemberExpression: (node, st, walkDeeper) => { - node.object && walkDeeper(node.object, st); - node.property && walkDeeper(node.property, st); - }, - } - } - - //Spread syntax not supported in Edge yet, use Object.assign - /* - walk.recursive(ast, {key: globalKey}, { - ImportDeclaration: (node, st, walkDeeper) => { - const importModuleName = node.source.value; - additionalModules.push(importModuleName); - - // This module's global scope refers to that module's global scope, no matter how we - // import it. - dependencyMap[st.key].add(importModuleName + memCheckGlobalKey); - - for (let i = 0; i < node.specifiers.length; ++i) { - const spec = node.specifiers[i]; - if (spec.imported !== undefined && spec.local !== undefined) { - // We depend on specific things. - internalToExternal[spec.local.name] = importModuleName + "." + spec.imported.name; - } else { - // We depend on everything. - dependencyMap[st.key].add(importModuleName + ".*"); - } - } - }, - FunctionDeclaration: (node, st, walkDeeper) => { - // Don't use walkDeeper, because we are changing the visitor set. - const key = currentModule + "." + node.id.name; - walk.recursive(node, {key: key}, commonVisitors()); - }, - ...commonVisitors() - }); - */ - walk.recursive(ast, {key: globalKey}, Object.assign({ - ImportDeclaration: (node, st, walkDeeper) => { - const importModuleName = node.source.value; - additionalModules.push(importModuleName); - - // This module's global scope refers to that module's global scope, no matter how we - // import it. - dependencyMap[st.key].add(importModuleName + memCheckGlobalKey); - - for (let i = 0; i < node.specifiers.length; ++i) { - const spec = node.specifiers[i]; - if (spec.imported !== undefined && spec.local !== undefined) { - // We depend on specific things. - internalToExternal[spec.local.name] = importModuleName + "." + spec.imported.name; - } else { - // We depend on everything. - dependencyMap[st.key].add(importModuleName + ".*"); - } - } - }, - FunctionDeclaration: (node, st, walkDeeper) => { - // Don't use walkDeeper, because we are changing the visitor set. - const key = currentModule + "." + node.id.name; - walk.recursive(node, {key: key}, commonVisitors()); - }, - }, commonVisitors())); - - return {dependencyMap: dependencyMap, additionalModules: additionalModules}; -} - -async function calculateRamUsage(codeCopy) { - //Create a temporary/mock WorkerScript and an AST from the code - var currServ = Player.getCurrentServer(); - var workerScript = new WorkerScript({ - filename:"foo", - scriptRef: {code:""}, - args:[], - getCode: function() { return ""; } - }); - workerScript.checkingRam = true; //Netscript functions will return RAM usage - workerScript.serverIp = currServ.ip; - - try { - return await parseOnlyRamCalculate(currServ, codeCopy, workerScript); - } catch (e) { - console.log("Failed to parse ram using new method. Falling back.", e); - } - - // Try the old way. - - try { - var ast = parse(codeCopy, {sourceType:"module"}); - } catch(e) { - return -1; - } - - //Search through AST, scanning for any 'Identifier' nodes for functions, or While/For/If nodes - var queue = [], ramUsage = CONSTANTS.ScriptBaseRamCost; - var whileUsed = false, forUsed = false, ifUsed = false; - queue.push(ast); - while (queue.length != 0) { - var exp = queue.shift(); - switch (exp.type) { - case "ImportDeclaration": - //Gets an array of all imported functions as AST expressions - //and pushes them on the queue. - var res = evaluateImport(exp, workerScript, true); - for (var i = 0; i < res.length; ++i) { - queue.push(res[i]); - } - break; - case "BlockStatement": - case "Program": - for (var i = 0; i < exp.body.length; ++i) { - if (exp.body[i] instanceof Node) { - queue.push(exp.body[i]); - } - } - break; - case "WhileStatement": - if (!whileUsed) { - ramUsage += CONSTANTS.ScriptWhileRamCost; - whileUsed = true; - } - break; - case "ForStatement": - if (!forUsed) { - ramUsage += CONSTANTS.ScriptForRamCost; - forUsed = true; - } - break; - case "IfStatement": - if (!ifUsed) { - ramUsage += CONSTANTS.ScriptIfRamCost; - ifUsed = true; - } - break; - case "Identifier": - if (exp.name in workerScript.env.vars) { - var func = workerScript.env.get(exp.name); - if (typeof func === "function") { - try { - var res = func.apply(null, []); - if (typeof res === "number") { - ramUsage += res; - } - } catch(e) { - console.log("ERROR applying function: " + e); - } - } - } - break; - default: - break; - } - - for (var prop in exp) { - if (exp.hasOwnProperty(prop)) { - if (exp[prop] instanceof Node) { - queue.push(exp[prop]); - } - } - } - } - - //Special case: hacknetnodes array - if (codeCopy.includes("hacknet")) { - ramUsage += CONSTANTS.ScriptHacknetNodesRamCost; - } - return ramUsage; -} - //Called when the game is loaded. Loads all running scripts (from all servers) //into worker scripts so that they will start running -function loadAllRunningScripts() { +export function loadAllRunningScripts() { var total = 0; let skipScriptLoad = (window.location.href.toLowerCase().indexOf("?noscripts") !== -1); if (skipScriptLoad) { console.info("Skipping the load of any scripts during startup"); } @@ -767,7 +359,7 @@ function scriptCalculateOfflineProduction(runningScriptObj) { var timesGrown = Math.round(0.5 * runningScriptObj.dataMap[ip][2] / runningScriptObj.onlineRunningTime * timePassed); console.log(runningScriptObj.filename + " called grow() on " + serv.hostname + " " + timesGrown + " times while offline"); runningScriptObj.log("Called grow() on " + serv.hostname + " " + timesGrown + " times while offline"); - var growth = processSingleServerGrowth(serv, timesGrown * 450); + var growth = processSingleServerGrowth(serv, timesGrown * 450, Player); runningScriptObj.log(serv.hostname + " grown by " + numeralWrapper.format(growth * 100 - 100, '0.000000%') + " from grow() calls made while offline"); } } @@ -838,7 +430,7 @@ function scriptCalculateOfflineProduction(runningScriptObj) { //Returns a RunningScript object matching the filename and arguments on the //designated server, and false otherwise -function findRunningScript(filename, args, server) { +export function findRunningScript(filename, args, server) { for (var i = 0; i < server.runningScripts.length; ++i) { if (server.runningScripts[i].filename == filename && compareArrays(server.runningScripts[i].args, args)) { @@ -847,6 +439,3 @@ function findRunningScript(filename, args, server) { } return null; } - -export {loadAllRunningScripts, findRunningScript, - scriptEditorInit, isScriptFilename}; diff --git a/src/Script/ScriptHelpersTS.ts b/src/Script/ScriptHelpersTS.ts new file mode 100644 index 000000000..f59b69810 --- /dev/null +++ b/src/Script/ScriptHelpersTS.ts @@ -0,0 +1,4 @@ +// Script helper functions +export function isScriptFilename(f: string) { + return f.endsWith(".js") || f.endsWith(".script") || f.endsWith(".ns"); +} diff --git a/src/Server/AllServers.ts b/src/Server/AllServers.ts index e04c33fbb..6d8d91c0b 100644 --- a/src/Server/AllServers.ts +++ b/src/Server/AllServers.ts @@ -1,40 +1,61 @@ -import { ipExists } from "../../utils/IPAddress"; +import { Server } from "./Server"; +import { SpecialServerIps } from "./SpecialServerIps"; +import { serverMetadata } from "./data/servers"; + +import { IMap } from "../types"; +import { createRandomIp, + ipExists } from "../../utils/IPAddress"; +import { getRandomInt } from "../../utils/helpers/getRandomInt"; +import { Reviver } from "../../utils/JSONReviver"; // Map of all Servers that exist in the game // Key (string) = IP // Value = Server object -let AllServers = {}; +export let AllServers: IMap = {}; // Saftely add a Server to the AllServers map -export function AddToAllServers(server) { +export function AddToAllServers(server: Server): void { var serverIp = server.ip; if (ipExists(serverIp)) { console.log("IP of server that's being added: " + serverIp); console.log("Hostname of the server thats being added: " + server.hostname); console.log("The server that already has this IP is: " + AllServers[serverIp].hostname); throw new Error("Error: Trying to add a server with an existing IP"); - return; } AllServers[serverIp] = server; } -export function initForeignServers() { +interface IServerParams { + hackDifficulty?: number; + hostname: string; + ip: string; + maxRam?: number; + moneyAvailable?: number; + numOpenPortsRequired: number; + organizationName: string; + requiredHackingSkill?: number; + serverGrowth?: number; + + [key: string]: any; +} + +export function initForeignServers(homeComputer: Server) { /* Create a randomized network for all the foreign servers */ //Groupings for creating a randomized network - const networkLayers = []; + const networkLayers: Server[][] = []; for (let i = 0; i < 15; i++) { networkLayers.push([]); } // Essentially any property that is of type 'number | IMinMaxRange' - const propertiesToPatternMatch = [ + const propertiesToPatternMatch: string[] = [ "hackDifficulty", "moneyAvailable", "requiredHackingSkill", "serverGrowth" ]; - const toNumber = (value) => { + const toNumber = (value: any) => { switch (typeof value) { case 'number': return value; @@ -46,7 +67,7 @@ export function initForeignServers() { } for (const metadata of serverMetadata) { - const serverParams = { + const serverParams: IServerParams = { hostname: metadata.hostname, ip: createRandomIp(), numOpenPortsRequired: metadata.numOpenPortsRequired, @@ -79,21 +100,21 @@ export function initForeignServers() { } /* Create a randomized network for all the foreign servers */ - const linkComputers = (server1, server2) => { + const linkComputers = (server1: Server, server2: Server) => { server1.serversOnNetwork.push(server2.ip); server2.serversOnNetwork.push(server1.ip); }; - const getRandomArrayItem = (arr) => arr[Math.floor(Math.random() * arr.length)]; + const getRandomArrayItem = (arr: any[]) => arr[Math.floor(Math.random() * arr.length)]; - const linkNetworkLayers = (network1, selectServer) => { + const linkNetworkLayers = (network1: Server[], selectServer: () => Server) => { for (const server of network1) { linkComputers(server, selectServer()); } }; // Connect the first tier of servers to the player's home computer - linkNetworkLayers(networkLayers[0], () => Player.getHomeComputer()); + linkNetworkLayers(networkLayers[0], () => homeComputer); for (let i = 1; i < networkLayers.length; i++) { linkNetworkLayers(networkLayers[i], () => getRandomArrayItem(networkLayers[i - 1])); } @@ -106,6 +127,6 @@ export function prestigeAllServers() { AllServers = {}; } -export function loadAllServers(saveString) { +export function loadAllServers(saveString: string) { AllServers = JSON.parse(saveString, Reviver); } diff --git a/src/Server/Server.ts b/src/Server/Server.ts index b4d1f33dc..bc3056d14 100644 --- a/src/Server/Server.ts +++ b/src/Server/Server.ts @@ -1,9 +1,14 @@ // Class representing a single generic Server + +// TODO This import is a circular import. Try to fix it in the future +import { GetServerByHostname } from "./ServerHelpers"; + import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; import { CodingContract } from "../CodingContracts"; import { Message } from "../Message/Message"; import { RunningScript } from "../Script/RunningScript"; import { Script } from "../Script/Script"; +import { isScriptFilename } from "../Script/ScriptHelpersTS"; import { TextFile } from "../TextFile"; import { createRandomIp } from "../../utils/IPAddress"; @@ -27,6 +32,11 @@ interface IConstructorParams { } export class Server { + // Initializes a Server Object from a JSON save state + static fromJSON(value: any): Server { + return Generic_fromJSON(Server, value.data); + } + // Initial server security level // (i.e. security level when the server was created) baseDifficulty: number = 1; @@ -172,49 +182,43 @@ export class Server { this.maxRam = ram; } - //The serverOnNetwork array holds the IP of all the servers. This function - //returns the actual Server objects - Server.prototype.getServerOnNetwork = function(i) { - if (i > this.serversOnNetwork.length) { - console.log("Tried to get server on network that was out of range"); - return; - } - return AllServers[this.serversOnNetwork[i]]; - } - - //Given the name of the script, returns the corresponding - //script object on the server (if it exists) - Server.prototype.getScript = function(scriptName) { - for (var i = 0; i < this.scripts.length; i++) { - if (this.scripts[i].filename == scriptName) { + // Given the name of the script, returns the corresponding + // script object on the server (if it exists) + getScript(scriptName: string): Script | null { + for (let i = 0; i < this.scripts.length; i++) { + if (this.scripts[i].filename === scriptName) { return this.scripts[i]; } } + return null; } - Server.prototype.capDifficulty = function() { + // Ensures that the server's difficulty (server security) doesn't get too high + capDifficulty(): void { if (this.hackDifficulty < this.minDifficulty) {this.hackDifficulty = this.minDifficulty;} if (this.hackDifficulty < 1) {this.hackDifficulty = 1;} - //Place some arbitrarily limit that realistically should never happen unless someone is - //screwing around with the game + + // Place some arbitrarily limit that realistically should never happen unless someone is + // screwing around with the game if (this.hackDifficulty > 1000000) {this.hackDifficulty = 1000000;} } - //Strengthens a server's security level (difficulty) by the specified amount - Server.prototype.fortify = function(amt) { + // Strengthens a server's security level (difficulty) by the specified amount + fortify(amt: number): void { this.hackDifficulty += amt; this.capDifficulty(); } - Server.prototype.weaken = function(amt) { + // Lowers the server's security level (difficulty) by the specified amount) + weaken(amt: number): void { this.hackDifficulty -= (amt * BitNodeMultipliers.ServerWeakenRate); this.capDifficulty(); } // Write to a script file // Overwrites existing files. Creates new files if the script does not eixst - Server.prototype.writeToScriptFile = function(fn, code) { + writeToScriptFile(fn: string, code: string) { var ret = {success: false, overwritten: false}; if (!isScriptFilename(fn)) { return ret; } @@ -232,7 +236,7 @@ export class Server { } //Otherwise, create a new script - var newScript = new Script(); + const newScript = new Script(); newScript.filename = fn; newScript.code = code; newScript.updateRamUsage(); @@ -244,8 +248,8 @@ export class Server { // Write to a text file // Overwrites existing files. Creates new files if the text file does not exist - Server.prototype.writeToTextFile = function(fn, txt) { - var ret = {success: false, overwritten: false}; + writeToTextFile(fn: string, txt: string) { + var ret = { success: false, overwritten: false }; if (!fn.endsWith("txt")) { return ret; } //Check if the text file already exists, and overwrite if it does @@ -265,11 +269,11 @@ export class Server { return ret; } - Server.prototype.addContract = function(contract) { + addContract(contract: CodingContract) { this.contracts.push(contract); } - Server.prototype.removeContract = function(contract) { + removeContract(contract: CodingContract) { if (contract instanceof CodingContract) { this.contracts = this.contracts.filter((c) => { return c.fn !== contract.fn; @@ -281,7 +285,7 @@ export class Server { } } - Server.prototype.getContract = function(contractName) { + getContract(contractName: string) { for (const contract of this.contracts) { if (contract.fn === contractName) { return contract; @@ -289,15 +293,11 @@ export class Server { } return null; } -} -//Functions for loading and saving a Server -Server.prototype.toJSON = function() { - return Generic_toJSON("Server", this); -} - -Server.fromJSON = function(value) { - return Generic_fromJSON(Server, value.data); + // Serialize the current object to a JSON save state + toJSON(): any { + return Generic_toJSON("Server", this); + } } Reviver.constructors.Server = Server; diff --git a/src/Server/ServerHelpers.js b/src/Server/ServerHelpers.ts similarity index 67% rename from src/Server/ServerHelpers.js rename to src/Server/ServerHelpers.ts index 0ab1183f5..e443deb0c 100644 --- a/src/Server/ServerHelpers.js +++ b/src/Server/ServerHelpers.ts @@ -1,23 +1,16 @@ -import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers"; -import { CodingContract, - ContractTypes } from "./CodingContracts"; -import { CONSTANTS } from "./Constants"; -import { Script, - isScriptFilename } from "./Script"; -import { Player } from "./Player"; -import { Programs } from "./Programs/Programs"; -import { SpecialServerIps } from "./SpecialServerIps"; -import { TextFile } from "./TextFile"; -import { getRandomInt } from "../utils/helpers/getRandomInt"; -import { serverMetadata } from "./data/servers"; -import { Reviver, - Generic_toJSON, - Generic_fromJSON} from "../utils/JSONReviver"; -import {isValidIPAddress} from "../utils/helpers/isValidIPAddress"; +import { AllServers } from "./AllServers"; +import { Server } from "./Server"; + +import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; +import { CONSTANTS } from "../Constants"; +import { IPlayer } from "../PersonObjects/IPlayer"; +import { Programs } from "../Programs/Programs"; + +import {isValidIPAddress} from "../../utils/helpers/isValidIPAddress"; // Returns the number of cycles needed to grow the specified server by the // specified amount. 'growth' parameter is in decimal form, not percentage -export function numCycleForGrowth(server, growth) { +export function numCycleForGrowth(server: Server, growth: number, p: IPlayer) { let ajdGrowthRate = 1 + (CONSTANTS.ServerBaseGrowthRate - 1) / server.hackDifficulty; if(ajdGrowthRate > CONSTANTS.ServerMaxGrowthRate) { ajdGrowthRate = CONSTANTS.ServerMaxGrowthRate; @@ -25,12 +18,12 @@ export function numCycleForGrowth(server, growth) { const serverGrowthPercentage = server.serverGrowth / 100; - const cycles = Math.log(growth)/(Math.log(ajdGrowthRate)*Player.hacking_grow_mult*serverGrowthPercentage); + const cycles = Math.log(growth)/(Math.log(ajdGrowthRate) * p.hacking_grow_mult * serverGrowthPercentage); return cycles; } //Applied server growth for a single server. Returns the percentage growth -export function processSingleServerGrowth(server, numCycles) { +export function processSingleServerGrowth(server: Server, numCycles: number, p: IPlayer) { //Server growth processed once every 450 game cycles const numServerGrowthCycles = Math.max(Math.floor(numCycles / 450), 0); @@ -44,7 +37,7 @@ export function processSingleServerGrowth(server, numCycles) { const numServerGrowthCyclesAdjusted = numServerGrowthCycles * serverGrowthPercentage * BitNodeMultipliers.ServerGrowthRate; //Apply serverGrowth for the calculated number of growth cycles - var serverGrowth = Math.pow(adjGrowthRate, numServerGrowthCyclesAdjusted * Player.hacking_grow_mult); + let serverGrowth = Math.pow(adjGrowthRate, numServerGrowthCyclesAdjusted * p.hacking_grow_mult); if (serverGrowth < 1) { console.log("WARN: serverGrowth calculated to be less than 1"); serverGrowth = 1; @@ -66,14 +59,14 @@ export function processSingleServerGrowth(server, numCycles) { // if there was any growth at all, increase security if (oldMoneyAvailable !== server.moneyAvailable) { //Growing increases server security twice as much as hacking - let usedCycles = numCycleForGrowth(server, server.moneyAvailable / oldMoneyAvailable); + let usedCycles = numCycleForGrowth(server, server.moneyAvailable / oldMoneyAvailable, p); usedCycles = Math.max(0, usedCycles); server.fortify(2 * CONSTANTS.ServerFortifyAmount * Math.ceil(usedCycles)); } return server.moneyAvailable / oldMoneyAvailable; } -export function prestigeHomeComputer(homeComp) { +export function prestigeHomeComputer(homeComp: Server) { const hasBitflume = homeComp.programs.includes(Programs.BitFlume.name); homeComp.programs.length = 0; //Remove programs @@ -93,17 +86,9 @@ export function prestigeHomeComputer(homeComp) { homeComp.messages.push("hackers-starting-handbook.lit"); } -function SizeOfAllServers() { - var size = 0, key; - for (key in AllServers) { - if (AllServers.hasOwnProperty(key)) size++; - } - return size; -} - //Returns server object with corresponding hostname // Relatively slow, would rather not use this a lot -export function GetServerByHostname(hostname) { +export function GetServerByHostname(hostname: string): Server | null { for (var ip in AllServers) { if (AllServers.hasOwnProperty(ip)) { if (AllServers[ip].hostname == hostname) { @@ -111,16 +96,30 @@ export function GetServerByHostname(hostname) { } } } + return null; } //Get server by IP or hostname. Returns null if invalid -export function getServer(s) { +export function getServer(s: string): Server | null { if (!isValidIPAddress(s)) { return GetServerByHostname(s); } - if(AllServers[s] !== undefined) { + if (AllServers[s] !== undefined) { return AllServers[s]; } + return null; } + +// Returns the i-th server on the specified server's network +// A Server's serverOnNetwork property holds only the IPs. This function returns +// the actual Server object +export function getServerOnNetwork(server: Server, i: number) { + if (i > server.serversOnNetwork.length) { + console.error("Tried to get server on network that was out of range"); + return; + } + + return AllServers[server.serversOnNetwork[i]]; +} diff --git a/src/Server/ServerPurchases.js b/src/Server/ServerPurchases.js index 00535b997..163a725d1 100644 --- a/src/Server/ServerPurchases.js +++ b/src/Server/ServerPurchases.js @@ -2,16 +2,16 @@ * Implements functions for purchasing servers or purchasing more RAM for * the home computer */ -import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers"; -import { CONSTANTS } from "./Constants"; -import { Player } from "./Player"; -import { Server, - AllServers, - AddToAllServers} from "./Server"; -import { dialogBoxCreate } from "../utils/DialogBox"; -import { createRandomIp } from "../utils/IPAddress"; -import { yesNoTxtInpBoxGetInput } from "../utils/YesNoBox"; -import { isPowerOfTwo } from "../utils/helpers/isPowerOfTwo"; +import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; +import { CONSTANTS } from "../Constants"; +import { Player } from "../Player"; +import { AllServers } from "../Server/AllServers"; +import { Server } from "../Server/Server"; +import { AddToAllServers } from "../Server/ServerHelpers"; +import { dialogBoxCreate } from "../../utils/DialogBox"; +import { createRandomIp } from "../../utils/IPAddress"; +import { yesNoTxtInpBoxGetInput } from "../../utils/YesNoBox"; +import { isPowerOfTwo } from "../../utils/helpers/isPowerOfTwo"; // Returns the cost of purchasing a server with the given RAM // Returns Infinity for invalid 'ram' arguments diff --git a/src/Server/SpecialServerIps.js b/src/Server/SpecialServerIps.js deleted file mode 100644 index d3175dc01..000000000 --- a/src/Server/SpecialServerIps.js +++ /dev/null @@ -1,50 +0,0 @@ -import {Reviver, Generic_toJSON, - Generic_fromJSON} from "../utils/JSONReviver"; - -/* Holds IP of Special Servers */ -let SpecialServerNames = { - FulcrumSecretTechnologies: "Fulcrum Secret Technologies Server", - CyberSecServer: "CyberSec Server", - NiteSecServer: "NiteSec Server", - TheBlackHandServer: "The Black Hand Server", - BitRunnersServer: "BitRunners Server", - TheDarkArmyServer: "The Dark Army Server", - DaedalusServer: "Daedalus Server", - WorldDaemon: "w0r1d_d43m0n", -} -function SpecialServerIpsMap() {} - -SpecialServerIpsMap.prototype.addIp = function(name, ip) { - this[name] = ip; -} - -SpecialServerIpsMap.prototype.toJSON = function() { - return Generic_toJSON("SpecialServerIpsMap", this); -} - -SpecialServerIpsMap.fromJSON = function(value) { - return Generic_fromJSON(SpecialServerIpsMap, value.data); -} - -Reviver.constructors.SpecialServerIpsMap = SpecialServerIpsMap; - -let SpecialServerIps = new SpecialServerIpsMap(); - -function prestigeSpecialServerIps() { - for (var member in SpecialServerIps) { - delete SpecialServerIps[member]; - } - SpecialServerIps = null; - SpecialServerIps = new SpecialServerIpsMap(); -} - -function loadSpecialServerIps(saveString) { - SpecialServerIps = JSON.parse(saveString, Reviver); -} - -function initSpecialServerIps() { - SpecialServerIps = new SpecialServerIpsMap(); -} - -export {SpecialServerNames, SpecialServerIps, SpecialServerIpsMap, loadSpecialServerIps, - prestigeSpecialServerIps, initSpecialServerIps}; diff --git a/src/Server/SpecialServerIps.ts b/src/Server/SpecialServerIps.ts new file mode 100644 index 000000000..121c377cc --- /dev/null +++ b/src/Server/SpecialServerIps.ts @@ -0,0 +1,56 @@ +import { IMap } from "../types"; +import { Reviver, + Generic_toJSON, + Generic_fromJSON } from "../../utils/JSONReviver"; + +/* Holds IP of Special Servers */ +export let SpecialServerNames: IMap = { + FulcrumSecretTechnologies: "Fulcrum Secret Technologies Server", + CyberSecServer: "CyberSec Server", + NiteSecServer: "NiteSec Server", + TheBlackHandServer: "The Black Hand Server", + BitRunnersServer: "BitRunners Server", + TheDarkArmyServer: "The Dark Army Server", + DaedalusServer: "Daedalus Server", + WorldDaemon: "w0r1d_d43m0n", +} + +export class SpecialServerIpsMap { + // Initializes a SpecialServerIpsMap Object from a JSON save state + static fromJSON(value: any): SpecialServerIpsMap { + return Generic_fromJSON(SpecialServerIpsMap, value.data); + } + + [key: string]: Function | string; + + constructor() {} + + addIp(name:string, ip: string) { + this[name] = ip; + } + + // Serialize the current object to a JSON save state + toJSON(): any { + return Generic_toJSON("SpecialServerIpsMap", this); + } +} + +Reviver.constructors.SpecialServerIpsMap = SpecialServerIpsMap; + +export let SpecialServerIps: SpecialServerIpsMap = new SpecialServerIpsMap(); + +export function prestigeSpecialServerIps() { + for (var member in SpecialServerIps) { + delete SpecialServerIps[member]; + } + + SpecialServerIps = new SpecialServerIpsMap(); +} + +export function loadSpecialServerIps(saveString: string) { + SpecialServerIps = JSON.parse(saveString, Reviver); +} + +export function initSpecialServerIps() { + SpecialServerIps = new SpecialServerIpsMap(); +} diff --git a/src/data/servers.ts b/src/Server/data/servers.ts similarity index 99% rename from src/data/servers.ts rename to src/Server/data/servers.ts index 12698a3b8..ec661c458 100644 --- a/src/data/servers.ts +++ b/src/Server/data/servers.ts @@ -81,6 +81,8 @@ interface IServerMetadata { * A "unique" server that has special implications when the player manually hacks it. */ specialName?: string; + + [key: string]: any; } /** diff --git a/src/Terminal.js b/src/Terminal.js index bb06b6c25..d2744b2c9 100644 --- a/src/Terminal.js +++ b/src/Terminal.js @@ -10,8 +10,9 @@ import { executeDarkwebTerminalCommand, checkIfConnectedToDarkweb } from "./DarkWeb/DarkWeb"; import { DarkWebItems } from "./DarkWeb/DarkWebItems"; import {Engine} from "./engine"; -import {FconfSettings, parseFconfSettings, - createFconf} from "./Fconf"; +import { parseFconfSettings, + createFconf } from "./Fconf/Fconf"; +import { FconfSettings } from "./Fconf/FconfSettings"; import {calculateHackingChance, calculateHackingExpGain, calculatePercentMoneyHacked, @@ -22,18 +23,22 @@ import {TerminalHelpText, HelpTexts} from "./HelpText"; import {iTutorialNextStep, iTutorialSteps, ITutorial} from "./InteractiveTutorial"; import {showLiterature} from "./Literature"; -import {showMessage, Message} from "./Message"; +import { Message } from "./Message/Message"; +import { showMessage } from "./Message/MessageHelpers"; import {killWorkerScript, addWorkerScript} from "./NetscriptWorker"; import {Player} from "./Player"; import {hackWorldDaemon} from "./RedPill"; -import { findRunningScript, - RunningScript, - isScriptFilename } from "./Script"; -import {AllServers, GetServerByHostname, - getServer, Server} from "./Server"; +import { RunningScript } from "./Script/RunningScript"; +import { findRunningScript } from "./Script/ScriptHelpers"; +import { isScriptFilename } from "./Script/ScriptHelpersTS"; +import { AllServers } from "./Server/AllServers"; +import { Server } from "./Server/Server"; +import { GetServerByHostname, + getServer, + getServerOnNetwork } from "./Server/ServerHelpers"; import {Settings} from "./Settings/Settings"; -import {SpecialServerIps, - SpecialServerNames} from "./SpecialServerIps"; +import { SpecialServerIps, + SpecialServerNames } from "./Server/SpecialServerIps"; import {getTextFile} from "./TextFile"; import { setTimeoutRef } from "./utils/SetTimeoutRef"; import {containsAllStrings, @@ -1157,8 +1162,8 @@ let Terminal = { let ip = commandArray[1]; - for (var i = 0; i < Player.getCurrentServer().serversOnNetwork.length; i++) { - if (Player.getCurrentServer().getServerOnNetwork(i).ip == ip || Player.getCurrentServer().getServerOnNetwork(i).hostname == ip) { + for (var i = 0; i < s.serversOnNetwork.length; i++) { + if (getServerOnNetwork(s, i).ip == ip || getServerOnNetwork(s, i).hostname == ip) { Terminal.connectToServer(ip); return; } @@ -1812,11 +1817,13 @@ let Terminal = { postError("Incorrect usage of netstat/scan command. Usage: netstat/scan"); return; } - //Displays available network connections using TCP + + // Displays available network connections using TCP + const currServ = Player.getCurrentServer(); post("Hostname IP Root Access"); - for (let i = 0; i < Player.getCurrentServer().serversOnNetwork.length; i++) { + for (let i = 0; i < currServ.serversOnNetwork.length; i++) { //Add hostname - let entry = Player.getCurrentServer().getServerOnNetwork(i); + let entry = getServerOnNetwork(currServ, i); if (entry == null) { continue; } entry = entry.hostname; @@ -1824,16 +1831,16 @@ let Terminal = { let numSpaces = 21 - entry.length; let spaces = Array(numSpaces+1).join(" "); entry += spaces; - entry += Player.getCurrentServer().getServerOnNetwork(i).ip; + entry += getServerOnNetwork(currServ, i).ip; //Calculate padding and add root access info let hasRoot; - if (Player.getCurrentServer().getServerOnNetwork(i).hasAdminRights) { + if (getServerOnNetwork(currServ, i).hasAdminRights) { hasRoot = 'Y'; } else { hasRoot = 'N'; } - numSpaces = 21 - Player.getCurrentServer().getServerOnNetwork(i).ip.length; + numSpaces = 21 - getServerOnNetwork(currServ, i).ip.length; spaces = Array(numSpaces+1).join(" "); entry += spaces; entry += hasRoot; @@ -1867,7 +1874,7 @@ let Terminal = { visited[s.ip] = 1; } for (var i = s.serversOnNetwork.length-1; i >= 0; --i) { - stack.push(s.getServerOnNetwork(i)); + stack.push(getServerOnNetwork(s, i)); depthQueue.push(d+1); } if (d == 0) {continue;} //Don't print current server diff --git a/src/engine.js b/src/engine.js index 7ad1175f9..5051c707d 100644 --- a/src/engine.js +++ b/src/engine.js @@ -21,14 +21,12 @@ import {CompanyPositions} from "./Company/CompanyP import {initCompanies} from "./Company/Companies"; import { Corporation } from "./Corporation/Corporation"; import {CONSTANTS} from "./Constants"; - - import {createDevMenu, closeDevMenu} from "./DevMenu"; import { Factions, initFactions } from "./Faction/Factions"; import { displayFactionContent, joinFaction, processPassiveFactionRepGain, inviteToFaction } from "./Faction/FactionHelpers"; -import {FconfSettings} from "./Fconf"; +import { FconfSettings } from "./Fconf/FconfSettings"; import {displayLocationContent, initLocationButtons} from "./Location"; import {Locations} from "./Locations"; @@ -36,7 +34,7 @@ import {displayHacknetNodesContent, processAllHacknetNodeEarnings, updateHacknetNodesContent} from "./HacknetNode"; import {iTutorialStart} from "./InteractiveTutorial"; import {initLiterature} from "./Literature"; -import {checkForMessagesToSend, initMessages} from "./Message"; +import { checkForMessagesToSend, initMessages } from "./Message/MessageHelpers"; import {inMission, currMission} from "./Missions"; import {initSingularitySFFlags, hasSingularitySF, hasCorporationSF} from "./NetscriptFunctions"; @@ -54,13 +52,14 @@ import {saveObject, loadGame} from "./SaveObject"; import { getCurrentEditor, loadAllRunningScripts, scriptEditorInit, - updateScriptEditorContent } from "./Script"; -import {AllServers, Server, initForeignServers} from "./Server"; + updateScriptEditorContent } from "./Script/ScriptHelpers"; +import { AllServers } from "./Server/AllServers"; +import { Server } from "./Server/Server"; +import { initForeignServers } from "./Server/ServerHelpers"; import {Settings} from "./Settings/Settings"; import { initSourceFiles, SourceFiles } from "./SourceFile"; import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags"; - -import {SpecialServerIps, initSpecialServerIps} from "./SpecialServerIps"; +import {SpecialServerIps, initSpecialServerIps} from "./Server/SpecialServerIps"; import {StockMarket, StockSymbols, SymbolToStockMap, initStockSymbols, initSymbolToStockMap, stockMarketCycle, @@ -1317,7 +1316,7 @@ const Engine = { Engine.setDisplayElements(); //Sets variables for important DOM elements Engine.start(); //Run main game loop and Scripts loop Player.init(); - initForeignServers(); + initForeignServers(Player.getHomeComputer()); initCompanies(); initFactions(); initAugmentations(); diff --git a/utils/IPAddress.ts b/utils/IPAddress.ts index 691f27505..948b055eb 100644 --- a/utils/IPAddress.ts +++ b/utils/IPAddress.ts @@ -1,5 +1,5 @@ -import {AllServers} from "../src/Server"; -import {getRandomByte} from "./helpers/getRandomByte"; +import { AllServers } from "../src/Server/AllServers"; +import { getRandomByte } from "./helpers/getRandomByte"; /* Functions to deal with manipulating IP addresses*/