Started server code refactor

This commit is contained in:
danielyxie 2019-03-02 19:15:10 -08:00
parent af3323a111
commit e1b8a23f1e
13 changed files with 868 additions and 717 deletions

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 { Message } from "./Message";
import { Augmentations } from "./Augmentation/Augmentations"; import { Augmentatation } from "../Augmentation/Augmentation";
import { AugmentationNames } from "./Augmentation/data/AugmentationNames"; import { Augmentations } from "../Augmentation/Augmentations";
import { Programs } from "./Programs/Programs"; import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { inMission } from "./Missions"; import { Programs } from "../Programs/Programs";
import { Player } from "./Player"; import { inMission } from "../Missions";
import { redPillFlag } from "./RedPill"; import { Player } from "../Player";
import { GetServerByHostname } from "./Server"; import { redPillFlag } from "../RedPill";
import { Settings } from "./Settings/Settings"; import { GetServerByHostname } from "../Server";
import { Settings } from "../Settings/Settings";
import { dialogBoxCreate, import { dialogBoxCreate,
dialogBoxOpened} from "../utils/DialogBox"; 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;
//Sends message to player, including a pop up //Sends message to player, including a pop up
function sendMessage(msg, forced=false) { function sendMessage(msg, forced=false) {

155
src/Script/RunningScript.ts Normal file

@ -0,0 +1,155 @@
// Class representing a Script instance that is actively running.
// A Script can have multiple active instances
import { Script } from "./Script";
import { IMap } from "../types";
import { Generic_fromJSON,
Generic_toJSON,
Reviver } from "../../utils/JSONReviver";
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;
}
RunningScript.prototype.getCode = function() {
const server = AllServers[this.server];
if (server == null) { return ""; }
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === this.filename) {
return server.scripts[i].code;
}
}
return "";
}
RunningScript.prototype.getRamUsage = function() {
if (this.ramUsage != null && this.ramUsage > 0) { return this.ramUsage; } // Use cached value
const server = AllServers[this.server];
if (server == null) { return 0; }
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === this.filename) {
// Cache the ram usage for the next call
this.ramUsage = server.scripts[i].ramUsage;
return this.ramUsage;
}
}
return 0;
}
RunningScript.prototype.log = function(txt) {
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;
}
RunningScript.prototype.displayLog = function() {
for (var i = 0; i < this.logs.length; ++i) {
post(this.logs[i]);
}
}
RunningScript.prototype.clearLog = function() {
this.logs.length = 0;
}
//Update the moneyStolen and numTimesHack maps when hacking
RunningScript.prototype.recordHack = function(serverIp, moneyGained, n=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()
RunningScript.prototype.recordGrow = function(serverIp, n=1) {
if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) {
this.dataMap[serverIp] = [0, 0, 0, 0];
}
this.dataMap[serverIp][2] += n;
}
//Update the weaken map when calling weaken() {
RunningScript.prototype.recordWeaken = function(serverIp, n=1) {
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;

104
src/Script/Script.ts Normal file

@ -0,0 +1,104 @@
// Class representing a script file
// This does NOT represent a script that is actively running and
// being evaluated. See RunningScript for that
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(): void {
if (routing.isOn(Page.ScriptEditor)) {
//Update code and filename
const code = getCurrentEditor().getCode();
this.code = code.replace(/^\s+|\s+$/g, '');
const filenameElem: HTMLInputElement | null = document.getElementById("script-editor-filename") as HTMLInputElement;
if (filenameElem == null) {
console.error(`Failed to get Script filename DOM element`);
return;
}
this.filename = filenameElem!.value;
// Server
this.server = Player.currentServer;
//Calculate/update ram usage, execution time, etc.
this.updateRamUsage();
this.module = "";
}
}
// Updates the script's RAM usage based on its code
async updateRamUsage(): void {
// 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;

187
src/Script.js → src/Script/ScriptHelpers.js Executable file → Normal file

@ -308,44 +308,6 @@ function checkValidFilename(filename) {
return false; return false;
} }
function Script(fn = "", code = "", server = "") {
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();}
};
//Get the script data from the Script Editor and save it to the object
Script.prototype.saveScript = function() {
if (routing.isOn(Page.ScriptEditor)) {
//Update code and filename
const code = getCurrentEditor().getCode();
this.code = code.replace(/^\s+|\s+$/g, '');
var filename = document.getElementById("script-editor-filename").value;
this.filename = filename;
//Server
this.server = Player.currentServer;
//Calculate/update ram usage, execution time, etc.
this.updateRamUsage();
this.module = "";
}
}
//Updates how much RAM the script uses when it is running.
Script.prototype.updateRamUsage = async function() {
var codeCopy = this.code.repeat(1);
var res = await calculateRamUsage(codeCopy);
if (res !== -1) {
this.ramUsage = roundToTwo(res);
}
}
// These special strings are used to reference the presence of a given logical // These special strings are used to reference the presence of a given logical
// construct within a user script. // construct within a user script.
const specialReferenceIF = "__SPECIAL_referenceIf"; const specialReferenceIF = "__SPECIAL_referenceIf";
@ -747,36 +709,6 @@ async function calculateRamUsage(codeCopy) {
return ramUsage; return ramUsage;
} }
Script.prototype.download = function() {
var filename = this.filename + ".js";
var 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);
}
}
Script.prototype.toJSON = function() {
return Generic_toJSON("Script", this);
}
Script.fromJSON = function(value) {
return Generic_fromJSON(Script, value.data);
}
Reviver.constructors.Script = Script;
//Called when the game is loaded. Loads all running scripts (from all servers) //Called when the game is loaded. Loads all running scripts (from all servers)
//into worker scripts so that they will start running //into worker scripts so that they will start running
function loadAllRunningScripts() { function loadAllRunningScripts() {
@ -916,122 +848,5 @@ function findRunningScript(filename, args, server) {
return null; return null;
} }
function RunningScript(script, args) {
if (script == null || script == undefined) { return; }
this.filename = script.filename;
this.args = args;
this.server = script.server; //IP Address only
this.ramUsage = script.ramUsage;
this.logs = []; //Script logging. Array of strings, with each element being a log entry
this.logUpd = false;
//Stats to display on the Scripts menu, and used to determine offline progress
this.offlineRunningTime = 0.01; //Seconds
this.offlineMoneyMade = 0;
this.offlineExpGained = 0;
this.onlineRunningTime = 0.01; //Seconds
this.onlineMoneyMade = 0;
this.onlineExpGained = 0;
this.threads = 1;
// Holds a map of all servers, 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
this.dataMap = {};
}
RunningScript.prototype.getCode = function() {
const server = AllServers[this.server];
if (server == null) { return ""; }
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === this.filename) {
return server.scripts[i].code;
}
}
return "";
}
RunningScript.prototype.getRamUsage = function() {
if (this.ramUsage != null && this.ramUsage > 0) { return this.ramUsage; } // Use cached value
const server = AllServers[this.server];
if (server == null) { return 0; }
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === this.filename) {
// Cache the ram usage for the next call
this.ramUsage = server.scripts[i].ramUsage;
return this.ramUsage;
}
}
return 0;
}
RunningScript.prototype.log = function(txt) {
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;
}
RunningScript.prototype.displayLog = function() {
for (var i = 0; i < this.logs.length; ++i) {
post(this.logs[i]);
}
}
RunningScript.prototype.clearLog = function() {
this.logs.length = 0;
}
//Update the moneyStolen and numTimesHack maps when hacking
RunningScript.prototype.recordHack = function(serverIp, moneyGained, n=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()
RunningScript.prototype.recordGrow = function(serverIp, n=1) {
if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) {
this.dataMap[serverIp] = [0, 0, 0, 0];
}
this.dataMap[serverIp][2] += n;
}
//Update the weaken map when calling weaken() {
RunningScript.prototype.recordWeaken = function(serverIp, n=1) {
if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) {
this.dataMap[serverIp] = [0, 0, 0, 0];
}
this.dataMap[serverIp][3] += n;
}
RunningScript.prototype.toJSON = function() {
return Generic_toJSON("RunningScript", this);
}
RunningScript.fromJSON = function(value) {
return Generic_fromJSON(RunningScript, value.data);
}
Reviver.constructors.RunningScript = RunningScript;
export {loadAllRunningScripts, findRunningScript, export {loadAllRunningScripts, findRunningScript,
RunningScript, Script, scriptEditorInit, isScriptFilename}; scriptEditorInit, isScriptFilename};

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

