Merge pull request #547 from danielyxie/server-code-refactor

Server code refactor
This commit is contained in:
danielyxie 2019-03-05 00:01:56 -08:00 committed by GitHub
commit f3dbdad011
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 1934 additions and 1715 deletions

@ -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";

@ -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";

@ -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/AllServers";
import { GetServerByHostname } from "./Server/ServerHelpers";
import { getRandomInt } from "../utils/helpers/getRandomInt";

@ -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";

@ -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";

@ -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" +

@ -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,
}

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

@ -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,
AddToAllServers } from "./Server/AllServers";
import { Server } from "./Server/Server";
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";

32
src/Message/Message.ts Normal file

@ -0,0 +1,32 @@
import { Reviver,
Generic_toJSON,
Generic_fromJSON } from "../../utils/JSONReviver";
export class Message {
// Initializes a Message Object from a JSON save state
static fromJSON(value: any): Message {
return Generic_fromJSON(Message, value.data);
}
// Name of Message file
filename: string = "";
// The text contains in the Message
msg: string = "";
// Flag indicating whether this Message has been received by the player
recvd: boolean = false;
constructor(filename="", msg="") {
this.filename = filename;
this.msg = msg;
this.recvd = false;
}
// Serialize the current object to a JSON save state
toJSON(): any {
return Generic_toJSON("Message", this);
}
}
Reviver.constructors.Message = Message;

@ -1,34 +1,15 @@
import { Augmentatation } from "./Augmentation/Augmentation";
import { Augmentations } from "./Augmentation/Augmentations";
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
import { Programs } from "./Programs/Programs";
import { inMission } from "./Missions";
import { Player } from "./Player";
import { redPillFlag } from "./RedPill";
import { GetServerByHostname } from "./Server";
import { Settings } from "./Settings/Settings";
import { Message } from "./Message";
import { Augmentatation } from "../Augmentation/Augmentation";
import { Augmentations } from "../Augmentation/Augmentations";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { Programs } from "../Programs/Programs";
import { inMission } from "../Missions";
import { Player } from "../Player";
import { redPillFlag } from "../RedPill";
import { GetServerByHostname } from "../Server/ServerHelpers";
import { Settings } from "../Settings/Settings";
import { dialogBoxCreate,
dialogBoxOpened} from "../utils/DialogBox";
import {Reviver, Generic_toJSON,
Generic_fromJSON} from "../utils/JSONReviver";
/* Message.js */
function Message(filename="", msg="") {
this.filename = filename;
this.msg = msg;
this.recvd = false;
}
Message.prototype.toJSON = function() {
return Generic_toJSON("Message", this);
}
Message.fromJSON = function(value) {
return Generic_fromJSON(Message, value.data);
}
Reviver.constructors.Message = Message;
dialogBoxOpened} from "../../utils/DialogBox";
//Sends message to player, including a pop up
function sendMessage(msg, forced=false) {

@ -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";

@ -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,
@ -306,9 +312,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;
@ -484,7 +490,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;
@ -513,7 +519,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) {

@ -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";

@ -20,6 +20,7 @@ export interface IPlayer {
city: string;
companyName: string;
corporation: any;
currentServer: string;
factions: string[];
hacknetNodes: any[];
hasWseAccount: boolean;

@ -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,
AddToAllServers } from "./Server/AllServers";
import { Server } from "./Server/Server";
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";

@ -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 { Message } from "./Message/Message";
import { initMessages,
Messages } 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,
AddToAllServers,
prestigeAllServers } from "./Server/AllServers";
import { Server } from "./Server/Server"
import { initForeignServers,
prestigeHomeComputer } from "./Server/ServerHelpers";
import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags";
import {SpecialServerIps, SpecialServerIpsMap,
import { SpecialServerIps,
SpecialServerIpsMap,
prestigeSpecialServerIps,
SpecialServerNames} from "./SpecialServerIps";
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) {

@ -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,
loadAllServers } from "./Server/AllServers";
import { Settings } from "./Settings/Settings";
import { loadSpecialServerIps,
SpecialServerIps } from "./Server/SpecialServerIps";
import {loadStockMarket, StockMarket} from "./StockMarket/StockMarket";
import { setTimeoutRef } from "./utils/SetTimeoutRef";

File diff suppressed because it is too large Load Diff

1
src/Script/RamCalculations.d.ts vendored Normal file

@ -0,0 +1 @@
export declare function calculateRamUsage(codeCopy: string): number;

@ -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;
}

161
src/Script/RunningScript.ts Normal file

@ -0,0 +1,161 @@
// Class representing a Script instance that is actively running.
// A Script can have multiple active instances
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";
import { getTimestamp } from "../../utils/helpers/getTimestamp";
export class RunningScript {
// Initializes a RunningScript Object from a JSON save state
static fromJSON(value: any): RunningScript {
return Generic_fromJSON(RunningScript, value.data);
}
// Script arguments
args: any[] = [];
// Holds a map of servers hacked, where server = key and the value for each
// server is an array of four numbers. The four numbers represent:
// [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken]
// This data is used for offline progress
dataMap: IMap<number[]> = {};
// Script filename
filename: string = "";
// This script's logs. An array of log entries
logs: string[] = [];
// Flag indicating whether the logs have been updated since
// the last time the UI was updated
logUpd: boolean = false;
// Total amount of hacking experience earned from this script when offline
offlineExpGained: number = 0;
// Total amount of money made by this script when offline
offlineMoneyMade: number = 0;
// Number of seconds that the script has been running offline
offlineRunningTime: number = 0.01;
// Total amount of hacking experience earned from this script when online
onlineExpGained: number = 0;
// Total amount of money made by this script when online
onlineMoneyMade: number = 0;
// Number of seconds that this script has been running online
onlineRunningTime: number = 0.01;
// How much RAM this script uses for ONE thread
ramUsage: number = 0;
// IP of the server on which this script is running
server: string = "";
// Number of threads that this script is running with
threads: number = 1;
constructor(script: Script | null = null, args: any[] = []) {
if (script == null) { return; }
this.filename = script.filename;
this.args = args;
this.server = script.server; //IP Address only
this.ramUsage = script.ramUsage;
}
getCode(): string {
const server = AllServers[this.server];
if (server == null) { return ""; }
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === this.filename) {
return server.scripts[i].code;
}
}
return "";
}
getRamUsage(): number {
if (this.ramUsage != null && this.ramUsage > 0) { return this.ramUsage; } // Use cached value
const server = AllServers[this.server];
if (server == null) { return 0; }
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === this.filename) {
// Cache the ram usage for the next call
this.ramUsage = server.scripts[i].ramUsage;
return this.ramUsage;
}
}
return 0;
}
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
//to improve performance
this.logs.shift();
}
let logEntry = txt;
if (FconfSettings.ENABLE_TIMESTAMPS) {
logEntry = "[" + getTimestamp() + "] " + logEntry;
}
this.logs.push(logEntry);
this.logUpd = true;
}
displayLog(): void {
for (var i = 0; i < this.logs.length; ++i) {
post(this.logs[i]);
}
}
clearLog(): void {
this.logs.length = 0;
}
// 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];
}
this.dataMap[serverIp][0] += moneyGained;
this.dataMap[serverIp][1] += n;
}
// 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() {
recordWeaken(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][3] += n;
}
// Serialize the current object to a JSON save state
toJSON(): any {
return Generic_toJSON("RunningScript", this);
}
}
Reviver.constructors.RunningScript = RunningScript;

