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
+111
View 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
View 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
View 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;
}
+104
View File
@@ -0,0 +1,104 @@
/**
* 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";
// Returns the cost of purchasing a server with the given RAM
// Returns Infinity for invalid 'ram' arguments
export function getPurchaseServerCost(ram) {
const sanitizedRam = Math.round(ram);
if (isNaN(sanitizedRam) || !isPowerOfTwo(sanitizedRam)) {
return Infinity;
}
if (sanitizedRam > getPurchaseServerMaxRam()) {
return Infinity;
}
return sanitizedRam * CONSTANTS.BaseCostFor1GBOfRamServer * BitNodeMultipliers.PurchasedServerCost;
}
export function getPurchaseServerLimit() {
return Math.round(CONSTANTS.PurchasedServerLimit * BitNodeMultipliers.PurchasedServerLimit);
}
export function getPurchaseServerMaxRam() {
const ram = Math.round(CONSTANTS.PurchasedServerMaxRam * BitNodeMultipliers.PurchasedServerMaxRam);
// Round this to the nearest power of 2
return 1 << 31 - Math.clz32(ram);
}
// Manually purchase a server (NOT through Netscript)
export function purchaseServer(ram) {
const cost = getPurchaseServerCost(ram);
//Check if player has enough money
if (Player.money.lt(cost)) {
dialogBoxCreate("You don't have enough money to purchase this server!");
return;
}
//Maximum server limit
if (Player.purchasedServers.length >= getPurchaseServerLimit()) {
dialogBoxCreate("You have reached the maximum limit of " + getPurchaseServerLimit() + " servers. " +
"You cannot purchase any more. You can " +
"delete some of your purchased servers using the deleteServer() Netscript function in a script");
return;
}
var hostname = yesNoTxtInpBoxGetInput();
if (hostname == "") {
dialogBoxCreate("You must enter a hostname for your new server!");
return;
}
//Create server
var newServ = new Server({
ip:createRandomIp(), hostname:hostname, organizationName:"",
isConnectedTo:false, adminRights:true, purchasedByPlayer:true, maxRam:ram
});
AddToAllServers(newServ);
//Add to Player's purchasedServers array
Player.purchasedServers.push(newServ.ip);
//Connect new server to home computer
var homeComputer = Player.getHomeComputer();
homeComputer.serversOnNetwork.push(newServ.ip);
newServ.serversOnNetwork.push(homeComputer.ip);
Player.loseMoney(cost);
dialogBoxCreate("Server successfully purchased with hostname " + hostname);
}
// Manually upgrade RAM on home computer (NOT through Netscript)
export function purchaseRamForHomeComputer(cost) {
if (Player.money.lt(cost)) {
dialogBoxCreate("You do not have enough money to purchase additional RAM for your home computer");
return;
}
const homeComputer = Player.getHomeComputer();
if (homeComputer.maxRam >= CONSTANTS.HomeComputerMaxRam) {
dialogBoxCreate(`You cannot upgrade your home computer RAM because it is at its maximum possible value`);
return;
}
homeComputer.maxRam *= 2;
Player.loseMoney(cost);
dialogBoxCreate("Purchased additional RAM for home computer! It now has " + homeComputer.maxRam + "GB of RAM.");
}
+50
View File
@@ -0,0 +1,50 @@
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};