111
src/Server/AllServers.ts Normal file

@ -0,0 +1,111 @@
import { ipExists } from "../../utils/IPAddress";
// Map of all Servers that exist in the game
// Key (string) = IP
// Value = Server object
let AllServers = {};
// Saftely add a Server to the AllServers map
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;
}
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]));
}
}
export function prestigeAllServers() {
for (var member in AllServers) {
delete AllServers[member];
}
AllServers = {};
}
export function loadAllServers(saveString) {
AllServers = JSON.parse(saveString, Reviver);
}

303
src/Server/Server.ts Normal file

@ -0,0 +1,303 @@
// Class representing a single generic Server
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 { 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 {
// 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;
}
//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;

126
src/Server/ServerHelpers.js Normal file

@ -0,0 +1,126 @@
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
import { CodingContract,
ContractTypes } from "./CodingContracts";
import { CONSTANTS } from "./Constants";
import { Script,
isScriptFilename } from "./Script";
import { Player } from "./Player";
import { Programs } from "./Programs/Programs";
import { SpecialServerIps } from "./SpecialServerIps";
import { TextFile } from "./TextFile";
import { getRandomInt } from "../utils/helpers/getRandomInt";
import { serverMetadata } from "./data/servers";
import { Reviver,
Generic_toJSON,
Generic_fromJSON} from "../utils/JSONReviver";
import {isValidIPAddress} from "../utils/helpers/isValidIPAddress";
// 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");
}
function SizeOfAllServers() {
var size = 0, key;
for (key in AllServers) {
if (AllServers.hasOwnProperty(key)) size++;
}
return size;
}
//Returns server object with corresponding hostname
// Relatively slow, would rather not use this a lot
export function GetServerByHostname(hostname) {
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;
}

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