106
src/Script/Script.ts Normal file

@ -0,0 +1,106 @@
// 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,
Reviver } from "../../utils/JSONReviver";
import { roundToTwo } from "../../utils/helpers/roundToTwo";
export class Script {
// Initializes a Script Object from a JSON save state
static fromJSON(value: any): Script {
return Generic_fromJSON(Script, value.data);
}
// Code for this script
code: string = "";
// Filename for the script file
filename: string = "";
// The dynamic module generated for this script when it is run.
// This is only applicable for NetscriptJS
module: any = "";
// Amount of RAM this Script requres to run
ramUsage: number = 0;
// IP of server that this script is on.
server: string = "";
constructor(fn: string = "", code: string = "", server: string = "") {
this.filename = fn;
this.code = code;
this.ramUsage = 0;
this.server = server; // IP of server this script is on
this.module = "";
if (this.code !== "") {this.updateRamUsage();}
};
download(): void {
const filename = this.filename + ".js";
const file = new Blob([this.code], {type: 'text/plain'});
if (window.navigator.msSaveOrOpenBlob) {// IE10+
window.navigator.msSaveOrOpenBlob(file, filename);
} else { // Others
var a = document.createElement("a"),
url = URL.createObjectURL(file);
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
setTimeoutRef(function() {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
}
// Save a script FROM THE SCRIPT EDITOR
saveScript(code: string, p: IPlayer): void {
if (routing.isOn(Page.ScriptEditor)) {
//Update code and filename
this.code = code.replace(/^\s+|\s+$/g, '');
const filenameElem: HTMLInputElement | null = document.getElementById("script-editor-filename") as HTMLInputElement;
if (filenameElem == null) {
console.error(`Failed to get Script filename DOM element`);
return;
}
this.filename = filenameElem!.value;
// Server
this.server = p.currentServer;
//Calculate/update ram usage, execution time, etc.
this.updateRamUsage();
this.module = "";
}
}
// Updates the script's RAM usage based on its code
async updateRamUsage() {
// TODO Commented this out because I think its unnecessary
// DOuble check/Test
// var codeCopy = this.code.repeat(1);
var res = await calculateRamUsage(this.code);
if (res !== -1) {
this.ramUsage = roundToTwo(res);
}
}
// Serialize the current object to a JSON save state
toJSON(): any {
return Generic_toJSON("Script", this);
}
}
Reviver.constructors.Script = Script;

441
src/Script/ScriptHelpers.js Normal file

@ -0,0 +1,441 @@
import { calculateRamUsage } from "./RamCalculations";
import { isScriptFilename } from "./ScriptHelpersTS";
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 { 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";
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;
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) {
console.error("Could not find 'script-editor-buttons-wrapper'");
return false;
}
// Beautify button
const beautifyButton = createElement("button", {
class: "std-button",
display: "inline-block",
innerText: "Beautify",
clickListener:()=>{
let editor = getCurrentEditor();
if (editor != null) {
editor.beautifyScript();
}
return false;
}
});
// Text that displays RAM calculation
scriptEditorRamText = createElement("p", {
display:"inline-block", margin:"10px", id:"script-editor-status-text"
});
// Label for checkbox (defined below)
const checkboxLabel = createElement("label", {
for:"script-editor-ram-check", margin:"4px", marginTop: "8px",
innerText:"Dynamic RAM Usage Checker", color:"white",
tooltip:"Enable/Disable the dynamic RAM Usage display. You may " +
"want to disable it for very long scripts because there may be " +
"performance issues"
});
// Checkbox for enabling/disabling dynamic RAM calculation
scriptEditorRamCheck = createElement("input", {
type:"checkbox", name:"script-editor-ram-check", id:"script-editor-ram-check",
margin:"4px", marginTop: "8px",
});
scriptEditorRamCheck.checked = true;
// Link to Netscript documentation
const documentationButton = createElement("a", {
class: "std-button",
display: "inline-block",
href:"https://bitburner.readthedocs.io/en/latest/index.html",
innerText:"Netscript Documentation",
target:"_blank",
});
// Save and Close button
const closeButton = createElement("button", {
class: "std-button",
display: "inline-block",
innerText: "Save & Close (Ctrl/Cmd + b)",
clickListener:()=>{
saveAndCloseScriptEditor();
return false;
}
});
// Add all buttons to the UI
wrapper.appendChild(beautifyButton);
wrapper.appendChild(closeButton);
wrapper.appendChild(scriptEditorRamText);
wrapper.appendChild(scriptEditorRamCheck);
wrapper.appendChild(checkboxLabel);
wrapper.appendChild(documentationButton);
// Initialize editors
const initParams = {
saveAndCloseFn: saveAndCloseScriptEditor,
quitFn: Engine.loadTerminalContent,
}
AceEditor.init(initParams);
CodeMirrorEditor.init(initParams);
// Setup the selector for which Editor to use
const editorSelector = document.getElementById("script-editor-option-editor");
if (editorSelector == null) {
console.error(`Could not find DOM Element for editor selector (id=script-editor-option-editor)`);
return false;
}
for (let i = 0; i < editorSelector.options.length; ++i) {
if (editorSelector.options[i].value === Settings.Editor) {
editorSelector.selectedIndex = i;
break;
}
}
editorSelector.onchange = () => {
const opt = editorSelector.value;
switch (opt) {
case EditorSetting.Ace:
const codeMirrorCode = CodeMirrorEditor.getCode();
const codeMirrorFn = CodeMirrorEditor.getFilename();
AceEditor.create();
CodeMirrorEditor.setInvisible();
AceEditor.openScript(codeMirrorFn, codeMirrorCode);
break;
case EditorSetting.CodeMirror:
const aceCode = AceEditor.getCode();
const aceFn = AceEditor.getFilename();
CodeMirrorEditor.create();
AceEditor.setInvisible();
CodeMirrorEditor.openScript(aceFn, aceCode);
break;
default:
console.error(`Unrecognized Editor Setting: ${opt}`);
return;
}
Settings.Editor = opt;
}
editorSelector.onchange(); // Trigger the onchange event handler
}
export function getCurrentEditor() {
switch (Settings.Editor) {
case EditorSetting.Ace:
return AceEditor;
case EditorSetting.CodeMirror:
return CodeMirrorEditor;
default:
console.log(`Invalid Editor Setting: ${Settings.Editor}`);
throw new Error(`Invalid Editor Setting: ${Settings.Editor}`);
return null;
}
}
//Updates RAM usage in script
export async function updateScriptEditorContent() {
var filename = document.getElementById("script-editor-filename").value;
if (scriptEditorRamCheck == null || !scriptEditorRamCheck.checked || !isScriptFilename(filename)) {
scriptEditorRamText.innerText = "N/A";
return;
}
let code;
try {
code = getCurrentEditor().getCode();
} catch(e) {
scriptEditorRamText.innerText = "RAM: ERROR";
return;
}
var codeCopy = code.repeat(1);
var ramUsage = await calculateRamUsage(codeCopy);
if (ramUsage !== -1) {
scriptEditorRamText.innerText = "RAM: " + numeralWrapper.format(ramUsage, '0.00') + " GB";
} else {
scriptEditorRamText.innerText = "RAM: Syntax Error";
}
}
//Define key commands in script editor (ctrl o to save + close, etc.)
$(document).keydown(function(e) {
if (Settings.DisableHotkeys === true) {return;}
if (routing.isOn(Page.ScriptEditor)) {
//Ctrl + b
if (e.keyCode == 66 && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
saveAndCloseScriptEditor();
}
}
});
function saveAndCloseScriptEditor() {
var filename = document.getElementById("script-editor-filename").value;
let code;
try {
code = getCurrentEditor().getCode();
} catch(e) {
dialogBoxCreate("Something went wrong when trying to save (getCurrentEditor().getCode()). Please report to game developer with details");
return;
}
if (ITutorial.isRunning && ITutorial.currStep === iTutorialSteps.TerminalTypeScript) {
//Make sure filename + code properly follow tutorial
if (filename !== "foodnstuff.script") {
dialogBoxCreate("Leave the script name as 'foodnstuff'!");
return;
}
code = code.replace(/\s/g, "");
if (code.indexOf("while(true){hack('foodnstuff');}") == -1) {
dialogBoxCreate("Please copy and paste the code from the tutorial!");
return;
}
//Save the script
let s = Player.getCurrentServer();
for (var i = 0; i < s.scripts.length; i++) {
if (filename == s.scripts[i].filename) {
s.scripts[i].saveScript(getCurrentEditor().getCode(), Player);
Engine.loadTerminalContent();
return iTutorialNextStep();
}
}
//If the current script does NOT exist, create a new one
let script = new Script();
script.saveScript(getCurrentEditor().getCode(), Player);
s.scripts.push(script);
return iTutorialNextStep();
}
if (filename == "") {
dialogBoxCreate("You must specify a filename!");
return;
}
if (checkValidFilename(filename) == false) {
dialogBoxCreate("Script filename can contain only alphanumerics, hyphens, and underscores");
return;
}
var s = Player.getCurrentServer();
if (filename === ".fconf") {
try {
parseFconfSettings(code);
} catch(e) {
dialogBoxCreate(`Invalid .fconf file: ${e}`);
return;
}
} else if (isScriptFilename(filename)) {
//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(getCurrentEditor().getCode(), Player);
Engine.loadTerminalContent();
return;
}
}
//If the current script does NOT exist, create a new one
var script = new Script();
script.saveScript(getCurrentEditor().getCode(), Player);
s.scripts.push(script);
} else if (filename.endsWith(".txt")) {
for (var i = 0; i < s.textFiles.length; ++i) {
if (s.textFiles[i].fn === filename) {
s.textFiles[i].write(code);
Engine.loadTerminalContent();
return;
}
}
var textFile = new TextFile(filename, code);
s.textFiles.push(textFile);
} else {
dialogBoxCreate("Invalid filename. Must be either a script (.script) or " +
" or text file (.txt)")
return;
}
Engine.loadTerminalContent();
}
//Checks that the string contains only valid characters for a filename, which are alphanumeric,
// underscores, hyphens, and dots
function checkValidFilename(filename) {
var regex = /^[.a-zA-Z0-9_-]+$/;
if (filename.match(regex)) {
return true;
}
return false;
}
//Called when the game is loaded. Loads all running scripts (from all servers)
//into worker scripts so that they will start running
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"); }
for (var property in AllServers) {
if (AllServers.hasOwnProperty(property)) {
var server = AllServers[property];
//Reset each server's RAM usage to 0
server.ramUsed = 0;
//Reset modules on all scripts
for (var i = 0; i < server.scripts.length; ++i) {
server.scripts[i].module = "";
}
if (skipScriptLoad) {
//Start game with no scripts
server.runningScripts.length = 0;
} else {
for (var j = 0; j < server.runningScripts.length; ++j) {
addWorkerScript(server.runningScripts[j], server);
//Offline production
total += scriptCalculateOfflineProduction(server.runningScripts[j]);
}
}
}
}
return total;
}
function scriptCalculateOfflineProduction(runningScriptObj) {
//The Player object stores the last update time from when we were online
var thisUpdate = new Date().getTime();
var lastUpdate = Player.lastUpdate;
var timePassed = (thisUpdate - lastUpdate) / 1000; //Seconds
//Calculate the "confidence" rating of the script's true production. This is based
//entirely off of time. We will arbitrarily say that if a script has been running for
//4 hours (14400 sec) then we are completely confident in its ability
var confidence = (runningScriptObj.onlineRunningTime) / 14400;
if (confidence >= 1) {confidence = 1;}
//Data map: [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken]
// Grow
for (var ip in runningScriptObj.dataMap) {
if (runningScriptObj.dataMap.hasOwnProperty(ip)) {
if (runningScriptObj.dataMap[ip][2] == 0 || runningScriptObj.dataMap[ip][2] == null) {continue;}
var serv = AllServers[ip];
if (serv == null) {continue;}
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, Player);
runningScriptObj.log(serv.hostname + " grown by " + numeralWrapper.format(growth * 100 - 100, '0.000000%') + " from grow() calls made while offline");
}
}
// Money from hacking
var totalOfflineProduction = 0;
for (var ip in runningScriptObj.dataMap) {
if (runningScriptObj.dataMap.hasOwnProperty(ip)) {
if (runningScriptObj.dataMap[ip][0] == 0 || runningScriptObj.dataMap[ip][0] == null) {continue;}
var serv = AllServers[ip];
if (serv == null) {continue;}
var production = 0.5 * runningScriptObj.dataMap[ip][0] / runningScriptObj.onlineRunningTime * timePassed;
production *= confidence;
if (production > serv.moneyAvailable) {
production = serv.moneyAvailable;
}
totalOfflineProduction += production;
Player.gainMoney(production);
Player.recordMoneySource(production, "hacking");
console.log(runningScriptObj.filename + " generated $" + production + " while offline by hacking " + serv.hostname);
runningScriptObj.log(runningScriptObj.filename + " generated $" + production + " while offline by hacking " + serv.hostname);
serv.moneyAvailable -= production;
if (serv.moneyAvailable < 0) {serv.moneyAvailable = 0;}
if (isNaN(serv.moneyAvailable)) {serv.moneyAvailable = 0;}
}
}
// Offline EXP gain
// A script's offline production will always be at most half of its online production.
var expGain = 0.5 * (runningScriptObj.onlineExpGained / runningScriptObj.onlineRunningTime) * timePassed;
expGain *= confidence;
Player.gainHackingExp(expGain);
// Update script stats
runningScriptObj.offlineMoneyMade += totalOfflineProduction;
runningScriptObj.offlineRunningTime += timePassed;
runningScriptObj.offlineExpGained += expGain;
// Fortify a server's security based on how many times it was hacked
for (var ip in runningScriptObj.dataMap) {
if (runningScriptObj.dataMap.hasOwnProperty(ip)) {
if (runningScriptObj.dataMap[ip][1] == 0 || runningScriptObj.dataMap[ip][1] == null) {continue;}
var serv = AllServers[ip];
if (serv == null) {continue;}
var timesHacked = Math.round(0.5 * runningScriptObj.dataMap[ip][1] / runningScriptObj.onlineRunningTime * timePassed);
console.log(runningScriptObj.filename + " hacked " + serv.hostname + " " + timesHacked + " times while offline");
runningScriptObj.log("Hacked " + serv.hostname + " " + timesHacked + " times while offline");
serv.fortify(CONSTANTS.ServerFortifyAmount * timesHacked);
}
}
// Weaken
for (var ip in runningScriptObj.dataMap) {
if (runningScriptObj.dataMap.hasOwnProperty(ip)) {
if (runningScriptObj.dataMap[ip][3] == 0 || runningScriptObj.dataMap[ip][3] == null) {continue;}
var serv = AllServers[ip];
if (serv == null) {continue;}
var timesWeakened = Math.round(0.5 * runningScriptObj.dataMap[ip][3] / runningScriptObj.onlineRunningTime * timePassed);
console.log(runningScriptObj.filename + " called weaken() on " + serv.hostname + " " + timesWeakened + " times while offline");
runningScriptObj.log("Called weaken() on " + serv.hostname + " " + timesWeakened + " times while offline");
serv.weaken(CONSTANTS.ServerWeakenAmount * timesWeakened);
}
}
return totalOfflineProduction;
}
//Returns a RunningScript object matching the filename and arguments on the
//designated server, and false otherwise
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)) {
return server.runningScripts[i];
}
}
return null;
}

