diff --git a/lib/Model/ClusterManager.js b/lib/Model/ClusterManager.js new file mode 100644 index 00000000..1b8fcc59 --- /dev/null +++ b/lib/Model/ClusterManager.js @@ -0,0 +1,57 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const NodeInstance_1 = require("./NodeInstance"); +const Point_1 = require("./Point"); +const Grid_1 = require("./Grid"); +/** + * Manages all nodes within the cluster + */ +class ClusterManager { + constructor() { + this.Grid = new Grid_1.Grid(); + } + /** + * Generates a x*y grid of 1x1 nodes + * @param width How wide to make the node grid + * @param height How high to make the node grid + * @param nodeNamePrefix The prefix used for the initial node names. The name will be in the format _x_y. + */ + GenerateBasicGridOfNodes(width, height, nodeNamePrefix) { + for (let x = 0; x < width; x++) { + for (let y = 0; y < height; y++) { + let newNode = new NodeInstance_1.NodeInstance(nodeNamePrefix + "-" + (x + 1) + "-" + (y + 1), new Point_1.Point(x, y), 1, 1); + this.Grid.AddNodeToGrid(newNode); + } + } + } + /** + * Assigns all nodes that currently do not have a server assigned to the given server + * @param server Server to assign all unassigned nodes to + */ + AssignAllUnassignedNodesToServer(server) { + this.CheckServerArray(server); + this.Grid.GetNodes() + .filter(node => { node.Server === null; }) + .forEach(node => { + server.AddNodeInstance(node); + node.Server = server; + }); + } + /** + * Assigns a node to a server + * @param x x co-ordinate of the node + * @param y y co-ordinate of the node + * @param serverName server name + */ + AssignNodeToServer(x, y, server) { + this._nodeArray[x][y].Server = server; + } + CheckServerArray(server) { + let s = this._servers[server.Name]; + if (s == null) { + this._servers[server.Name] = server; + } + } +} +exports.ClusterManager = ClusterManager; +//# sourceMappingURL=ClusterManager.js.map \ No newline at end of file diff --git a/lib/Model/ClusterioMasterProxy.js b/lib/Model/ClusterioMasterProxy.js new file mode 100644 index 00000000..0d82a841 --- /dev/null +++ b/lib/Model/ClusterioMasterProxy.js @@ -0,0 +1,112 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const child_process = require("child_process"); +const needle = require("needle"); +const Helpers_1 = require("./Helpers"); +const fs = require("fs"); +const util = require("util"); +/** + * Contains all interactions between the cluster model, the + * Clusterio master.js/client.js infrastructure and the operating system. + */ +class ClusterioMasterProxy { + constructor(masterIP, masterPort, masterAuthToken, serviceHostType) { + this._masterIP = `${masterIP}:${masterPort}`; + this._needleOptionsWithTokenAuthHeader = { + headers: { + 'x-access-token': masterAuthToken, + }, + }; + } + async RunRconCommand(instanceId, command, logDescription) { + console.log("Running command: " + logDescription); + let request = { instanceID: instanceId, command }; + let response = await needle("post", this._masterIP + "/api/runCommand", request, this._needleOptionsWithTokenAuthHeader); + } + async GetSlaves() { + let response = await needle("get", this._masterIP + "/api/slaves", this._needleOptionsWithTokenAuthHeader); + let body = response.body; + let slaves = new Array(); + Object.entries(body).forEach((keyValuePair, index, array) => { + let slave = keyValuePair[1]; + slave.id = keyValuePair[0]; + slaves.push(slave); + }); + return slaves; + } + async CreateNodeInstancesOnLocalServer(nodes) { + await Helpers_1.asyncForEach(nodes, async (node) => { + await this.CreateNodeInstanceOnLocalServer(node); + }); + await Helpers_1.asyncForEach(nodes, async (node) => { + await this.CreateTeleportZonesForANodeInstance(node); + }); + await this.sleep(1000); + await Helpers_1.asyncForEach(nodes, async (node) => { + await this.CreateTeleportRestrictionsForANodeInstance(node); + await this.sleep(100); + }); + } + async CreateNodeInstanceOnLocalServer(node) { + console.log(`Creating instance ${node.Name}...`); + await this.CreateNodeInstanceOnLocalServerUsingPm2(node); + console.log("Waiting for node to connect to cluster master..."); + while (true) { + await this.sleep(1000); + //TODO: Use /api/getSlaveMeta/ + let slaves = await this.GetSlaves(); + let slave = slaves.find((slave, index, array) => slave.instanceName === node.Name); + if (slave != null) { + node.ClusterioWorldId = slave.id; + break; + } + } + } + async CreateNodeInstanceOnLocalServerUsingPm2(node) { + let proc = child_process.spawn("pm2", ['start', '--name', node.Name, 'client.js', '--', 'start', node.Name]); + proc.stdout.on('data', (data) => { console.log(`stdout: ${data}`); }); + proc.stderr.on('data', data => { console.log(`stderr: ${data}`); }); + proc.on('error', error => { console.log(`error: ${error}`); }); + } + async CreateNodeInstanceOnLocalServerUsingScreen(node) { + // first call of client.js start for a new node creates map from HotPatch scenario then exits. + let proc = child_process.spawn("screen", ['-dmS', node.Name, '-L', '-Logfile', node.Name + '.log', 'node', 'client.js', 'start', node.Name]); + proc.stdout.on('data', (data) => { console.log(`stdout: ${data}`); }); + proc.stderr.on('data', data => { console.log(`stderr: ${data}`); }); + proc.on('error', error => { console.log(`error: ${error}`); }); + console.log("Waiting for creation of new map..."); + // create awaiter for fs.readFile + const readFile = util.promisify(fs.readFile); + while (true) { + await this.sleep(1000); + let fileData = await readFile(node.Name + '.log'); + if (fileData.includes('Instance created')) { + break; + } + } + // start node + proc = child_process.spawn("screen", ['-dmS', node.Name, '-L', '-Logfile', node.Name + '.log', 'node', 'client.js', 'start', node.Name]); + proc.stdout.on('data', data => { console.log(`stdout: ${data}`); }); + proc.stderr.on('data', data => { console.log(`stderr: ${data}`); }); + proc.on('error', error => { console.log(`error: ${error}`); }); + } + async CreateTeleportZonesForANodeInstance(nodeInstance) { + await Helpers_1.asyncForEach(nodeInstance.TeleportZones.values(), async (zone) => { + let o = zone.GenerateTeleportZoneInFactorioCoordinates(); + let cmd = `CreateZone('${o[0]}',${o[1]},${o[2]},${o[3]},${o[4]},${o[5]},true)`; + await this.RunRconCommand(nodeInstance.ClusterioWorldId, `/c remote.call('trainTeleports','runCode',\"${cmd}\")`, cmd); + }); + } + async CreateTeleportRestrictionsForANodeInstance(nodeInstance) { + await Helpers_1.asyncForEach(nodeInstance.TeleportZones.values(), async (zone) => { + let o = zone.GenerateZoneRestriction(); + let cmd = `CreateZoneRestriction(${o[0]},'${o[1]}','${o[2]}')`; + await this.RunRconCommand(nodeInstance.ClusterioWorldId, `/c remote.call('trainTeleports','runCode',\"${cmd}\")`, cmd); + }); + } + sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } +} +exports.ClusterioMasterProxy = ClusterioMasterProxy; +//# sourceMappingURL=ClusterioMasterProxy.js.map \ No newline at end of file diff --git a/lib/Model/Config.js b/lib/Model/Config.js new file mode 100644 index 00000000..5c334bb3 --- /dev/null +++ b/lib/Model/Config.js @@ -0,0 +1,8 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +/** The multiplier used to convert from the grid's coordinate system to factorio's. + * 1000 means a 1x1 grid node will have a factorio map size of 1000x1000. */ +exports.GridToFactorioCoordinateSystemMultiplier = 1000; +/** The width of teleport zones when they get created. */ +exports.TeleportZoneWidth = 50; +//# sourceMappingURL=Config.js.map \ No newline at end of file diff --git a/lib/Model/Grid.js b/lib/Model/Grid.js new file mode 100644 index 00000000..c67d318a --- /dev/null +++ b/lib/Model/Grid.js @@ -0,0 +1,177 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const typescript_collections_1 = require("typescript-collections"); +const Point_1 = require("./Point"); +const TeleportZone_1 = require("./TeleportZone"); +const TeleportZoneDirection_1 = require("./TeleportZoneDirection"); +class Grid { + constructor() { + this._nodes = new typescript_collections_1.Dictionary(); + this._minX = 0; + this._minY = 0; + this._maxX = 0; + this._maxY = 0; + } + GetNodes() { + return this._nodes.values(); + } + /** + * Adds a node to the grid + * @param topLeftCoordinate The top left x,y coordinate of the node + * @param nodeInstance + */ + AddNodeToGrid(nodeInstance) { + let topLeftCoordinate = nodeInstance.TopLeftCoordinate; + let pointsToSave = new Array(); + // check all points that make up nodeInstance for existing nodes + for (let x = topLeftCoordinate.X; x < topLeftCoordinate.X + nodeInstance.Width; x++) { + for (let y = topLeftCoordinate.Y; y < topLeftCoordinate.Y + nodeInstance.Height; y++) { + let currentPoint = new Point_1.Point(x, y); + let existingNode = this._nodes.getValue(currentPoint); + if (existingNode != null) { + throw new Error("Cannot add node to grid - collision detected with node '" + existingNode.Name + "'"); + } + pointsToSave.push(currentPoint); + } + } + // no existing nodes found to assign node to these points + for (let i = 0; i < pointsToSave.length; i++) { + this._nodes.setValue(pointsToSave[i], nodeInstance); + } + //TODO: Refactor + //#region Set up teleport zones + // check surrounding points for nodes + // check north points + let y = topLeftCoordinate.Y - 1; + for (let x = topLeftCoordinate.X; x < topLeftCoordinate.X + nodeInstance.Width; x++) { + let currentPoint = new Point_1.Point(x, y); + let existingNode = this._nodes.getValue(currentPoint); + // found a node - set up teleport zone + if (existingNode != null) { + // Create/Update teleport node in existing node to new node + let teleportZoneFromExistingToNew = existingNode.TeleportZones.getValue(nodeInstance.Key); + if (teleportZoneFromExistingToNew == null) { + teleportZoneFromExistingToNew = new TeleportZone_1.TeleportZone(existingNode, TeleportZoneDirection_1.TeleportZoneDirection.South, currentPoint, currentPoint, existingNode.TeleportZones.size(), nodeInstance); + existingNode.TeleportZones.setValue(nodeInstance.Key, teleportZoneFromExistingToNew); + } + else { + teleportZoneFromExistingToNew.BottomRightCoordinate = currentPoint; + } + // Create/Update teleport node in new node to existing node + let teleportZoneFromNewToExisting = nodeInstance.TeleportZones.getValue(existingNode.Key); + let pointInNewNode = new Point_1.Point(currentPoint.X, currentPoint.Y + 1); + if (teleportZoneFromNewToExisting == null) { + teleportZoneFromNewToExisting = new TeleportZone_1.TeleportZone(nodeInstance, TeleportZoneDirection_1.TeleportZoneDirection.North, pointInNewNode, pointInNewNode, nodeInstance.TeleportZones.size(), existingNode); + nodeInstance.TeleportZones.setValue(existingNode.Key, teleportZoneFromNewToExisting); + } + else { + teleportZoneFromNewToExisting.BottomRightCoordinate = currentPoint; + } + } + } + // check south points + y = topLeftCoordinate.Y + nodeInstance.Height + 1; + for (let x = topLeftCoordinate.X; x < topLeftCoordinate.X + nodeInstance.Width; x++) { + let currentPoint = new Point_1.Point(x, y); + let existingNode = this._nodes.getValue(currentPoint); + // found a node - set up teleport zone + if (existingNode != null) { + // Create/Update teleport node in existing node to new node + let teleportZoneFromExistingToNew = existingNode.TeleportZones.getValue(nodeInstance.Key); + if (teleportZoneFromExistingToNew == null) { + teleportZoneFromExistingToNew = new TeleportZone_1.TeleportZone(existingNode, TeleportZoneDirection_1.TeleportZoneDirection.North, currentPoint, currentPoint, existingNode.TeleportZones.size(), nodeInstance); + existingNode.TeleportZones.setValue(nodeInstance.Key, teleportZoneFromExistingToNew); + } + else { + teleportZoneFromExistingToNew.BottomRightCoordinate = currentPoint; + } + // Create/Update teleport node in new node to existing node + let teleportZoneFromNewToExisting = nodeInstance.TeleportZones.getValue(existingNode.Key); + let pointInNewNode = new Point_1.Point(currentPoint.X, currentPoint.Y - 1); + if (teleportZoneFromNewToExisting == null) { + teleportZoneFromNewToExisting = new TeleportZone_1.TeleportZone(nodeInstance, TeleportZoneDirection_1.TeleportZoneDirection.South, pointInNewNode, pointInNewNode, nodeInstance.TeleportZones.size(), existingNode); + nodeInstance.TeleportZones.setValue(existingNode.Key, teleportZoneFromNewToExisting); + } + else { + teleportZoneFromNewToExisting.BottomRightCoordinate = currentPoint; + } + } + } + // check west points + let x = topLeftCoordinate.X - 1; + for (let y = topLeftCoordinate.Y; y < topLeftCoordinate.Y + nodeInstance.Height; y++) { + let currentPoint = new Point_1.Point(x, y); + let existingNode = this._nodes.getValue(currentPoint); + // found a node - set up teleport zone + if (existingNode != null) { + // Create/Update teleport node in existing node to new node + let teleportZoneFromExistingToNew = existingNode.TeleportZones.getValue(nodeInstance.Key); + if (teleportZoneFromExistingToNew == null) { + teleportZoneFromExistingToNew = new TeleportZone_1.TeleportZone(existingNode, TeleportZoneDirection_1.TeleportZoneDirection.East, currentPoint, currentPoint, existingNode.TeleportZones.size(), nodeInstance); + existingNode.TeleportZones.setValue(nodeInstance.Key, teleportZoneFromExistingToNew); + } + else { + teleportZoneFromExistingToNew.BottomRightCoordinate = currentPoint; + } + // Create/Update teleport node in new node to existing node + let teleportZoneFromNewToExisting = nodeInstance.TeleportZones.getValue(existingNode.Key); + let pointInNewNode = new Point_1.Point(currentPoint.X + 1, currentPoint.Y); + if (teleportZoneFromNewToExisting == null) { + teleportZoneFromNewToExisting = new TeleportZone_1.TeleportZone(nodeInstance, TeleportZoneDirection_1.TeleportZoneDirection.West, pointInNewNode, pointInNewNode, nodeInstance.TeleportZones.size(), existingNode); + nodeInstance.TeleportZones.setValue(existingNode.Key, teleportZoneFromNewToExisting); + } + else { + teleportZoneFromNewToExisting.BottomRightCoordinate = currentPoint; + } + } + } + // check east points + x = topLeftCoordinate.X + nodeInstance.Width + 1; + for (let y = topLeftCoordinate.Y; y < topLeftCoordinate.Y + nodeInstance.Height; y++) { + let currentPoint = new Point_1.Point(x, y); + let existingNode = this._nodes.getValue(currentPoint); + // found a node - set up teleport zone + if (existingNode != null) { + // Create/Update teleport node in existing node to new node + let teleportZoneFromExistingToNew = existingNode.TeleportZones.getValue(nodeInstance.Key); + if (teleportZoneFromExistingToNew == null) { + teleportZoneFromExistingToNew = new TeleportZone_1.TeleportZone(existingNode, TeleportZoneDirection_1.TeleportZoneDirection.West, currentPoint, currentPoint, existingNode.TeleportZones.size(), nodeInstance); + existingNode.TeleportZones.setValue(nodeInstance.Key, teleportZoneFromExistingToNew); + } + else { + teleportZoneFromExistingToNew.BottomRightCoordinate = currentPoint; + } + // Create/Update teleport node in new node to existing node + let teleportZoneFromNewToExisting = nodeInstance.TeleportZones.getValue(existingNode.Key); + let pointInNewNode = new Point_1.Point(currentPoint.X - 1, currentPoint.Y); + if (teleportZoneFromNewToExisting == null) { + teleportZoneFromNewToExisting = new TeleportZone_1.TeleportZone(nodeInstance, TeleportZoneDirection_1.TeleportZoneDirection.East, pointInNewNode, pointInNewNode, nodeInstance.TeleportZones.size(), existingNode); + nodeInstance.TeleportZones.setValue(existingNode.Key, teleportZoneFromNewToExisting); + } + else { + teleportZoneFromNewToExisting.BottomRightCoordinate = currentPoint; + } + } + } + //#endregion + this._nodes.setValue(topLeftCoordinate, nodeInstance); + this._minX = Math.min(this._minX, topLeftCoordinate.X); + this._minY = Math.min(this._minY, topLeftCoordinate.Y); + this._maxX = Math.max(this._maxX, topLeftCoordinate.X + nodeInstance.Width); + this._maxY = Math.max(this._maxY, topLeftCoordinate.Y + nodeInstance.Height); + } + get Width() { + return this._maxX - this._minX; + } + get Height() { + return this._maxY - this._minY; + } + get TopLeftCoordinate() { + return new Point_1.Point(this._minX, this._minY); + } + get BottomRightCoordinate() { + return new Point_1.Point(this._maxX, this._maxY); + } +} +exports.Grid = Grid; +//# sourceMappingURL=Grid.js.map \ No newline at end of file diff --git a/lib/Model/Helpers.js b/lib/Model/Helpers.js new file mode 100644 index 00000000..7fa3597e --- /dev/null +++ b/lib/Model/Helpers.js @@ -0,0 +1,10 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//TODO: Make generic +async function asyncForEach(array, callback) { + for (let index = 0; index < array.length; index++) { + await callback(array[index], index, array); + } +} +exports.asyncForEach = asyncForEach; +//# sourceMappingURL=Helpers.js.map \ No newline at end of file diff --git a/lib/Model/IApiSlaves.js b/lib/Model/IApiSlaves.js new file mode 100644 index 00000000..350b42ae --- /dev/null +++ b/lib/Model/IApiSlaves.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//# sourceMappingURL=IApiSlaves.js.map \ No newline at end of file diff --git a/lib/Model/IClusterioMasterProxy.js b/lib/Model/IClusterioMasterProxy.js new file mode 100644 index 00000000..712c6897 --- /dev/null +++ b/lib/Model/IClusterioMasterProxy.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//# sourceMappingURL=IClusterioMasterProxy.js.map \ No newline at end of file diff --git a/lib/Model/ModelController.js b/lib/Model/ModelController.js new file mode 100644 index 00000000..37929de6 --- /dev/null +++ b/lib/Model/ModelController.js @@ -0,0 +1,27 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +console.log("started."); +const ClusterioMasterProxy_1 = require("./ClusterioMasterProxy"); +const ClusterManager_1 = require("./ClusterManager"); +const argv = require('minimist')(process.argv.slice(2)); +let configPath = argv['configPath']; +if (configPath === undefined || configPath === null || configPath === "") { + throw new Error("configPath is null."); +} +; +let config = require(configPath); +let proxy = new ClusterioMasterProxy_1.ClusterioMasterProxy(config.masterIP, config.masterPort, config.masterAuthToken); +let manager = new ClusterManager_1.ClusterManager(); +manager.GenerateBasicGridOfNodes(2, 2, 'GridLockTest_'); +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} +// async function test() { +// await asyncForEach([1,2,3], async (v, i, a) => { +// console.log("test" + i); +// await sleep(5000); +// }); +// } +// test(); +proxy.CreateNodeInstancesOnLocalServer(manager.Grid.GetNodes()); +//# sourceMappingURL=ModelController.js.map \ No newline at end of file diff --git a/lib/Model/ModelDemo.js b/lib/Model/ModelDemo.js new file mode 100644 index 00000000..99878096 --- /dev/null +++ b/lib/Model/ModelDemo.js @@ -0,0 +1,24 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +console.log("started."); +const ClusterioMasterProxy_1 = require("./ClusterioMasterProxy"); +const ClusterManager_1 = require("./ClusterManager"); +const argv = require('minimist')(process.argv.slice(2)); +let configPath = argv['configPath']; +if (configPath === undefined || configPath === null || configPath === "") { + throw new Error("configPath is null."); +} +; +let config = require(configPath); +let proxy = new ClusterioMasterProxy_1.ClusterioMasterProxy(config.masterIP, config.masterPort, config.masterAuthToken); +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} +// main +(async () => { + let manager = new ClusterManager_1.ClusterManager(); + manager.GenerateBasicGridOfNodes(2, 2, 'GridLockTest'); + await proxy.CreateNodeInstancesOnLocalServer(manager.Grid.GetNodes()); + return; +})(); +//# sourceMappingURL=ModelDemo.js.map \ No newline at end of file diff --git a/lib/Model/ModelRunner.js b/lib/Model/ModelRunner.js new file mode 100644 index 00000000..c5926af3 --- /dev/null +++ b/lib/Model/ModelRunner.js @@ -0,0 +1,61 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +console.log("Started."); +const ClusterioMasterProxy_1 = require("./ClusterioMasterProxy"); +const ClusterManager_1 = require("./ClusterManager"); +const argv = require('minimist')(process.argv.slice(2)); +// arg: configPath +// description: should point to the clusterio config.json file. +let configPath = argv['configPath']; +if (configPath === undefined || configPath === null || configPath === "") { + configPath = "../../config.json"; +} +; +let config = require(configPath); +// arg: mode +// description: should be GenerateBasicGridOfNodes for now. Can extend in future. +let mode = argv['mode']; +if (mode === undefined || mode === null || mode === "") { + throw new Error("mode is null. mode must be 'GenerateBasicGridOfNodes' (for now)."); +} +; +// arg: serviceHostType +// description: what program to use to host the client.js instances. +let serviceHostType = argv['serviceHostType']; +if (serviceHostType === undefined || serviceHostType === null || serviceHostType === "") { + throw new Error("serviceHostType is null. serviceHostType must be either 'pm2' or 'screen'."); +} +; +if (serviceHostType != "pm2" && serviceHostType != "screen") { + throw new Error("serviceHostType must be either 'pm2' or 'screen'."); +} +// arg: width +// description: width of node grid +let width = argv['width']; +if (width === undefined || width === null || width === "") { + throw new Error("width is null. width must be an int > 0."); +} +; +// arg: height +// description: height of node grid +let height = argv['height']; +if (height === undefined || height === null || height === "") { + throw new Error("height is null. height must be an int > 0."); +} +; +// arg: namePrefix +// description: namePrefix of node grid +let namePrefix = argv['namePrefix']; +if (namePrefix === undefined || namePrefix === null || namePrefix === "") { + throw new Error("namePrefix is null."); +} +; +let proxy = new ClusterioMasterProxy_1.ClusterioMasterProxy(config.masterIP, config.masterPort, config.masterAuthToken, serviceHostType); +// main +(async () => { + let manager = new ClusterManager_1.ClusterManager(); + manager.GenerateBasicGridOfNodes(width, height, namePrefix); + await proxy.CreateNodeInstancesOnLocalServer(manager.Grid.GetNodes()); + return; +})(); +//# sourceMappingURL=ModelRunner.js.map \ No newline at end of file diff --git a/lib/Model/NodeInstance.js b/lib/Model/NodeInstance.js new file mode 100644 index 00000000..d8442c5d --- /dev/null +++ b/lib/Model/NodeInstance.js @@ -0,0 +1,29 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const Point_1 = require("./Point"); +const typescript_collections_1 = require("typescript-collections"); +/** + * An instance of a Factorio server. Multiple nodes can run on the same server. + */ +class NodeInstance { + constructor(name, topLeftCoordinate, width, height) { + this.TeleportZones = new typescript_collections_1.Dictionary(); + this.Name = name; + this.Width = width; + this.Height = height; + this.TopLeftCoordinate = topLeftCoordinate; + } + get BottomRightCoordinate() { + return new Point_1.Point(this.TopLeftCoordinate.X + this.Width, this.TopLeftCoordinate.Y + this.Height); + } + // public AddTeleportZone(name: string, topLeftCoordinate: Point, bottomRightCoordinate: Point) { + // this.TeleportZones.push(new TeleportZone(this, topLeftCoordinate, bottomRightCoordinate, name)); + // } + AddTeleportZonePoint() { + } + get Key() { + return this.Name; + } +} +exports.NodeInstance = NodeInstance; +//# sourceMappingURL=NodeInstance.js.map \ No newline at end of file diff --git a/lib/Model/Point.js b/lib/Model/Point.js new file mode 100644 index 00000000..b265d5d2 --- /dev/null +++ b/lib/Model/Point.js @@ -0,0 +1,57 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +class Point { + /** + * Constructs a Point. Will not accept NaNs. + * @param x A number. Cannot be NaN. + * @param y A number. Cannot be NaN. + */ + constructor(x, y) { + this.toString = () => { + return "(" + this.X + "," + this.Y + ")"; + }; + if (Number.isNaN(x)) { + throw new Error("x is NaN. Will not construct Point."); + } + if (Number.isNaN(y)) { + throw new Error("y is NaN. Will not construct Point."); + } + this.X = x; + this.Y = y; + } + /** + * Constructs a Point from a string representation in the format (x,y). Will not accept NaNs. + * @param vectorAsString A string in the format (x,y). Will not accept NaNs. + */ + static FromString(vectorAsString) { + this.CheckChar('(', vectorAsString[0], vectorAsString); + this.CheckChar(')', vectorAsString[vectorAsString.length - 1], vectorAsString); + if (vectorAsString.indexOf(',') === -1) { + throw new Error("vectorAsString is an invalid format. Cannot find ','. fullString: " + vectorAsString); + } + let splitStr = vectorAsString.substr(1, vectorAsString.length - 2).split(','); + if (splitStr.length != 2) { + throw new Error("vectorAsString is an invalid format. Expecting string in format (x,y). fullString: " + vectorAsString); + } + let x = parseInt(splitStr[0]); + let y = parseInt(splitStr[1]); + let v = new Point(x, y); + return v; + } + static CheckChar(expectedChar, actualChar, fullString) { + if (actualChar != expectedChar) { + throw new Error("vectorAsString is an invalid format. Expecting: '" + expectedChar + "'. Actual: '" + actualChar + "'. fullString: " + fullString); + } + } + equals(obj) { + return this.X === obj.X && this.Y === obj.Y; + } + Add(p) { + return new Point(this.X + p.X, this.Y + p.Y); + } + Subtract(p) { + return new Point(this.X - p.X, this.Y - p.Y); + } +} +exports.Point = Point; +//# sourceMappingURL=Point.js.map \ No newline at end of file diff --git a/lib/Model/Server.js b/lib/Model/Server.js new file mode 100644 index 00000000..292b9b10 --- /dev/null +++ b/lib/Model/Server.js @@ -0,0 +1,16 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * A server. Can contain multiple nodes (Factorio server instances). + */ +class Server { + constructor(name) { + this.Nodes = new Array(); + this.Name = name; + } + AddNodeInstance(node) { + this.Nodes.push(node); + } +} +exports.Server = Server; +//# sourceMappingURL=Server.js.map \ No newline at end of file diff --git a/lib/Model/ServiceHostType.js b/lib/Model/ServiceHostType.js new file mode 100644 index 00000000..f314a000 --- /dev/null +++ b/lib/Model/ServiceHostType.js @@ -0,0 +1,8 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var ServiceHostType; +(function (ServiceHostType) { + ServiceHostType[ServiceHostType["pm2"] = 0] = "pm2"; + ServiceHostType[ServiceHostType["screen"] = 1] = "screen"; +})(ServiceHostType = exports.ServiceHostType || (exports.ServiceHostType = {})); +//# sourceMappingURL=ServiceHostType.js.map \ No newline at end of file diff --git a/lib/Model/TeleportZone.js b/lib/Model/TeleportZone.js new file mode 100644 index 00000000..d94582a3 --- /dev/null +++ b/lib/Model/TeleportZone.js @@ -0,0 +1,64 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const Point_1 = require("./Point"); +const TeleportZoneDirection_1 = require("./TeleportZoneDirection"); +const Config = require("./Config"); +class TeleportZone { + constructor(parentNode, direction, topLeftCoordinate, bottomRightCoordinate, index, targetNode) { + this.TopLeftCoordinate = topLeftCoordinate; + this.BottomRightCoordinate = bottomRightCoordinate; + this.ParentNode = parentNode; + this.Direction = direction; + this.Index = index; + this.TargetNode = targetNode; + } + get Name() { + return TeleportZoneDirection_1.TeleportZoneDirection[this.Direction] + " to " + this.TargetNode.Name; + } + // public AddRestriction(restrictingTeleportZone: TeleportZone) { + // this.Restrictions.push(new TeleportZoneRestriction(this, restrictingTeleportZone)); + // } + /** + * Converts this teleport zone into Factorio map co-ordinates + * @returns [name, topleft.x, topleft.y, width, height, zoneIndex] + */ + GenerateTeleportZoneInFactorioCoordinates() { + // get where this zone is relative to the node itself + let topLeftRelativeCoordinate = this.TopLeftCoordinate.Subtract(this.ParentNode.TopLeftCoordinate); + // a grid coordinate point is equivalent to a 1x1 node, so bottomright = topleft. + // add (1,1) to translate from area representation to coordinates representing actual points. + let bottomRightRelativeCoordinate = this.BottomRightCoordinate + .Subtract(this.ParentNode.TopLeftCoordinate) + .Add(new Point_1.Point(1, 1)); + // Factorio map is e.g. (-500,-500) to (500, 500) for a 1000x1000 node. + // We therefore need to offset our co-ordinates so that (0,0) is the center and not (width/2, height/2) + let m = Config.GridToFactorioCoordinateSystemMultiplier; + let factorioTeleportZoneTopLeft = new Point_1.Point(topLeftRelativeCoordinate.X * m - m / 2, topLeftRelativeCoordinate.Y * m - m / 2); + let factorioTeleportZoneBottomRight = new Point_1.Point(bottomRightRelativeCoordinate.X * m - m / 2, bottomRightRelativeCoordinate.Y * m - m / 2); + let zoneWidth = factorioTeleportZoneBottomRight.X - factorioTeleportZoneTopLeft.X; + let zoneHeight = factorioTeleportZoneBottomRight.Y - factorioTeleportZoneTopLeft.Y; + let tl = factorioTeleportZoneTopLeft; + let br = factorioTeleportZoneBottomRight; + let zw = Config.TeleportZoneWidth; + switch (this.Direction) { + case TeleportZoneDirection_1.TeleportZoneDirection.North: + return [this.Name, tl.X, tl.Y, zoneWidth, zw, this.Index + 1]; + case TeleportZoneDirection_1.TeleportZoneDirection.West: + return [this.Name, tl.X, tl.Y, zw, zoneHeight, this.Index + 1]; + case TeleportZoneDirection_1.TeleportZoneDirection.South: + return [this.Name, tl.X, br.Y - zw, zoneWidth, zw, this.Index + 1]; + case TeleportZoneDirection_1.TeleportZoneDirection.East: + return [this.Name, br.X - zw, tl.Y, zw, zoneHeight, this.Index + 1]; + } + } + /** + * Generates a zone restriction from this teleport zone to target zone + * @returns [zoneIndex, targetNodeName, targetZoneName] + */ + GenerateZoneRestriction() { + let targetZone = this.TargetNode.TeleportZones.getValue(this.ParentNode.Key); + return [this.Index + 1, this.TargetNode.Name, targetZone.Name]; + } +} +exports.TeleportZone = TeleportZone; +//# sourceMappingURL=TeleportZone.js.map \ No newline at end of file diff --git a/lib/Model/TeleportZoneDirection.js b/lib/Model/TeleportZoneDirection.js new file mode 100644 index 00000000..5aced53a --- /dev/null +++ b/lib/Model/TeleportZoneDirection.js @@ -0,0 +1,10 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var TeleportZoneDirection; +(function (TeleportZoneDirection) { + TeleportZoneDirection[TeleportZoneDirection["North"] = 0] = "North"; + TeleportZoneDirection[TeleportZoneDirection["East"] = 1] = "East"; + TeleportZoneDirection[TeleportZoneDirection["South"] = 2] = "South"; + TeleportZoneDirection[TeleportZoneDirection["West"] = 3] = "West"; +})(TeleportZoneDirection = exports.TeleportZoneDirection || (exports.TeleportZoneDirection = {})); +//# sourceMappingURL=TeleportZoneDirection.js.map \ No newline at end of file diff --git a/lib/Model/TeleportZoneRestriction.js b/lib/Model/TeleportZoneRestriction.js new file mode 100644 index 00000000..71a78da4 --- /dev/null +++ b/lib/Model/TeleportZoneRestriction.js @@ -0,0 +1,10 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +class TeleportZoneRestriction { + constructor(parentTeleportZone, restrictingTeleportZone) { + this.ParentTeleportZone = parentTeleportZone; + this.RestrictingTeleportZone = restrictingTeleportZone; + } +} +exports.TeleportZoneRestriction = TeleportZoneRestriction; +//# sourceMappingURL=TeleportZoneRestriction.js.map \ No newline at end of file diff --git a/lib/Model/index.js b/lib/Model/index.js new file mode 100644 index 00000000..fbc2d538 --- /dev/null +++ b/lib/Model/index.js @@ -0,0 +1,13 @@ +"use strict"; +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +Object.defineProperty(exports, "__esModule", { value: true }); +__export(require("./ClusterManager")); +__export(require("./NodeInstance")); +__export(require("./Server")); +__export(require("./TeleportZone")); +__export(require("./TeleportZoneRestriction")); +__export(require("./Point")); +__export(require("./Grid")); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/package.json b/package.json index ecda9e2a..51ee37db 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "socket.io-client": "^2.0.4", "socket.io-prometheus": "^0.2.1", "sync-request": "^4.0.1", + "typescript-collections": "^1.3.2", "underscore": "^1.8.3", "snyk": "^1.134.2" },