@ -0,0 +1,4 @@
// Script helper functions
export function isScriptFilename(f: string) {
return f.endsWith(".js") || f.endsWith(".script") || f.endsWith(".ns");
}

@ -1,468 +0,0 @@
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 { createRandomIp,
ipExists } from "../utils/IPAddress";
import { serverMetadata } from "./data/servers";
import { Reviver,
Generic_toJSON,
Generic_fromJSON} from "../utils/JSONReviver";
import {isValidIPAddress} from "../utils/helpers/isValidIPAddress";
function Server(params={ip:createRandomIp(), hostname:""}) {
/* Properties */
//Connection information
this.ip = params.ip ? params.ip : createRandomIp();
var hostname = params.hostname;
var i = 0;
var suffix = "";
while (GetServerByHostname(hostname+suffix) != null) {
//Server already exists
suffix = "-" + i;
++i;
}
this.hostname = hostname + suffix;
this.organizationName = params.organizationName != null ? params.organizationName : "";
this.isConnectedTo = params.isConnectedTo != null ? params.isConnectedTo : false;
//Access information
this.hasAdminRights = params.adminRights != null ? params.adminRights : false;
this.purchasedByPlayer = params.purchasedByPlayer != null ? params.purchasedByPlayer : false;
this.manuallyHacked = false; //Flag that tracks whether or not the server has been hacked at least once
//RAM, CPU speed and Scripts
this.maxRam = params.maxRam != null ? params.maxRam : 0; //GB
this.ramUsed = 0;
this.cpuCores = 1; //Max of 8, affects hacking times and Hacking Mission starting Cores
this.scripts = [];
this.runningScripts = []; //Stores RunningScript objects
this.programs = [];
this.messages = [];
this.textFiles = [];
this.contracts = [];
this.dir = 0; //new Directory(this, null, ""); TODO
/* Hacking information (only valid for "foreign" aka non-purchased servers) */
this.requiredHackingSkill = params.requiredHackingSkill != null ? params.requiredHackingSkill : 1;
this.moneyAvailable = params.moneyAvailable != null ? params.moneyAvailable * BitNodeMultipliers.ServerStartingMoney : 0;
this.moneyMax = 25 * this.moneyAvailable * BitNodeMultipliers.ServerMaxMoney;
//Hack Difficulty is synonymous with server security. Base Difficulty = Starting difficulty
this.hackDifficulty = params.hackDifficulty != null ? params.hackDifficulty * BitNodeMultipliers.ServerStartingSecurity : 1;
this.baseDifficulty = this.hackDifficulty;
this.minDifficulty = Math.max(1, Math.round(this.hackDifficulty / 3));
this.serverGrowth = params.serverGrowth != null ? params.serverGrowth : 1; //Integer from 0 to 100. Affects money increase from grow()
//The IP's of all servers reachable from this one (what shows up if you run scan/netstat)
// NOTE: Only contains IP and not the Server objects themselves
this.serversOnNetwork = [];
//Port information, required for porthacking servers to get admin rights
this.numOpenPortsRequired = params.numOpenPortsRequired != null ? params.numOpenPortsRequired : 5;
this.sshPortOpen = false; //Port 22
this.ftpPortOpen = false; //Port 21
this.smtpPortOpen = false; //Port 25
this.httpPortOpen = false; //Port 80
this.sqlPortOpen = false; //Port 1433
this.openPortCount = 0;
};
Server.prototype.setMaxRam = function(ram) {
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) {
return this.scripts[i];
}
}
return null;
}
Server.prototype.capDifficulty = function() {
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
if (this.hackDifficulty > 1000000) {this.hackDifficulty = 1000000;}
}
//Strengthens a server's security level (difficulty) by the specified amount
Server.prototype.fortify = function(amt) {
this.hackDifficulty += amt;
this.capDifficulty();
}
Server.prototype.weaken = function(amt) {
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) {
var ret = {success: false, overwritten: false};
if (!isScriptFilename(fn)) { return ret; }
//Check if the script already exists, and overwrite it if it does
for (let i = 0; i < this.scripts.length; ++i) {
if (fn === this.scripts[i].filename) {
let script = this.scripts[i];
script.code = code;
script.updateRamUsage();
script.module = "";
ret.overwritten = true;
ret.success = true;
return ret;
}
}
//Otherwise, create a new script
var newScript = new Script();
newScript.filename = fn;
newScript.code = code;
newScript.updateRamUsage();
newScript.server = this.ip;
this.scripts.push(newScript);
ret.success = true;
return ret;
}
// 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};
if (!fn.endsWith("txt")) { return ret; }
//Check if the text file already exists, and overwrite if it does
for (let i = 0; i < this.textFiles.length; ++i) {
if (this.textFiles[i].fn === fn) {
ret.overwritten = true;
this.textFiles[i].text = txt;
ret.success = true;
return ret;
}
}
//Otherwise create a new text file
var newFile = new TextFile(fn, txt);
this.textFiles.push(newFile);
ret.success = true;
return ret;
}
Server.prototype.addContract = function(contract) {
this.contracts.push(contract);
}
Server.prototype.removeContract = function(contract) {
if (contract instanceof CodingContract) {
this.contracts = this.contracts.filter((c) => {
return c.fn !== contract.fn;
});
} else {
this.contracts = this.contracts.filter((c) => {
return c.fn !== contract;
});
}
}
Server.prototype.getContract = function(contractName) {
for (const contract of this.contracts) {
if (contract.fn === contractName) {
return contract;
}
}
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);
}
Reviver.constructors.Server = Server;
export function initForeignServers() {
/* Create a randomized network for all the foreign servers */
//Groupings for creating a randomized network
const networkLayers = [];
for (let i = 0; i < 15; i++) {
networkLayers.push([]);
}
// Essentially any property that is of type 'number | IMinMaxRange'
const propertiesToPatternMatch = [
"hackDifficulty",
"moneyAvailable",
"requiredHackingSkill",
"serverGrowth"
];
const toNumber = (value) => {
switch (typeof value) {
case 'number':
return value;
case 'object':
return getRandomInt(value.min, value.max);
default:
throw Error(`Do not know how to convert the type '${typeof value}' to a number`);
}
}
for (const metadata of serverMetadata) {
const serverParams = {
hostname: metadata.hostname,
ip: createRandomIp(),
numOpenPortsRequired: metadata.numOpenPortsRequired,
organizationName: metadata.organizationName
};
if (metadata.maxRamExponent !== undefined) {
serverParams.maxRam = Math.pow(2, toNumber(metadata.maxRamExponent));
}
for (const prop of propertiesToPatternMatch) {
if (metadata[prop] !== undefined) {
serverParams[prop] = toNumber(metadata[prop]);
}
}
const server = new Server(serverParams);
for (const filename of (metadata.literature || [])) {
server.messages.push(filename);
}
if (metadata.specialName !== undefined) {
SpecialServerIps.addIp(metadata.specialName, server.ip);
}
AddToAllServers(server);
if (metadata.networkLayer !== undefined) {
networkLayers[toNumber(metadata.networkLayer) - 1].push(server);
}
}
/* Create a randomized network for all the foreign servers */
const linkComputers = (server1, server2) => {
server1.serversOnNetwork.push(server2.ip);
server2.serversOnNetwork.push(server1.ip);
};
const getRandomArrayItem = (arr) => arr[Math.floor(Math.random() * arr.length)];
const linkNetworkLayers = (network1, selectServer) => {
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());
for (let i = 1; i < networkLayers.length; i++) {
linkNetworkLayers(networkLayers[i], () => getRandomArrayItem(networkLayers[i - 1]));
}
}
// 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) {
let ajdGrowthRate = 1 + (CONSTANTS.ServerBaseGrowthRate - 1) / server.hackDifficulty;
if(ajdGrowthRate > CONSTANTS.ServerMaxGrowthRate) {
ajdGrowthRate = CONSTANTS.ServerMaxGrowthRate;
}
const serverGrowthPercentage = server.serverGrowth / 100;
const cycles = Math.log(growth)/(Math.log(ajdGrowthRate)*Player.hacking_grow_mult*serverGrowthPercentage);
return cycles;
}
//Applied server growth for a single server. Returns the percentage growth
export function processSingleServerGrowth(server, numCycles) {
//Server growth processed once every 450 game cycles
const numServerGrowthCycles = Math.max(Math.floor(numCycles / 450), 0);
//Get adjusted growth rate, which accounts for server security
const growthRate = CONSTANTS.ServerBaseGrowthRate;
var adjGrowthRate = 1 + (growthRate - 1) / server.hackDifficulty;
if (adjGrowthRate > CONSTANTS.ServerMaxGrowthRate) {adjGrowthRate = CONSTANTS.ServerMaxGrowthRate;}
//Calculate adjusted server growth rate based on parameters
const serverGrowthPercentage = server.serverGrowth / 100;
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);
if (serverGrowth < 1) {
console.log("WARN: serverGrowth calculated to be less than 1");
serverGrowth = 1;
}
const oldMoneyAvailable = server.moneyAvailable;
server.moneyAvailable *= serverGrowth;
// in case of data corruption
if (server.moneyMax && isNaN(server.moneyAvailable)) {
server.moneyAvailable = server.moneyMax;
}
// cap at max
if (server.moneyMax && server.moneyAvailable > server.moneyMax) {
server.moneyAvailable = server.moneyMax;
}
// 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);
usedCycles = Math.max(0, usedCycles);
server.fortify(2 * CONSTANTS.ServerFortifyAmount * Math.ceil(usedCycles));
}
return server.moneyAvailable / oldMoneyAvailable;
}
export function prestigeHomeComputer(homeComp) {
const hasBitflume = homeComp.programs.includes(Programs.BitFlume.name);
homeComp.programs.length = 0; //Remove programs
homeComp.runningScripts = [];
homeComp.serversOnNetwork = [];
homeComp.isConnectedTo = true;
homeComp.ramUsed = 0;
homeComp.programs.push(Programs.NukeProgram.name);
if (hasBitflume) { homeComp.programs.push(Programs.BitFlume.name); }
//Update RAM usage on all scripts
homeComp.scripts.forEach(function(script) {
script.updateRamUsage();
});
homeComp.messages.length = 0; //Remove .lit and .msg files
homeComp.messages.push("hackers-starting-handbook.lit");
}
//List of all servers that exist in the game, indexed by their ip
let AllServers = {};
export function prestigeAllServers() {
for (var member in AllServers) {
delete AllServers[member];
}
AllServers = {};
}
export function loadAllServers(saveString) {
AllServers = JSON.parse(saveString, Reviver);
}
function SizeOfAllServers() {
var size = 0, key;
for (key in AllServers) {
if (AllServers.hasOwnProperty(key)) size++;
}
return size;
}
//Add a server onto the map of all servers in the game
export function AddToAllServers(server) {
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;
}
//Returns server object with corresponding hostname
// Relatively slow, would rather not use this a lot
export function GetServerByHostname(hostname) {
for (var ip in AllServers) {
if (AllServers.hasOwnProperty(ip)) {
if (AllServers[ip].hostname == hostname) {
return AllServers[ip];
}
}
}
return null;
}
//Get server by IP or hostname. Returns null if invalid
export function getServer(s) {
if (!isValidIPAddress(s)) {
return GetServerByHostname(s);
}
if(AllServers[s] !== undefined) {
return AllServers[s];
}
return null;
}
//Debugging tool
function PrintAllServers() {
for (var ip in AllServers) {
if (AllServers.hasOwnProperty(ip)) {
console.log("Ip: " + ip + ", hostname: " + AllServers[ip].hostname);
}
}
}
// Directory object (folders)
function Directory(server, parent, name) {
this.s = server; //Ref to server
this.p = parent; //Ref to parent directory
this.c = []; //Subdirs
this.n = name;
this.d = parent.d + 1; //We'll only have a maximum depth of 3 or something
this.scrs = []; //Holds references to the scripts in server.scripts
this.pgms = [];
this.msgs = [];
}
Directory.prototype.createSubdir = function(name) {
var subdir = new Directory(this.s, this, name);
}
Directory.prototype.getPath = function(name) {
var res = [];
var i = this;
while (i !== null) {
res.unshift(i.n, "/");
i = i.parent;
}
res.unshift("/");
return res.join("");
}
export {Server, AllServers};

132
src/Server/AllServers.ts Normal file

@ -0,0 +1,132 @@
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
export let AllServers: IMap<Server> = {};
// Saftely add a Server to the AllServers map
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");
}
AllServers[serverIp] = server;
}
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: Server[][] = [];
for (let i = 0; i < 15; i++) {
networkLayers.push([]);
}
// Essentially any property that is of type 'number | IMinMaxRange'
const propertiesToPatternMatch: string[] = [
"hackDifficulty",
"moneyAvailable",
"requiredHackingSkill",
"serverGrowth"
];
const toNumber = (value: any) => {
switch (typeof value) {
case 'number':
return value;
case 'object':
return getRandomInt(value.min, value.max);
default:
throw Error(`Do not know how to convert the type '${typeof value}' to a number`);
}
}
for (const metadata of serverMetadata) {
const serverParams: IServerParams = {
hostname: metadata.hostname,
ip: createRandomIp(),
numOpenPortsRequired: metadata.numOpenPortsRequired,
organizationName: metadata.organizationName
};
if (metadata.maxRamExponent !== undefined) {
serverParams.maxRam = Math.pow(2, toNumber(metadata.maxRamExponent));
}
for (const prop of propertiesToPatternMatch) {
if (metadata[prop] !== undefined) {
serverParams[prop] = toNumber(metadata[prop]);
}
}
const server = new Server(serverParams);
for (const filename of (metadata.literature || [])) {
server.messages.push(filename);
}
if (metadata.specialName !== undefined) {
SpecialServerIps.addIp(metadata.specialName, server.ip);
}
AddToAllServers(server);
if (metadata.networkLayer !== undefined) {
networkLayers[toNumber(metadata.networkLayer) - 1].push(server);
}
}
/* Create a randomized network for all the foreign servers */
const linkComputers = (server1: Server, server2: Server) => {
server1.serversOnNetwork.push(server2.ip);
server2.serversOnNetwork.push(server1.ip);
};
const getRandomArrayItem = (arr: any[]) => arr[Math.floor(Math.random() * arr.length)];
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], () => homeComputer);
for (let i = 1; i < networkLayers.length; i++) {
linkNetworkLayers(networkLayers[i], () => getRandomArrayItem(networkLayers[i - 1]));
}
}
export function prestigeAllServers() {
for (var member in AllServers) {
delete AllServers[member];
}
AllServers = {};
}
export function loadAllServers(saveString: string) {
AllServers = JSON.parse(saveString, Reviver);
}

303
src/Server/Server.ts Normal file

@ -0,0 +1,303 @@
// 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";
import { Generic_fromJSON,
Generic_toJSON,
Reviver } from "../../utils/JSONReviver";
interface IConstructorParams {
adminRights?: boolean;
hackDifficulty?: number;
hostname: string;
ip?: string;
isConnectedTo?: boolean;
maxRam?: number;
moneyAvailable?: number;
numOpenPortsRequired?: number;
organizationName?: string;
purchasedByPlayer?: boolean;
requiredHackingSkill?: number;
serverGrowth?: number;
}
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;
// Coding Contract files on this server
contracts: CodingContract[] = [];
// How many CPU cores this server has. Maximum of 8.
// Currently, this only affects hacking missions
cpuCores: number = 1;
// Flag indicating whether the FTP port is open
ftpPortOpen: boolean = false;
// Server Security Level
hackDifficulty: number = 1;
// Flag indicating whether player has admin/root access to this server
hasAdminRights: boolean = false;
// Hostname. Must be unique
hostname: string = "";
// Flag indicating whether HTTP Port is open
httpPortOpen: boolean = false;
// IP Address. Must be unique
ip: string = "";
// Flag indicating whether player is curently connected to this server
isConnectedTo: boolean = false;
// Flag indicating whether this server has been manually hacked (ie.
// hacked through Terminal) by the player
manuallyHacked: boolean = false;
// RAM (GB) available on this server
maxRam: number = 0;
// Message files AND Literature files on this Server
// For Literature files, this array contains only the filename (string)
// For Messages, it contains the actual Message object
// TODO Separate literature files into its own property
messages: (Message | string)[] = [];
// Minimum server security level that this server can be weakened to
minDifficulty: number = 1;
// How much money currently resides on the server and can be hacked
moneyAvailable: number = 0;
// Maximum amount of money that this server can hold
moneyMax: number = 0;
// Number of open ports required in order to gain admin/root access
numOpenPortsRequired: number = 5;
// How many ports are currently opened on the server
openPortCount: number = 0;
// Name of company/faction/etc. that this server belongs to.
// Optional, not applicable to all Servers
organizationName: string = "";
// Programs on this servers. Contains only the names of the programs
programs: string[] = [];
// Flag indicating wehther this is a purchased server
purchasedByPlayer: boolean = false;
// RAM (GB) used. i.e. unavailable RAM
ramUsed: number = 0;
// Hacking level required to hack this server
requiredHackingSkill: number = 1;
// RunningScript files on this server
runningScripts: RunningScript[] = [];
// Script files on this Server
scripts: Script[] = [];
// Parameter that affects how effectively this server's money can
// be increased using the grow() Netscript function
serverGrowth: number = 1;
// Contains the IP Addresses of all servers that are immediately
// reachable from this one
serversOnNetwork: string[] = [];
// Flag indicating whether SMTP Port is open
smtpPortOpen: boolean = false;
// Flag indicating whether SQL Port is open
sqlPortOpen: boolean = false;
// Flag indicating whether the SSH Port is open
sshPortOpen: boolean = false;
// Text files on this server
textFiles: TextFile[] = [];
constructor(params: IConstructorParams={hostname: "", ip: createRandomIp() }) {
/* Properties */
//Connection information
this.ip = params.ip ? params.ip : createRandomIp();
var hostname = params.hostname;
var i = 0;
var suffix = "";
while (GetServerByHostname(hostname+suffix) != null) {
//Server already exists
suffix = "-" + i;
++i;
}
this.hostname = hostname + suffix;
this.organizationName = params.organizationName != null ? params.organizationName : "";
this.isConnectedTo = params.isConnectedTo != null ? params.isConnectedTo : false;
//Access information
this.hasAdminRights = params.adminRights != null ? params.adminRights : false;
this.purchasedByPlayer = params.purchasedByPlayer != null ? params.purchasedByPlayer : false;
//RAM, CPU speed and Scripts
this.maxRam = params.maxRam != null ? params.maxRam : 0; //GB
/* Hacking information (only valid for "foreign" aka non-purchased servers) */
this.requiredHackingSkill = params.requiredHackingSkill != null ? params.requiredHackingSkill : 1;
this.moneyAvailable = params.moneyAvailable != null ? params.moneyAvailable * BitNodeMultipliers.ServerStartingMoney : 0;
this.moneyMax = 25 * this.moneyAvailable * BitNodeMultipliers.ServerMaxMoney;
//Hack Difficulty is synonymous with server security. Base Difficulty = Starting difficulty
this.hackDifficulty = params.hackDifficulty != null ? params.hackDifficulty * BitNodeMultipliers.ServerStartingSecurity : 1;
this.baseDifficulty = this.hackDifficulty;
this.minDifficulty = Math.max(1, Math.round(this.hackDifficulty / 3));
this.serverGrowth = params.serverGrowth != null ? params.serverGrowth : 1; //Integer from 0 to 100. Affects money increase from grow()
//Port information, required for porthacking servers to get admin rights
this.numOpenPortsRequired = params.numOpenPortsRequired != null ? params.numOpenPortsRequired : 5;
};
setMaxRam(ram: number): void {
this.maxRam = ram;
}
// 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;
}
// 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
if (this.hackDifficulty > 1000000) {this.hackDifficulty = 1000000;}
}
// Strengthens a server's security level (difficulty) by the specified amount
fortify(amt: number): void {
this.hackDifficulty += amt;
this.capDifficulty();
}
// 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
writeToScriptFile(fn: string, code: string) {
var ret = {success: false, overwritten: false};
if (!isScriptFilename(fn)) { return ret; }
//Check if the script already exists, and overwrite it if it does
for (let i = 0; i < this.scripts.length; ++i) {
if (fn === this.scripts[i].filename) {
let script = this.scripts[i];
script.code = code;
script.updateRamUsage();
script.module = "";
ret.overwritten = true;
ret.success = true;
return ret;
}
}
//Otherwise, create a new script
const newScript = new Script();
newScript.filename = fn;
newScript.code = code;
newScript.updateRamUsage();
newScript.server = this.ip;
this.scripts.push(newScript);
ret.success = true;
return ret;
}
// Write to a text file
// Overwrites existing files. Creates new files if the text file does not exist
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
for (let i = 0; i < this.textFiles.length; ++i) {
if (this.textFiles[i].fn === fn) {
ret.overwritten = true;
this.textFiles[i].text = txt;
ret.success = true;
return ret;
}
}
//Otherwise create a new text file
var newFile = new TextFile(fn, txt);
this.textFiles.push(newFile);
ret.success = true;
return ret;
}
addContract(contract: CodingContract) {
this.contracts.push(contract);
}
removeContract(contract: CodingContract) {
if (contract instanceof CodingContract) {
this.contracts = this.contracts.filter((c) => {
return c.fn !== contract.fn;
});
} else {
this.contracts = this.contracts.filter((c) => {
return c.fn !== contract;
});
}
}
getContract(contractName: string) {
for (const contract of this.contracts) {
if (contract.fn === contractName) {
return contract;
}
}
return null;
}
// Serialize the current object to a JSON save state
toJSON(): any {
return Generic_toJSON("Server", this);
}
}
Reviver.constructors.Server = Server;

125
src/Server/ServerHelpers.ts Normal file

@ -0,0 +1,125 @@
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: Server, growth: number, p: IPlayer) {
let ajdGrowthRate = 1 + (CONSTANTS.ServerBaseGrowthRate - 1) / server.hackDifficulty;
if(ajdGrowthRate > CONSTANTS.ServerMaxGrowthRate) {
ajdGrowthRate = CONSTANTS.ServerMaxGrowthRate;
}
const serverGrowthPercentage = server.serverGrowth / 100;
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: Server, numCycles: number, p: IPlayer) {
//Server growth processed once every 450 game cycles
const numServerGrowthCycles = Math.max(Math.floor(numCycles / 450), 0);
//Get adjusted growth rate, which accounts for server security
const growthRate = CONSTANTS.ServerBaseGrowthRate;
var adjGrowthRate = 1 + (growthRate - 1) / server.hackDifficulty;
if (adjGrowthRate > CONSTANTS.ServerMaxGrowthRate) {adjGrowthRate = CONSTANTS.ServerMaxGrowthRate;}
//Calculate adjusted server growth rate based on parameters
const serverGrowthPercentage = server.serverGrowth / 100;
const numServerGrowthCyclesAdjusted = numServerGrowthCycles * serverGrowthPercentage * BitNodeMultipliers.ServerGrowthRate;
//Apply serverGrowth for the calculated number of growth cycles
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;
}
const oldMoneyAvailable = server.moneyAvailable;
server.moneyAvailable *= serverGrowth;
// in case of data corruption
if (server.moneyMax && isNaN(server.moneyAvailable)) {
server.moneyAvailable = server.moneyMax;
}
// cap at max
if (server.moneyMax && server.moneyAvailable > server.moneyMax) {
server.moneyAvailable = server.moneyMax;
}
// 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, p);
usedCycles = Math.max(0, usedCycles);
server.fortify(2 * CONSTANTS.ServerFortifyAmount * Math.ceil(usedCycles));
}
return server.moneyAvailable / oldMoneyAvailable;
}
export function prestigeHomeComputer(homeComp: Server) {
const hasBitflume = homeComp.programs.includes(Programs.BitFlume.name);
homeComp.programs.length = 0; //Remove programs
homeComp.runningScripts = [];
homeComp.serversOnNetwork = [];
homeComp.isConnectedTo = true;
homeComp.ramUsed = 0;
homeComp.programs.push(Programs.NukeProgram.name);
if (hasBitflume) { homeComp.programs.push(Programs.BitFlume.name); }
//Update RAM usage on all scripts
homeComp.scripts.forEach(function(script) {
script.updateRamUsage();
});
homeComp.messages.length = 0; //Remove .lit and .msg files
homeComp.messages.push("hackers-starting-handbook.lit");
}
//Returns server object with corresponding hostname
// Relatively slow, would rather not use this a lot
export function GetServerByHostname(hostname: string): Server | null {
for (var ip in AllServers) {
if (AllServers.hasOwnProperty(ip)) {
if (AllServers[ip].hostname == hostname) {
return AllServers[ip];
}
}
}
return null;
}
//Get server by IP or hostname. Returns null if invalid
export function getServer(s: string): Server | null {
if (!isValidIPAddress(s)) {
return GetServerByHostname(s);
}
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]];
}

@ -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,
AddToAllServers } from "../Server/AllServers";
import { Server } from "../Server/Server";
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

@ -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<string> = {
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();
}

@ -81,6 +81,8 @@ interface IServerMetadata {
* A "unique" server that has special implications when the player manually hacks it.
*/
specialName?: string;
[key: string]: any;
}
/**

@ -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};

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

@ -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,
@ -1271,7 +1270,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();

@ -1,33 +0,0 @@
import {AllServers} from "../src/Server";
import {getRandomByte} from "./helpers/getRandomByte";
/* Functions to deal with manipulating IP addresses*/
//Generate a random IP address
//Will not return an IP address that already exists in the AllServers array
function createRandomIp() {
var ip = getRandomByte(99) + '.' +
getRandomByte(9) + '.' +
getRandomByte(9) + '.' +
getRandomByte(9);
//If the Ip already exists, recurse to create a new one
if (ipExists(ip)) {
return createRandomIp();
}
return ip;
}
//Returns true if the IP already exists in one of the game's servers
function ipExists(ip) {
for (var property in AllServers) {
if (AllServers.hasOwnProperty(property)) {
if (property == ip) {
return true;
}
}
}
return false;
}
export {createRandomIp, ipExists};

25
utils/IPAddress.ts Normal file

@ -0,0 +1,25 @@
import { AllServers } from "../src/Server/AllServers";
import { getRandomByte } from "./helpers/getRandomByte";
/* Functions to deal with manipulating IP addresses*/
//Generate a random IP address
//Will not return an IP address that already exists in the AllServers array
export function createRandomIp(): string {
const ip: string = getRandomByte(99) + '.' +
getRandomByte(9) + '.' +
getRandomByte(9) + '.' +
getRandomByte(9);
// If the Ip already exists, recurse to create a new one
if (ipExists(ip)) {
return createRandomIp();
}
return ip;
}
// Returns true if the IP already exists in one of the game's servers
export function ipExists(ip: string) {
return (AllServers[ip] != null);
}