├── .gitignore ├── index.js ├── tools ├── lib │ ├── fixRail.js │ ├── trainStation.js │ ├── landfill.js │ ├── defenses.js │ └── addBeacons.js ├── requesterChest.js ├── blueprintTool.js ├── oilOutpost.js └── outpost.js ├── package.json ├── cliRequesterChest ├── README.md └── cli /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | outpost: require('./tools/outpost'), 4 | blueprintTool: require('./tools/blueprintTool'), 5 | oilOutpost: require('./tools/oilOutpost'), 6 | requesterChest: require('./tools/requesterChest') 7 | }; -------------------------------------------------------------------------------- /tools/lib/fixRail.js: -------------------------------------------------------------------------------- 1 | module.exports = function(bp) { 2 | const firstRail = bp.entities.filter(e => e.name == 'straight_rail')[0]; 3 | 4 | if (firstRail) { 5 | let { x, y } = firstRail.position; 6 | x -= 0.5; 7 | y -= 0.5; 8 | if (x < 0) x += Math.ceil(-x / 2) * 2; 9 | if (y < 0) y += Math.ceil(-y / 2) * 2; 10 | x %= 2; 11 | y %= 2; 12 | bp.fixCenter({ x, y }); // Move center some amount between 0 and 2 so rails snap correctly 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "factorio-generators", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "a-star": "git+https://github.com/andrewrk/node-astar.git", 13 | "factorio-blueprint": "^2.6.5", 14 | "meow": "^3.7.0", 15 | "victor": "^1.1.0", 16 | "yamljs": "^0.2.10" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /cliRequesterChest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | const GENERATOR = require('./index'); 4 | const MEOW = require('meow'); 5 | 6 | const cli = MEOW(` 7 | Usage 8 | $ cliSupplyChest blueprintstring 9 | Examples 10 | $ cliSupplyChest 0eNqNj8EKwj...` 11 | ); 12 | 13 | let blueprint = ''; 14 | //if no input (arguments without -- prefixed) show help and exit the program 15 | if (cli.input.length === 0) { 16 | cli.showHelp(); 17 | return; 18 | } else { 19 | blueprint = cli.input[0] 20 | } 21 | 22 | try { 23 | global.console = new console.Console({ stdout: process.stderr, stderr: process.stderr }); 24 | process.stdout.write(GENERATOR.requesterChest(blueprint)); 25 | } catch (e) { 26 | console.log(e.message); 27 | } -------------------------------------------------------------------------------- /tools/requesterChest.js: -------------------------------------------------------------------------------- 1 | const Blueprint = require('factorio-blueprint'); 2 | const REQUEST_CHEST_SLOT_COUNT = 12; 3 | const RAILS_PER_CURVED_RAIL = 4; 4 | const RAILED_PER_STRAIGHT_RAIL = 1; 5 | 6 | module.exports = function(string) { 7 | if (Blueprint.isBook(string)) { 8 | throw new Error('Blueprint string must be a blueprint, not a book.'); 9 | } 10 | const importedBP = new Blueprint(string); 11 | const requestBP = new Blueprint(); 12 | 13 | requestBP.name = 'Requester for ' + importedBP.name; 14 | requestBP.icons[0] = 'logistic_chest_requester'; 15 | 16 | let entityCounts = new Map(); 17 | for (const entity of importedBP.entities) { 18 | const ent = entity.name; 19 | if (!entityCounts.has(ent)) 20 | entityCounts.set(ent, 1); 21 | else 22 | entityCounts.set(ent, entityCounts.get(ent)+1); 23 | } 24 | 25 | for (const tile of importedBP.tiles) { 26 | const tl = tile.name; 27 | if (!entityCounts.has(tl)) 28 | entityCounts.set(tl, 1); 29 | else 30 | entityCounts.set(tl, entityCounts.get(tl)+1); 31 | } 32 | 33 | // replace rails 34 | let railCount = 0; 35 | if (entityCounts.has('curved_rail')) { 36 | railCount += entityCounts.get('curved_rail') * RAILS_PER_CURVED_RAIL; 37 | entityCounts.delete('curved_rail'); 38 | } 39 | if (entityCounts.has('straight_rail')) { 40 | railCount += entityCounts.get('straight_rail') * RAILED_PER_STRAIGHT_RAIL; 41 | entityCounts.delete('straight_rail'); 42 | } 43 | if (railCount > 0) { 44 | entityCounts.set('rail', railCount); 45 | } 46 | 47 | const chestCount = Math.ceil(entityCounts.size / REQUEST_CHEST_SLOT_COUNT); 48 | const squareSize = Math.ceil(Math.sqrt(chestCount)); 49 | 50 | let requestItemId = 0; 51 | let entities = entityCounts.entries(); 52 | let entry = entities.next(); 53 | for (let i = 0; i < chestCount; i++) { 54 | const x = i % squareSize; 55 | const y = Math.floor(i/squareSize); 56 | let requestEntity = requestBP.createEntity('logistic_chest_requester', {x, y}); 57 | for (let j = 0; j < REQUEST_CHEST_SLOT_COUNT && !entry.done; j++) { 58 | requestEntity.setRequestFilter(j, entry.value[0], entry.value[1]); 59 | entry = entities.next(); 60 | } 61 | } 62 | 63 | requestBP.center(); 64 | return requestBP.encode(); 65 | }; 66 | -------------------------------------------------------------------------------- /tools/lib/trainStation.js: -------------------------------------------------------------------------------- 1 | const Blueprint = require('factorio-blueprint'); 2 | 3 | module.exports = function(bp, { x, y }, highY, { lowerY, LOCOMOTIVES, TRACK_CONCRETE, SINGLE_HEADED_TRAIN, WALL_SPACE, WALL_THICKNESS, INCLUDE_RADAR, INCLUDE_LIGHTS }) { 4 | 5 | const yPosition = y - LOCOMOTIVES * 7; 6 | const xPosition = x; 7 | 8 | const trainStopLocation = { x: xPosition + 2, y: yPosition }; 9 | bp.createEntity('train_stop', trainStopLocation, Blueprint.UP); 10 | if(INCLUDE_LIGHTS && !SINGLE_HEADED_TRAIN) { 11 | bp.createEntity('small_lamp', { x: xPosition, y: yPosition - 1 }); 12 | bp.createEntity('small_lamp', { x: xPosition + 1, y: yPosition - 1 }); 13 | bp.createEntity('medium_electric_pole', { x: xPosition + 1, y: yPosition - 2 }); 14 | } 15 | const railTarget = highY - trainStopLocation.y + WALL_SPACE + WALL_THICKNESS + 3; 16 | let i = 0; 17 | for (; i <= railTarget; i += 2) { 18 | bp.createEntity('straight_rail', { x: xPosition, y: yPosition + i }, Blueprint.DOWN); 19 | // Concrete 20 | if (TRACK_CONCRETE) { 21 | const UPPER_Y = highY + WALL_SPACE + WALL_THICKNESS + 2; 22 | for (let xOffset = -1; xOffset <= 2; xOffset++) { 23 | for (let yOffset = -1; yOffset <= 2; yOffset++) { 24 | if (yPosition + i + yOffset > UPPER_Y) continue; 25 | bp.createTile(TRACK_CONCRETE, { x: xPosition + xOffset, y: yPosition + i + yOffset }); 26 | } 27 | } 28 | } 29 | } 30 | bp.createEntity('straight_rail', { x: xPosition, y: yPosition + i }, Blueprint.DOWN); 31 | bp.createEntity('rail_signal', { x: xPosition + 2, y: yPosition + i }, Blueprint.DOWN); 32 | if (TRACK_CONCRETE) { 33 | const UPPER_Y = highY + WALL_SPACE + WALL_THICKNESS + 2; 34 | for (let xOffset = -1; xOffset <= 2; xOffset++) { 35 | for (let yOffset = -1; yOffset <= 2; yOffset++) { 36 | if (yPosition + i + yOffset > UPPER_Y) continue; 37 | bp.createTile(TRACK_CONCRETE, { x: xPosition + xOffset, y: yPosition + i + yOffset }); 38 | } 39 | } 40 | } 41 | //poke the gate out 42 | for (i = i + 2; i <= railTarget + 1 + WALL_SPACE + WALL_THICKNESS; i += 2) 43 | bp.createEntity('straight_rail', { x: xPosition, y: yPosition + i }, Blueprint.DOWN); 44 | if (SINGLE_HEADED_TRAIN) { 45 | const LOWER_Y = typeof lowerY != 'undefined' ? lowerY : Math.min(INCLUDE_RADAR ? -3 : 0, trainStopLocation.y) - 1; 46 | for (let i = 2; i < (yPosition - LOWER_Y) + WALL_SPACE + 1 + WALL_THICKNESS; i += 2) { 47 | bp.createEntity('straight_rail', { x: xPosition, y: yPosition - i }, Blueprint.DOWN); 48 | // Concrete 49 | if (TRACK_CONCRETE) { 50 | for (let xOffset = -1; xOffset <= 2; xOffset++) { 51 | for (let yOffset = -1; yOffset <= 2; yOffset++) { 52 | if (yPosition - i + yOffset < LOWER_Y - WALL_SPACE) continue; 53 | bp.createTile(TRACK_CONCRETE, { x: xPosition + xOffset, y: yPosition - i + yOffset }); 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | return trainStopLocation; 61 | } 62 | -------------------------------------------------------------------------------- /tools/lib/landfill.js: -------------------------------------------------------------------------------- 1 | // `curved_rail`s require special landfill shapes. the below offsets were generated in `/editor` mode using Factorio 1.1.59 2 | const curvedRailLandfillRequirementsByDirection = [ 3 | [[-3, -3], [-3, -2], [-2, -4], [-2, -3], [-2, -2], [-2, -1], [-1, -3], [-1, -2], [-1, -1], [-1, 0], [0, -2], [0, -1], [0, 0], [0, 1], [0, 2], [0, 3], [1, 0], [1, 1], [1, 2], [1, 3]], 4 | [[-2, 0], [-2, 1], [-2, 2], [-2, 3], [-1, -2], [-1, -1], [-1, 0], [-1, 1], [-1, 2], [-1, 3], [0, -3], [0, -2], [0, -1], [0, 0], [1, -4], [1, -3], [1, -2], [1, -1], [2, -3], [2, -2]], 5 | [[-4, 0], [-4, 1], [-3, 0], [-3, 1], [-2, 0], [-2, 1], [-1, -1], [-1, 0], [-1, 1], [0, -2], [0, -1], [0, 0], [1, -3], [1, -2], [1, -1], [1, 0], [2, -3], [2, -2], [2, -1], [3, -2]], 6 | [[-4, -2], [-4, -1], [-3, -2], [-3, -1], [-2, -2], [-2, -1], [-1, -2], [-1, -1], [-1, 0], [0, -1], [0, 0], [0, 1], [1, -1], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2], [3, 1]], 7 | [[-2, -4], [-2, -3], [-2, -2], [-2, -1], [-1, -4], [-1, -3], [-1, -2], [-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [1, 3], [2, 1], [2, 2]], 8 | [[-3, 1], [-3, 2], [-2, 0], [-2, 1], [-2, 2], [-2, 3], [-1, -1], [-1, 0], [-1, 1], [-1, 2], [0, -4], [0, -3], [0, -2], [0, -1], [0, 0], [0, 1], [1, -4], [1, -3], [1, -2], [1, -1]], 9 | [[-4, 1], [-3, 0], [-3, 1], [-3, 2], [-2, -1], [-2, 0], [-2, 1], [-2, 2], [-1, -1], [-1, 0], [-1, 1], [0, -2], [0, -1], [0, 0], [1, -2], [1, -1], [2, -2], [2, -1], [3, -2], [3, -1]], 10 | [[-4, -2], [-3, -3], [-3, -2], [-3, -1], [-2, -3], [-2, -2], [-2, -1], [-2, 0], [-1, -2], [-1, -1], [-1, 0], [0, -1], [0, 0], [0, 1], [1, 0], [1, 1], [2, 0], [2, 1], [3, 0], [3, 1]], 11 | ]; 12 | 13 | // diagonal `straight_rail`s require less landfill than their `size`-based bounding box. the below offsets were generated in `/editor` mode using Factorio 1.1.59 14 | const straightRailLandfillRequirementsByDirection = [ 15 | undefined, // not diagonal, so default to standard rectangle 16 | [[0, 0], [1, -1], [1, 0], [1, 1], [2, 0]], 17 | undefined, // not diagonal, so default to standard rectangle 18 | [[0, 1], [1, 0], [1, 1], [1, 2], [2, 1]], 19 | undefined, // not diagonal, so default to standard rectangle 20 | [[-1, 1], [0, 0], [0, 1], [0, 2], [1, 1]], 21 | undefined, // not diagonal, so default to standard rectangle 22 | [[-1, 0], [0, -1], [0, 0], [0, 1], [1, 0]], 23 | ]; 24 | 25 | function getSpecialLandfillOffsets(entity) { 26 | if (entity.name === 'curved_rail') { 27 | return curvedRailLandfillRequirementsByDirection[entity.direction ?? 0]; 28 | } 29 | else if (entity.name === 'straight_rail') { 30 | return straightRailLandfillRequirementsByDirection[entity.direction ?? 0]; 31 | } 32 | return undefined; 33 | } 34 | 35 | function generateLandfill(e, bp) { 36 | // offshore pumps are built on water, so don't create landfill for them 37 | // stone walls are weaker than just keeping the water 38 | if (e.name === 'offshore_pump' || e.name === 'stone_wall') { 39 | return; 40 | } 41 | 42 | // look up if there is a special offset list for this entity 43 | let specialOffsets = getSpecialLandfillOffsets(e) 44 | if (specialOffsets !== undefined) { 45 | for (const offset of specialOffsets) { 46 | bp.createTile('landfill', { 47 | x: e.position.x + offset[0], 48 | y: e.position.y + offset[1], 49 | }); 50 | } 51 | } 52 | 53 | // otherwise, add a rectangle of landfill defined by the entity's 'size' property 54 | else { 55 | for (let ox = 0; ox < e.size.x; ox++) { 56 | for (let oy = 0; oy < e.size.y; oy++) { 57 | bp.createTile('landfill', { 58 | x: e.position.x + ox, 59 | y: e.position.y + oy, 60 | }); 61 | } 62 | } 63 | } 64 | } 65 | 66 | module.exports = generateLandfill; 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Factorio Generators 3 | 4 | ## Usage 5 | 6 | `require('./index')` returns an object with the following keys as functions: 7 | 8 | ### outpost(blueprintString, opt) 9 | 10 | Generates an outpost given a blueprintString with 2 walls at the corner of the ore patch you want to cover. Options are: 11 | 12 | - minerSpace: 0-2, space between miners (default 1) 13 | - minerSize: 3-5, width/length of a miner (default 3) 14 | - miningDrillName: String, custom mining drill name (default electric_mining_drill) 15 | - undergroundBelts: Use underground belts in front of miners instead of regular belts 16 | - compact: No horizontal spacing between miners, place electric poles in between miners. Requires `undergroundBelts` to be `true`. 17 | - beltName: Name of type of belt in the format `type` or `type_transport_belt` (default `''` which is yellow belt) 18 | - useStackInserters: Boolean, use stack inserters between the buffer chests and cargo wagon instead of fast inserters (default true) 19 | - botBased: Boolean, use passive provider and requester chests (default false) 20 | - requestItem: Item for requester chests to request if botBased (default iron_ore) 21 | - requestAmount: The amount of items each chest requests when bot based (default 4800) 22 | - roboports: Boolean, include roboports by tracks (default false) 23 | - balancer: Blueprint string for an NxN balancer if the script does not have any available (N being the # of cargo wagons) (no default) 24 | 25 | 26 | #### Shared Options 27 | 28 | - trainSide: 0-3, side the train station should be on (default 1 right) 29 | - trainDirection: 0-3, side train station should enter from (Must be perpendicular to trainSide) (default 2 bottom) 30 | - module: Module name to fill up miners with, empty for none 31 | - includeRadar: Boolean, whether or not to include a radar (default true) 32 | - turretSpacing: 2-9, spacing between turrets on wall (default 8) 33 | - turrets: Boolean, whether turrets are enabled or not (default true) 34 | - laserTurrets: Boolean, use laser turrets instead of gun turrets (default true) 35 | - includeTrainStation: Boolean, whether or not to include a train station (default true) 36 | - locomotiveCount: 1+, number of locomotives before cargo wagons (default 2) 37 | - cargoWagonCount: 1+, number of cargo wagons on train (default 4) 38 | - exitRoute: true/false, whether or not there is a route past the train station for single-headed trains (default false) 39 | - walls: Whether or not to include walls (default true) 40 | - wallSpace: Space between wall and rest of the outpost (default 5) 41 | - wallThickness: Number of walls thick the outpost defenses are (default 1) 42 | - conrete: Name of concrete type (in vanilla either `concrete` or `hazard_conrete`) (default none) 43 | - borderConcrete: Type of concrete put on the wall and just inside the walls (default none) 44 | - trackConrete: Type of concrete put on the track and just surrounding the track (default none) 45 | - includeLights: Boolean, Let there be light! (Default: `false`) 46 | - name: Name of blueprint (%drills% is replaced with the # of drills in ore outposts and %pumpjacks% the number of pumpjacks in oil outposts) 47 | 48 | ### oilOutpost(blueprintString, opt) 49 | 50 | Shares options with the "shared" section from above 51 | 52 | - pumpjackName: String, Name of pumpjack entity (default: `'pumpjack'`) 53 | - tanks: Int, Number of tanks to use to store oil per cargo wagon (default: `2`) 54 | 55 | ### blueprintTool(blueprintString, opt) 56 | 57 | Modifies a blueprint in different ways and returns a new blueprint string. 58 | 59 | - flipX: Boolean, flip along the X axis (default false) 60 | - flipY: Boolean, flip along the Y axis (default false) 61 | - landfillEntities: Boolean, place landfill under entities (default false) 62 | - The following are an array of objects with a key `to` for the entity name to convert to and `from` for an exact name or `includes` for any entity including the string 63 | - entityReplace: Array of objects to convert entity types in the form { from: 'name', to: 'name' } (default empty array) 64 | - recipeReplace: Array of objects to convert recipes in assembly machines in the form { from: 'name', to: 'name' } (default empty array) 65 | - modifiedOnly: Resulting blueprint only contains entities which have been modified by a replace. 66 | -------------------------------------------------------------------------------- /tools/blueprintTool.js: -------------------------------------------------------------------------------- 1 | const Blueprint = require('factorio-blueprint'); 2 | const generateLandfill = require('./lib/landfill'); 3 | module.exports = function(string, opt) { 4 | opt = opt || {}; 5 | 6 | const FLIP_X = opt.flipX || false; 7 | const FLIP_Y = opt.flipY || false; 8 | const LANDFILL_ENTITIES = opt.landfillEntities || false; 9 | const ENTITY_REPLACE = opt.entityReplace || []; 10 | const RECIPE_REPLACE = opt.recipeReplace || []; 11 | const MODULE_REPLACE = opt.moduleReplace || []; 12 | const MODIFIED_ONLY = opt.modifiedOnly || false; 13 | 14 | let blueprints = [new Blueprint(string, { checkWithEntityData: false })]; 15 | let newBP = []; 16 | 17 | // is book? 18 | const isBook = Blueprint.isBook(string); 19 | if (isBook) { 20 | blueprints = Blueprint.getBook(string, { checkWithEntityData: false }); 21 | } 22 | for (let blueprintIndex in blueprints) { 23 | let old = blueprints[blueprintIndex]; 24 | if (!old) { 25 | newBP.push(null); 26 | continue; 27 | } 28 | 29 | const bp = new Blueprint(null, { checkWithEntityData: false }); 30 | bp.name = old.name; 31 | bp.version = old.version; 32 | bp.icons = old.icons; 33 | newBP.push(bp); 34 | 35 | const newEntityData = {}; 36 | old.icons.forEach(icon => { 37 | if (!Blueprint.getEntityData()[icon]) 38 | newEntityData[icon] = { type: 'item' }; 39 | }); 40 | 41 | [ENTITY_REPLACE, RECIPE_REPLACE, MODULE_REPLACE].forEach(replaceType => { 42 | replaceType.forEach(replace => { 43 | ['to', 'from', 'includes'].forEach(type => { 44 | if ( 45 | replace[type] && 46 | !Blueprint.getEntityData()[ 47 | bp.jsName(replace[type].replace('includes:', '')) 48 | ] 49 | ) 50 | newEntityData[bp.jsName(replace[type].replace('includes:', ''))] = { 51 | type: 'item', 52 | }; 53 | }); 54 | }); 55 | }); 56 | 57 | Blueprint.setEntityData(newEntityData); 58 | 59 | old.entities.forEach(ent => { 60 | ENTITY_REPLACE.forEach(replace => { 61 | if ( 62 | ent.name == bp.jsName(replace.from) || 63 | ent.name.includes(bp.jsName(replace.includes)) 64 | ) { 65 | ent.name = bp.jsName(replace.to); 66 | ent.changed = true; 67 | } 68 | }); 69 | }); 70 | 71 | old.tiles.forEach(tile => { 72 | ENTITY_REPLACE.forEach(replace => { 73 | if ( 74 | tile.name == bp.jsName(replace.from) || 75 | tile.name.includes(bp.jsName(replace.includes)) 76 | ) { 77 | tile.name = bp.jsName(replace.to); 78 | tile.changed = true; 79 | } 80 | }); 81 | }); 82 | 83 | old.entities.forEach(ent => { 84 | RECIPE_REPLACE.forEach(replace => { 85 | if ( 86 | ent.recipe == bp.jsName(replace.from) || 87 | ent.recipe.includes(bp.jsName(replace.includes)) 88 | ) { 89 | ent.recipe = bp.jsName(replace.to); 90 | ent.changed = true; 91 | } 92 | }); 93 | }); 94 | 95 | old.entities.forEach(ent => { 96 | if (!ent.modules) return; 97 | MODULE_REPLACE.forEach(replaceModule => { 98 | Object.keys(ent.modules).forEach(mod => { 99 | if ( 100 | mod == bp.jsName(replaceModule.from) || 101 | mod.includes(bp.jsName(replaceModule.includes)) 102 | ) { 103 | const to = bp.jsName(replaceModule.to); 104 | if (ent.modules[to]) ent.modules[to] += ent.modules[mod]; 105 | else ent.modules[to] = ent.modules[mod]; 106 | delete ent.modules[mod]; 107 | 108 | ent.changed = true; 109 | } 110 | }); 111 | }); 112 | }); 113 | 114 | old.entities.forEach(ent => { 115 | if (!Blueprint.getEntityData()[ent.name]) { 116 | const obj = {}; 117 | obj[ent.name] = { type: 'item' }; 118 | Blueprint.setEntityData(obj); 119 | } 120 | if (ent.changed || !MODIFIED_ONLY) 121 | bp.createEntityWithData(ent.getData(), true, true, true); // Allow overlap in case modded items with unknown size 122 | }); 123 | 124 | bp.entities.forEach(ent => { 125 | ent.place(bp.entityPositionGrid, bp.entities); 126 | }); 127 | 128 | old.tiles.forEach(tile => { 129 | if (tile.changed || !MODIFIED_ONLY) bp.createTileWithData(tile.getData()); 130 | }); 131 | 132 | // DIR = 'x' | 'y' 133 | const flip = (DIR, MAP, CURVED_MAP) => { 134 | bp.entities.forEach(e => { 135 | e.position[DIR] = -e.position[DIR] - e.size[DIR]; 136 | 137 | if (e.name == 'curved_rail' && CURVED_MAP[e.direction] !== undefined) { 138 | e.direction = CURVED_MAP[e.direction]; 139 | } else if (e.name != 'curved_rail' && MAP[e.direction] !== undefined) { 140 | e.direction = MAP[e.direction]; 141 | } 142 | 143 | if (e.name.includes('splitter')) { 144 | const SWITCH_DIR = { 145 | left: 'right', 146 | right: 'left', 147 | }; 148 | e.setInputPriority(SWITCH_DIR[e.inputPriority] || undefined); 149 | e.setOutputPriority(SWITCH_DIR[e.outputPriority] || undefined); 150 | } 151 | }); 152 | bp.tiles.forEach(e => { 153 | e.position[DIR] = -e.position[DIR] - 1; 154 | }); 155 | 156 | bp.fixCenter({ 157 | // In case of tracks 158 | x: DIR === 'x' ? 1 : 0, 159 | y: DIR === 'y' ? 1 : 0, 160 | }); 161 | }; 162 | 163 | if (FLIP_X) { 164 | const MAP = { 165 | 2: 6, 166 | 6: 2, 167 | 168 | 1: 7, 169 | 7: 1, 170 | 171 | 3: 5, 172 | 5: 3, 173 | }; 174 | const CURVED_MAP = { 175 | 5: 4, 176 | 4: 5, 177 | 178 | 1: 0, 179 | 0: 1, 180 | 181 | 7: 2, 182 | 2: 7, 183 | 184 | 3: 6, 185 | 6: 3, 186 | }; 187 | flip('x', MAP, CURVED_MAP); 188 | } 189 | 190 | if (FLIP_Y) { 191 | const MAP = { 192 | 0: 4, 193 | 4: 0, 194 | 195 | 1: 3, 196 | 3: 1, 197 | 198 | 5: 7, 199 | 7: 5, 200 | }; 201 | const CURVED_MAP = { 202 | 1: 4, 203 | 4: 1, 204 | 205 | 5: 0, 206 | 0: 5, 207 | 208 | 3: 2, 209 | 2: 3, 210 | 211 | 7: 6, 212 | 6: 7, 213 | }; 214 | flip('y', MAP, CURVED_MAP); 215 | } 216 | 217 | if (LANDFILL_ENTITIES) { 218 | bp.entities.forEach(e => { 219 | generateLandfill(e, bp); 220 | }); 221 | } 222 | } 223 | if (isBook) { 224 | return Blueprint.toBook(newBP); 225 | } else { 226 | return newBP[0].encode(); 227 | } 228 | }; 229 | -------------------------------------------------------------------------------- /tools/lib/defenses.js: -------------------------------------------------------------------------------- 1 | const Blueprint = require('factorio-blueprint'); 2 | 3 | module.exports = function(bp, { lowerX, upperX, lowerY, upperY }, { TURRETS_ENABLED, TURRET_SPACING, USE_LASER_TURRETS, WALL_SPACE, WALL_THICKNESS, 4 | CONCRETE, BORDER_CONCRETE, INCLUDE_LIGHTS }) { 5 | 6 | function generateTurret(isX, variable, upper, placePowerpole) { 7 | const sign = upper ? 1 : -1; 8 | const yPosition = isX ? 9 | ((upper ? upperY : lowerY - 1) + WALL_SPACE * sign - 3 * sign) : 10 | variable; 11 | const xPosition = isX ? 12 | (variable) : 13 | (upper ? upperX : lowerX - 1) + WALL_SPACE * sign - 3 * sign; 14 | 15 | let dir = isX ? (upper ? Blueprint.DOWN : Blueprint.UP) : (upper ? Blueprint.RIGHT : Blueprint.LEFT); 16 | 17 | try { 18 | bp.createEntity(USE_LASER_TURRETS ? 'laser_turret' : 'gun_turret', { x: xPosition, y: yPosition }, dir); 19 | } catch (e) {} 20 | // Try to generate power poles anyway so that they can connect 21 | if (USE_LASER_TURRETS && placePowerpole) { 22 | const movePowerpoleBehind = (TURRET_SPACING == 2 ? (upper ? -1 : 2) : 0); 23 | try { 24 | const OFFSET_Y = isX ? movePowerpoleBehind : -1; 25 | const OFFSET_X = isX ? -1 : movePowerpoleBehind; 26 | bp.createEntity('medium_electric_pole', { x: xPosition + OFFSET_X, y: yPosition + OFFSET_Y }); 27 | if(INCLUDE_LIGHTS){ 28 | const LX = OFFSET_X + (isX ? 0 : 1); 29 | const LY = OFFSET_Y + (isX ? 1 : 0); 30 | bp.createEntity('small_lamp', { x: xPosition + LX, y: yPosition + LY }); 31 | } 32 | } catch (e) { 33 | const OFFSET_Y = isX ? movePowerpoleBehind : 2; 34 | const OFFSET_X = isX ? 2 : movePowerpoleBehind; 35 | bp.createEntity('medium_electric_pole', { x: xPosition + OFFSET_X, y: yPosition + OFFSET_Y }); 36 | if(INCLUDE_LIGHTS){ 37 | const LX = OFFSET_X + (isX ? 0 : 1); 38 | const LY = OFFSET_Y + (isX ? 1 : 0); 39 | bp.createEntity('small_lamp', { x: xPosition + LX, y: yPosition + LY }); 40 | } 41 | } 42 | } 43 | } 44 | 45 | if (TURRETS_ENABLED) { 46 | for (let x = lowerX - WALL_SPACE + 3; x <= upperX + WALL_SPACE - 3; x++) { 47 | const placePowerpole = TURRET_SPACING <= 4 ? x % (TURRET_SPACING * 2) == 0 : true; 48 | if (x % TURRET_SPACING == 0) { 49 | generateTurret(true, x, false, placePowerpole); 50 | generateTurret(true, x, true, placePowerpole); 51 | } 52 | } 53 | 54 | for (let y = lowerY - WALL_SPACE + 1 + 3; y < upperY + WALL_SPACE - 3; y++) { 55 | const placePowerpole = TURRET_SPACING <= 4 ? y % (TURRET_SPACING * 2) == 0 : true; 56 | if (y % TURRET_SPACING == 0) { 57 | generateTurret(false, y, false, placePowerpole); 58 | generateTurret(false, y, true, placePowerpole); 59 | } 60 | } 61 | } 62 | 63 | if (WALL_THICKNESS > 0) { 64 | for (let i = 0; i < WALL_THICKNESS; i++) { 65 | var placedLowerGateLast = false; 66 | var placedUpperGateLast = false; 67 | for (let x = lowerX - WALL_SPACE - i; x <= upperX + WALL_SPACE + i; x++) { 68 | const ent1 = bp.findEntity({ x: x, y: lowerY - WALL_SPACE - i }); 69 | const ent2 = bp.findEntity({ x: x, y: upperY + WALL_SPACE + i }); 70 | if (!ent1 || ent1.name == 'straight_rail') 71 | { 72 | bp.createEntity(ent1 ? 'gate' : 'stone_wall', { x: x, y: lowerY - WALL_SPACE - i }, Blueprint.RIGHT, true); 73 | if(!ent1){ 74 | if(placedLowerGateLast && i == WALL_THICKNESS - 1) { 75 | bp.createEntity('small_lamp', { x: x, y: lowerY - WALL_SPACE - i - 1 }, Blueprint.RIGHT, true); 76 | bp.createEntity('medium_electric_pole', { x: x + 1, y: lowerY - WALL_SPACE - i - 1 }, Blueprint.RIGHT, true); 77 | } 78 | placedLowerGateLast = false; 79 | }else{ 80 | if(!placedLowerGateLast && i == WALL_THICKNESS - 1) { 81 | bp.createEntity('small_lamp', { x: x - 1, y: lowerY - WALL_SPACE - i - 1 }, Blueprint.RIGHT, true); 82 | bp.createEntity('medium_electric_pole', { x: x - 2, y: lowerY - WALL_SPACE - i - 1 }, Blueprint.RIGHT, true); 83 | } 84 | placedLowerGateLast = true; 85 | } 86 | } 87 | if (!ent2 || ent2.name == 'straight_rail') { 88 | bp.createEntity(ent2 ? 'gate' : 'stone_wall', { x: x, y: upperY + WALL_SPACE + i }, Blueprint.RIGHT, true); 89 | if(!ent2){ 90 | if(placedUpperGateLast && i == WALL_THICKNESS - 1) { 91 | bp.createEntity('small_lamp', { x: x, y: upperY + WALL_SPACE + i + 1 }, Blueprint.RIGHT, true); 92 | bp.createEntity('medium_electric_pole', { x: x + 1, y: upperY + WALL_SPACE + i + 1 }, Blueprint.RIGHT, true); 93 | } 94 | placedUpperGateLast = false; 95 | }else{ 96 | if(!placedUpperGateLast && i == WALL_THICKNESS - 1) { 97 | bp.createEntity('small_lamp', { x: x - 1, y: upperY + WALL_SPACE + i + 1 }, Blueprint.RIGHT, true); 98 | bp.createEntity('medium_electric_pole', { x: x - 2, y: upperY + WALL_SPACE + i + 1 }, Blueprint.RIGHT, true); 99 | } 100 | placedUpperGateLast = true; 101 | } 102 | } 103 | } 104 | placedLowerGateLast = false; 105 | placedUpperGateLast = false; 106 | for (let y = lowerY - WALL_SPACE - i; y <= upperY + WALL_SPACE + i; y++) { 107 | const ent1 = bp.findEntity({ x: lowerX - WALL_SPACE - i, y: y }); 108 | const ent2 = bp.findEntity({ x: upperX + WALL_SPACE + i, y: y }); 109 | if (!ent1 || ent1.name == 'straight_rail') { 110 | bp.createEntity(ent1 ? 'gate' : 'stone_wall', { x: lowerX - WALL_SPACE - i, y: y }, Blueprint.DOWN, true); 111 | if(!ent1){ 112 | if(placedLowerGateLast) { 113 | bp.createEntity('small_lamp', { x: lowerX - WALL_SPACE - i - 1, y: y }, Blueprint.RIGHT, true); 114 | bp.createEntity('medium_electric_pole', { x: lowerX - WALL_SPACE - i - 1, y: y + 1 }, Blueprint.RIGHT, true); 115 | } 116 | placedLowerGateLast = false; 117 | }else{ 118 | if(!placedLowerGateLast) { 119 | bp.createEntity('small_lamp', { x: lowerX - WALL_SPACE - i - 1, y: y - 1 }, Blueprint.RIGHT, true); 120 | bp.createEntity('medium_electric_pole', { x: lowerX - WALL_SPACE - i - 1, y: y - 2 }, Blueprint.RIGHT, true); 121 | } 122 | placedLowerGateLast = true; 123 | } 124 | } 125 | if (!ent2 || ent2.name == 'straight_rail') { 126 | bp.createEntity(ent2 ? 'gate' : 'stone_wall', { x: upperX + WALL_SPACE + i, y: y }, Blueprint.DOWN, true); 127 | if(!ent2){ 128 | if(placedUpperGateLast) { 129 | bp.createEntity('small_lamp', { x: upperX + WALL_SPACE + i + 1, y: y }, Blueprint.RIGHT, true); 130 | bp.createEntity('medium_electric_pole', { x: upperX + WALL_SPACE + i + 1, y: y + 1 }, Blueprint.RIGHT, true); 131 | } 132 | placedUpperGateLast = false; 133 | }else{ 134 | if(!placedUpperGateLast) { 135 | bp.createEntity('small_lamp', { x: upperX + WALL_SPACE + i + 1, y: y - 1 }, Blueprint.RIGHT, true); 136 | bp.createEntity('medium_electric_pole', { x: upperX + WALL_SPACE + i + 1, y: y - 2 }, Blueprint.RIGHT, true); 137 | } 138 | placedUpperGateLast = true; 139 | } 140 | } 141 | } 142 | } 143 | } 144 | 145 | for (let y = lowerY - WALL_SPACE - WALL_THICKNESS + 1; y <= upperY + WALL_SPACE + WALL_THICKNESS - 1; y++) { 146 | for (let x = lowerX - WALL_SPACE - WALL_THICKNESS + 1; x <= upperX + WALL_SPACE + WALL_THICKNESS - 1; x++) { 147 | if (BORDER_CONCRETE && (y - lowerY + WALL_SPACE + WALL_THICKNESS <= WALL_THICKNESS + 1 || upperY + WALL_SPACE + WALL_THICKNESS - y <= 148 | WALL_THICKNESS + 1 || x - lowerX + WALL_SPACE + WALL_THICKNESS <= WALL_THICKNESS + 1 || upperX + WALL_SPACE + WALL_THICKNESS - x <= 149 | WALL_THICKNESS + 1)) { 150 | bp.createTile(BORDER_CONCRETE, { x: x, y: y }); 151 | } else if (CONCRETE && !bp.findTile({ x: x, y: y })) { 152 | bp.createTile(CONCRETE, { x: x, y: y }); 153 | } 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /cli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | const GENERATOR = require('./index'); 4 | const MEOW = require('meow'); 5 | const YAML = require('yamljs'); 6 | 7 | const options = [ 8 | { 9 | name: 'oilOutpost', 10 | alias: 'oil', 11 | description: 'Generate an oil outpost instead of an ore outpost' 12 | }, 13 | { 14 | name: 'trainSide', 15 | description: '0-3, side the train station should be on (default 1 right)' 16 | }, 17 | { 18 | name: 'trainDirection', 19 | description: '0-3, side train station should enter from (Must be perpendicular to trainSide) (default 2 bottom)' 20 | }, 21 | { 22 | name: 'beacons', 23 | description: 'Boolean, whether to use beacons (default false)' 24 | }, 25 | { 26 | name: 'minerSpace', 27 | alias: 'miner', 28 | description: '0-2, space between miners (default 1)' 29 | }, 30 | { 31 | name: 'minerSize', 32 | alias: 'size', 33 | description: '3-5, width/length of a miner (default 3)' 34 | }, 35 | { 36 | name: 'miningDrillName', 37 | alias: 'drillName', 38 | description: 'String, custom mining drill name (default electric_mining_drill)' 39 | }, 40 | { 41 | name: 'includeRadar', 42 | alias: 'radar', 43 | description: 'Boolean, whether or not to include a radar (default true)', 44 | }, 45 | { 46 | name: 'turretSpacing', 47 | alias: null, 48 | description: '2-9, spacing between turrets on wall (default 8)' 49 | }, 50 | { 51 | name: 'turrets', 52 | alias: null, 53 | description: 'Boolean, whether turrets are enabled or not (default true)' 54 | }, 55 | { 56 | name: 'laserTurrets', 57 | alias: 'laser', 58 | description: 'Boolean, use laser turrets instead of gun turrets (default true)' 59 | }, 60 | { 61 | name: 'includeTrainStation', 62 | alias: 'withTrain', 63 | description: 'Boolean, whether or not to include a train station (default true)' 64 | }, 65 | { 66 | name: 'locomotiveCount', 67 | alias: 'locomotives', 68 | description: '1+, number of locomotives before cargo wagons (default 2)' 69 | }, 70 | { 71 | name: 'cargoWagonCount', 72 | alias: 'wagons', 73 | description: '1+, number of cargo wagons on train (default 4)' 74 | }, 75 | { 76 | name: 'exitRoute', 77 | alias: 'exit', 78 | description: 'true/false, whether or not there is a route past the train station for single-headed trains (default false)' 79 | }, 80 | { 81 | name: 'walls', 82 | alias: null, 83 | description: 'Whether or not to include walls (default true)' 84 | }, 85 | { 86 | name: 'wallSpace', 87 | alias: null, 88 | description: 'Space between wall and rest of the outpost (default 5)' 89 | }, 90 | { 91 | name: 'wallThickness', 92 | alias: null, 93 | description: 'Number of walls thick the outpost defenses are (default 1)' 94 | }, 95 | { 96 | name: 'undergroundBelts', 97 | alias: null, 98 | description: 'Use underground belts in front of miners instead of regular belts' 99 | }, 100 | { 101 | name: 'beltName', 102 | alias: 'belt', 103 | description: 'Name of type of belt in the format `type` or `type_transport_belt` (default `` which is yellow belt)' 104 | }, 105 | { 106 | name: 'useStackInserters', 107 | alias: 'useStack', 108 | description: 'Boolean, use stack inserters between the buffer chests and cargo wagon instead of fast inserters (default true)' 109 | }, 110 | { 111 | name: 'botBased', 112 | alias: 'bots', 113 | description: 'Boolean, use passive provider and requester chests (default false)' 114 | }, 115 | { 116 | name: 'requestItem', 117 | alias: null, 118 | description: 'Item for requester chests to request if botBased (default iron_ore)' 119 | }, 120 | { 121 | name: 'requestAmount', 122 | alias: null, 123 | description: 'The amount of items each chest requests when bot based (default 4800)' 124 | }, 125 | { 126 | name: 'conrete', 127 | alias: null, 128 | description: 'Name of concrete type (in vanilla either `concrete` or `hazard_conrete`) (default none)' 129 | }, 130 | { 131 | name: 'borderConcrete', 132 | alias: null, 133 | description: 'Type of concrete put on the wall and just inside the walls (default none)' 134 | }, 135 | { 136 | name: 'trackConrete', 137 | alias: null, 138 | description: 'Type of concrete put on the track and just surrounding the track (default none)' 139 | }, 140 | { 141 | name: 'balancer', 142 | alias: null, 143 | description: 'Blueprint string for an NxN balancer if the script does not have any available (N being the # of cargo wagons) (no default)' 144 | }, 145 | { 146 | name: 'pumpjackName', 147 | alias: null, 148 | description: 'Name of modded pumpjack (not needed if using vanilla pumpjacks)' 149 | }, 150 | { 151 | name: 'includeLights', 152 | alias: 'lights', 153 | description: 'Include lights in the outpost' 154 | }, 155 | { 156 | name: 'useFile', 157 | alias: 'file', 158 | description: 'Use Yaml File for config instead of cli arguments' 159 | }]; 160 | 161 | function validateData(name, data) { 162 | 163 | switch (name) { 164 | case 'includeLights': 165 | return true; 166 | case 'minedOreDirection': 167 | case 'trainDirection': { 168 | if (parseInt(data) < 0 || parseInt(data) > 3) { 169 | showMessageAndExit(name + ' needs to be between 0 and 3') 170 | } 171 | break; 172 | } 173 | case 'minerSpace': { 174 | if (parseInt(data) < 0 || parseInt(data) > 2) { 175 | showMessageAndExit(name + ' needs to be between 0 and 2') 176 | } 177 | break; 178 | } 179 | case 'turretSpacing': { 180 | if (parseInt(data) < 2 || parseInt(data) > 9) { 181 | showMessageAndExit(name + ' needs to be between 2 and 9') 182 | } 183 | break; 184 | } 185 | case 'requestAmount': 186 | case 'cargoWagonCount': 187 | case 'locomotiveCount': { 188 | if (parseInt(data) <= 0) { 189 | showMessageAndExit(name + ' needs to be higher than 0') 190 | } 191 | break; 192 | } 193 | default: { 194 | return data; 195 | } 196 | } 197 | 198 | return data; 199 | 200 | } 201 | 202 | function showMessageAndExit(message, code = 1) { 203 | if (Array.isArray(message)) { 204 | message.forEach(function (val) { 205 | console.error(val); 206 | }) 207 | } else { 208 | console.error(message); 209 | } 210 | 211 | process.exit(code); 212 | } 213 | 214 | let usageOptions = ''; 215 | let aliases = {}; 216 | 217 | options.forEach(function (val) { 218 | let alias = ''; 219 | 220 | if (val.alias !== null) { 221 | alias = ' -' + val.alias; 222 | aliases[val.alias] = val.name; 223 | } 224 | 225 | usageOptions += `--${val.name},${alias} ${val.description} 226 | `; 227 | 228 | }); 229 | 230 | const cli = MEOW(` 231 | Usage 232 | $ cli blueprintstring 233 | Options 234 | ${usageOptions} 235 | Examples 236 | $ cli 0eNqNj8EKwj... --minedOreDirection=1 --radar=true --locomotives=1 --wagons=2 237 | $ cli 0eNqNj8EKwj... --useFile=config.yaml`, { 238 | alias: aliases 239 | }); 240 | 241 | 242 | let blueprint = ''; 243 | //if no input (arguments without -- prefixed) show help and exit the program 244 | if (cli.input.length === 0) { 245 | cli.showHelp(); 246 | return; 247 | } else { 248 | blueprint = cli.input[0] 249 | } 250 | 251 | let opts = {}; 252 | //load arguments from cli object 253 | let args = cli.flags; 254 | 255 | //check if useFile argument is available 256 | if (args.hasOwnProperty('useFile')) { 257 | try { 258 | //load arguments from yaml file instead of cli arguments 259 | args = YAML.load(args['useFile']); 260 | } catch (e) { 261 | showMessageAndExit(e.message); 262 | } 263 | } 264 | 265 | if(args.hasOwnProperty('blueprint')) { 266 | blueprint = args['blueprint']; 267 | delete args['blueprint']; 268 | } 269 | 270 | options.forEach(function (val) { 271 | if (args.hasOwnProperty(val.name)) { 272 | opts[val.name] = validateData(val.name, args[val.name]); 273 | //delete processed keys from arguments array 274 | delete args[val.name] 275 | delete args[val.alias] 276 | } 277 | }); 278 | 279 | //if arguments has still elements (arguments that are not in the options array) 280 | if (Object.keys(args).length >= 1) { 281 | let messages = []; 282 | for( let key in args) { 283 | if(!args.hasOwnProperty(key)) { 284 | continue; 285 | } 286 | //push new message to array 287 | messages.push(`Invalid Option: ${key}`); 288 | } 289 | //show all invalid options and exit the program 290 | showMessageAndExit(messages); 291 | } 292 | 293 | try { 294 | global.console = new console.Console({ stdout: process.stderr, stderr: process.stderr }); 295 | process.stdout.write(opts.oilOutpost ? GENERATOR.oilOutpost(blueprint, opts) : GENERATOR.outpost(blueprint, opts)); 296 | } catch (e) { 297 | console.log(e.message); 298 | } 299 | -------------------------------------------------------------------------------- /tools/lib/addBeacons.js: -------------------------------------------------------------------------------- 1 | const PUMPJACK_EXIT_DIRECTION = { 2 | 0: { x: 2, y: -1 }, 3 | 1: { x: 3, y: 0 }, 4 | 2: { x: 0, y: 3 }, 5 | 3: { x: -1, y: 2 } 6 | }; 7 | 8 | const ACTIVATIONS = { 9 | sigmoid: x => 1 / (1 + Math.pow(Math.E, Math.max(-60, Math.min(60, x * 5.0)))), 10 | relu: x => Math.max(x, 0.0) 11 | } 12 | 13 | const NODES = 14 | ` 15 | 0 DefaultNodeGene(key=0, bias=-0.19930210829, response=1.0, activation=relu, aggregation=sum) 16 | 4051 DefaultNodeGene(key=4051, bias=0.471368061127, response=1.0, activation=sigmoid, aggregation=sum) 17 | 4063 DefaultNodeGene(key=4063, bias=1.16531658624, response=1.0, activation=sigmoid, aggregation=sum)` 18 | .trim().split('\n').map(str => { 19 | const m = str.match(/DefaultNodeGene\(key=(\d+), bias=(-?[0-9.]+), response=1\.0, activation=([^,]+)/); 20 | return { 21 | key: parseInt(m[1]), 22 | bias: parseInt(m[2]), 23 | activation: m[3] 24 | } 25 | }); 26 | 27 | 28 | const CONNS = 29 | ` 30 | DefaultConnectionGene(key=(-224, 0), weight=1.5413694549, enabled=True) 31 | DefaultConnectionGene(key=(-221, 0), weight=-1.73652697394, enabled=False) 32 | DefaultConnectionGene(key=(-220, 0), weight=0.343078629085, enabled=True) 33 | DefaultConnectionGene(key=(-219, 0), weight=-0.301904084502, enabled=True) 34 | DefaultConnectionGene(key=(-217, 0), weight=-1.11489675462, enabled=True) 35 | DefaultConnectionGene(key=(-214, 0), weight=-0.529785274661, enabled=True) 36 | DefaultConnectionGene(key=(-212, 0), weight=1.70490013839, enabled=True) 37 | DefaultConnectionGene(key=(-211, 0), weight=-0.400655932131, enabled=True) 38 | DefaultConnectionGene(key=(-210, 0), weight=0.232071962473, enabled=True) 39 | DefaultConnectionGene(key=(-209, 0), weight=0.949608241967, enabled=False) 40 | DefaultConnectionGene(key=(-207, 0), weight=-0.854164713216, enabled=False) 41 | DefaultConnectionGene(key=(-206, 0), weight=0.791919494285, enabled=False) 42 | DefaultConnectionGene(key=(-204, 0), weight=0.832551571664, enabled=False) 43 | DefaultConnectionGene(key=(-202, 0), weight=0.705105144097, enabled=True) 44 | DefaultConnectionGene(key=(-201, 0), weight=3.118827935, enabled=True) 45 | DefaultConnectionGene(key=(-200, 0), weight=1.23197212973, enabled=False) 46 | DefaultConnectionGene(key=(-199, 0), weight=-0.855519826154, enabled=False) 47 | DefaultConnectionGene(key=(-198, 0), weight=-1.00537675395, enabled=True) 48 | DefaultConnectionGene(key=(-197, 0), weight=-1.29989554941, enabled=False) 49 | DefaultConnectionGene(key=(-195, 0), weight=-1.17663556138, enabled=False) 50 | DefaultConnectionGene(key=(-194, 0), weight=-1.6868171029, enabled=True) 51 | DefaultConnectionGene(key=(-193, 0), weight=-4.20255413837, enabled=False) 52 | DefaultConnectionGene(key=(-190, 0), weight=0.0301954636296, enabled=False) 53 | DefaultConnectionGene(key=(-189, 0), weight=-0.0207601798032, enabled=True) 54 | DefaultConnectionGene(key=(-185, 0), weight=2.85602068945, enabled=False) 55 | DefaultConnectionGene(key=(-184, 0), weight=-1.46537971189, enabled=False) 56 | DefaultConnectionGene(key=(-183, 0), weight=0.391997146851, enabled=True) 57 | DefaultConnectionGene(key=(-182, 0), weight=0.809332294847, enabled=False) 58 | DefaultConnectionGene(key=(-178, 0), weight=3.45868198473, enabled=False) 59 | DefaultConnectionGene(key=(-177, 0), weight=0.556744091697, enabled=False) 60 | DefaultConnectionGene(key=(-175, 0), weight=0.734870904163, enabled=True) 61 | DefaultConnectionGene(key=(-172, 0), weight=0.291742755804, enabled=True) 62 | DefaultConnectionGene(key=(-171, 0), weight=-0.687255399568, enabled=True) 63 | DefaultConnectionGene(key=(-170, 0), weight=-0.0266361683025, enabled=True) 64 | DefaultConnectionGene(key=(-169, 0), weight=-0.454846146535, enabled=False) 65 | DefaultConnectionGene(key=(-167, 0), weight=-2.10298686623, enabled=False) 66 | DefaultConnectionGene(key=(-166, 0), weight=0.295781796251, enabled=False) 67 | DefaultConnectionGene(key=(-165, 0), weight=0.85070743782, enabled=False) 68 | DefaultConnectionGene(key=(-164, 0), weight=1.61505756306, enabled=True) 69 | DefaultConnectionGene(key=(-163, 0), weight=1.34242161192, enabled=False) 70 | DefaultConnectionGene(key=(-162, 0), weight=0.147944519834, enabled=True) 71 | DefaultConnectionGene(key=(-161, 0), weight=-0.479709972784, enabled=True) 72 | DefaultConnectionGene(key=(-160, 0), weight=0.791876028519, enabled=True) 73 | DefaultConnectionGene(key=(-159, 0), weight=-0.0616190374359, enabled=False) 74 | DefaultConnectionGene(key=(-156, 0), weight=0.300566333075, enabled=False) 75 | DefaultConnectionGene(key=(-154, 0), weight=-0.0331157746529, enabled=True) 76 | DefaultConnectionGene(key=(-153, 0), weight=-0.334749631961, enabled=True) 77 | DefaultConnectionGene(key=(-152, 0), weight=-2.01053940319, enabled=False) 78 | DefaultConnectionGene(key=(-150, 0), weight=0.481962293653, enabled=True) 79 | DefaultConnectionGene(key=(-149, 0), weight=-3.53337647402, enabled=True) 80 | DefaultConnectionGene(key=(-148, 0), weight=-1.32794069721, enabled=True) 81 | DefaultConnectionGene(key=(-147, 0), weight=-0.393654029279, enabled=True) 82 | DefaultConnectionGene(key=(-146, 0), weight=-0.769349241496, enabled=True) 83 | DefaultConnectionGene(key=(-145, 0), weight=1.64002525305, enabled=False) 84 | DefaultConnectionGene(key=(-144, 0), weight=-1.16286174944, enabled=False) 85 | DefaultConnectionGene(key=(-143, 0), weight=-2.54444887185, enabled=True) 86 | DefaultConnectionGene(key=(-142, 0), weight=-0.032564806715, enabled=False) 87 | DefaultConnectionGene(key=(-141, 0), weight=-0.451481358431, enabled=False) 88 | DefaultConnectionGene(key=(-140, 0), weight=0.65006685108, enabled=True) 89 | DefaultConnectionGene(key=(-139, 0), weight=-0.536330356947, enabled=False) 90 | DefaultConnectionGene(key=(-138, 0), weight=0.864766392772, enabled=True) 91 | DefaultConnectionGene(key=(-137, 0), weight=1.3341409107, enabled=False) 92 | DefaultConnectionGene(key=(-136, 0), weight=3.29667542995, enabled=True) 93 | DefaultConnectionGene(key=(-134, 0), weight=-0.761100171666, enabled=True) 94 | DefaultConnectionGene(key=(-133, 0), weight=-2.65926316254, enabled=True) 95 | DefaultConnectionGene(key=(-132, 0), weight=-2.19535798843, enabled=False) 96 | DefaultConnectionGene(key=(-131, 0), weight=-0.066321456559, enabled=False) 97 | DefaultConnectionGene(key=(-130, 0), weight=-1.69160520442, enabled=False) 98 | DefaultConnectionGene(key=(-129, 0), weight=1.10583689194, enabled=True) 99 | DefaultConnectionGene(key=(-128, 0), weight=2.17015834864, enabled=False) 100 | DefaultConnectionGene(key=(-128, 4051), weight=-1.88270841462, enabled=True) 101 | DefaultConnectionGene(key=(-127, 0), weight=2.4282903591, enabled=True) 102 | DefaultConnectionGene(key=(-126, 0), weight=-0.565074680378, enabled=True) 103 | DefaultConnectionGene(key=(-125, 0), weight=-1.41625790678, enabled=True) 104 | DefaultConnectionGene(key=(-123, 0), weight=0.525458430609, enabled=True) 105 | DefaultConnectionGene(key=(-122, 0), weight=-0.233628531563, enabled=True) 106 | DefaultConnectionGene(key=(-121, 0), weight=1.49283260805, enabled=False) 107 | DefaultConnectionGene(key=(-119, 0), weight=1.16838564227, enabled=False) 108 | DefaultConnectionGene(key=(-118, 0), weight=2.87120981671, enabled=False) 109 | DefaultConnectionGene(key=(-117, 0), weight=0.00347417146359, enabled=False) 110 | DefaultConnectionGene(key=(-115, 0), weight=-3.43415774508, enabled=False) 111 | DefaultConnectionGene(key=(-113, 0), weight=-2.29199930737, enabled=False) 112 | DefaultConnectionGene(key=(-109, 0), weight=-1.41842555381, enabled=True) 113 | DefaultConnectionGene(key=(-108, 0), weight=1.19463968323, enabled=False) 114 | DefaultConnectionGene(key=(-107, 0), weight=-0.528686570881, enabled=False) 115 | DefaultConnectionGene(key=(-106, 0), weight=1.31882730949, enabled=False) 116 | DefaultConnectionGene(key=(-105, 0), weight=-1.40808776276, enabled=False) 117 | DefaultConnectionGene(key=(-101, 0), weight=3.5020179311, enabled=False) 118 | DefaultConnectionGene(key=(-101, 4063), weight=1.73994627505, enabled=True) 119 | DefaultConnectionGene(key=(-99, 0), weight=-1.2801160722, enabled=False) 120 | DefaultConnectionGene(key=(-96, 0), weight=-0.527117811987, enabled=False) 121 | DefaultConnectionGene(key=(-95, 0), weight=-1.91390037981, enabled=True) 122 | DefaultConnectionGene(key=(-94, 0), weight=-0.584922491649, enabled=True) 123 | DefaultConnectionGene(key=(-92, 0), weight=0.549085964898, enabled=False) 124 | DefaultConnectionGene(key=(-91, 0), weight=-0.933913178988, enabled=False) 125 | DefaultConnectionGene(key=(-90, 0), weight=-0.245489022132, enabled=False) 126 | DefaultConnectionGene(key=(-89, 0), weight=0.844356811709, enabled=True) 127 | DefaultConnectionGene(key=(-88, 0), weight=-1.19116348909, enabled=False) 128 | DefaultConnectionGene(key=(-85, 0), weight=1.41959166008, enabled=True) 129 | DefaultConnectionGene(key=(-84, 0), weight=-2.51267527432, enabled=False) 130 | DefaultConnectionGene(key=(-84, 4051), weight=0.301589674219, enabled=True) 131 | DefaultConnectionGene(key=(-83, 0), weight=-0.408036773825, enabled=False) 132 | DefaultConnectionGene(key=(-82, 0), weight=1.31927425599, enabled=False) 133 | DefaultConnectionGene(key=(-81, 0), weight=-25.218265036, enabled=True) 134 | DefaultConnectionGene(key=(-80, 0), weight=3.65327513827, enabled=False) 135 | DefaultConnectionGene(key=(-77, 0), weight=-1.92843333795, enabled=True) 136 | DefaultConnectionGene(key=(-76, 0), weight=-1.8473235276, enabled=False) 137 | DefaultConnectionGene(key=(-75, 0), weight=-3.89923551079, enabled=True) 138 | DefaultConnectionGene(key=(-74, 0), weight=-0.0839213988943, enabled=True) 139 | DefaultConnectionGene(key=(-73, 0), weight=0.689442370279, enabled=True) 140 | DefaultConnectionGene(key=(-71, 0), weight=0.792529973153, enabled=False) 141 | DefaultConnectionGene(key=(-68, 0), weight=-2.6916246089, enabled=False) 142 | DefaultConnectionGene(key=(-65, 0), weight=0.0773014426042, enabled=True) 143 | DefaultConnectionGene(key=(-63, 0), weight=0.688421437652, enabled=True) 144 | DefaultConnectionGene(key=(-61, 0), weight=0.164599194104, enabled=True) 145 | DefaultConnectionGene(key=(-60, 0), weight=-17.8732810798, enabled=True) 146 | DefaultConnectionGene(key=(-59, 0), weight=-0.132745187945, enabled=True) 147 | DefaultConnectionGene(key=(-56, 0), weight=1.46172809755, enabled=False) 148 | DefaultConnectionGene(key=(-54, 0), weight=-0.335819557718, enabled=False) 149 | DefaultConnectionGene(key=(-53, 0), weight=1.37925582364, enabled=False) 150 | DefaultConnectionGene(key=(-52, 0), weight=0.338384424307, enabled=True) 151 | DefaultConnectionGene(key=(-50, 0), weight=-6.65358791073, enabled=False) 152 | DefaultConnectionGene(key=(-48, 0), weight=-1.24753586969, enabled=True) 153 | DefaultConnectionGene(key=(-47, 0), weight=0.618499706801, enabled=False) 154 | DefaultConnectionGene(key=(-46, 0), weight=0.472309108051, enabled=False) 155 | DefaultConnectionGene(key=(-45, 0), weight=-0.920936569666, enabled=False) 156 | DefaultConnectionGene(key=(-43, 0), weight=4.19412027292, enabled=False) 157 | DefaultConnectionGene(key=(-42, 0), weight=1.90735419689, enabled=True) 158 | DefaultConnectionGene(key=(-41, 0), weight=2.4931499564, enabled=True) 159 | DefaultConnectionGene(key=(-40, 0), weight=0.817777417629, enabled=False) 160 | DefaultConnectionGene(key=(-39, 0), weight=-1.19539235949, enabled=False) 161 | DefaultConnectionGene(key=(-38, 0), weight=0.805559257215, enabled=True) 162 | DefaultConnectionGene(key=(-37, 0), weight=0.699511328861, enabled=False) 163 | DefaultConnectionGene(key=(-35, 0), weight=-0.326546673697, enabled=False) 164 | DefaultConnectionGene(key=(-32, 0), weight=0.0040379187648, enabled=True) 165 | DefaultConnectionGene(key=(-30, 0), weight=-0.546063451057, enabled=True) 166 | DefaultConnectionGene(key=(-29, 0), weight=-0.406826615086, enabled=True) 167 | DefaultConnectionGene(key=(-27, 0), weight=-1.32088808941, enabled=True) 168 | DefaultConnectionGene(key=(-26, 0), weight=-0.252547850745, enabled=False) 169 | DefaultConnectionGene(key=(-25, 0), weight=2.331221711, enabled=True) 170 | DefaultConnectionGene(key=(-24, 0), weight=-1.81795383608, enabled=True) 171 | DefaultConnectionGene(key=(-22, 0), weight=-2.5564789552, enabled=False) 172 | DefaultConnectionGene(key=(-20, 0), weight=1.96220394372, enabled=True) 173 | DefaultConnectionGene(key=(-15, 0), weight=-1.32133664618, enabled=False) 174 | DefaultConnectionGene(key=(-14, 0), weight=0.982033299449, enabled=False) 175 | DefaultConnectionGene(key=(-13, 0), weight=2.87883936481, enabled=False) 176 | DefaultConnectionGene(key=(-12, 0), weight=0.255847301078, enabled=False) 177 | DefaultConnectionGene(key=(-4, 0), weight=-0.659146242734, enabled=True) 178 | DefaultConnectionGene(key=(-2, 0), weight=-0.113186600412, enabled=True) 179 | DefaultConnectionGene(key=(-1, 0), weight=-0.616556219323, enabled=True) 180 | DefaultConnectionGene(key=(4051, 0), weight=2.42256427044, enabled=True) 181 | DefaultConnectionGene(key=(4063, 0), weight=2.28399291126, enabled=True) 182 | ` 183 | .trim().split('\n').map(str => { 184 | const m = str.match(/DefaultConnectionGene\(key=\(([\-0-9]+), ([\-0-9]+)\), weight=([\-.0-9]+), enabled=(True|False)/); 185 | 186 | return m[4] == 'True' ? { 187 | from: parseInt(m[1]), 188 | to: parseInt(m[2]), 189 | weight: parseInt(m[3]) 190 | } : null 191 | }).filter(connection => !!connection); 192 | 193 | function runNN(input) { 194 | function getForNode(key) { 195 | if (key < 0) return input[-key - 1]; 196 | const node = NODES.filter(n => n.key == key)[0]; 197 | const total = CONNS.filter(conn => conn.to == key) 198 | .reduce((total, conn) => total + getForNode(conn.from) * conn.weight, 0); 199 | 200 | return ACTIVATIONS[node.activation](node.bias + total); 201 | } 202 | 203 | return getForNode(0); 204 | } 205 | 206 | function shouldPlaceBeacon(bp, posX, posY) { 207 | const input = []; 208 | const RADIUS = 5; 209 | let reach = false; 210 | for (let x = posX - RADIUS; x <= posX + RADIUS; x++) { 211 | for (let y = posY - RADIUS; y <= posY + RADIUS; y++) { 212 | const ent = bp.findEntity({ x, y }); 213 | if (!reach && ent && ent.name == 'pumpjack' && x >= posX - 4 && x <= posX + 4 && y >= posY - 4 && y <= posY + 4) { 214 | reach = true; 215 | } 216 | if (x < posX - 1 || x > posX + 1 || y < posY - 1 || y > posY + 1) { 217 | input.push(ent && ent.name == 'pumpjack' ? 1 : (ent && ent.name == 'pipe' ? 0.5 : 0)); 218 | input.push(ent && ent.name == 'beacon' ? 1 : 0); 219 | } 220 | } 221 | } 222 | return reach && runNN(input) >= 0.5; 223 | } 224 | 225 | function placeBeacons(bp, getPumpjackOutput) { 226 | const tempPipes = []; 227 | bp.entities.filter(ent => ent.name == 'pumpjack').forEach(pumpjack => { 228 | const pumpjackOutput = getPumpjackOutput(pumpjack); 229 | if (!bp.findEntity(pumpjackOutput)) { 230 | tempPipes.push(bp.createEntity('pipe', pumpjackOutput)); 231 | } 232 | }); 233 | 234 | const start = bp.topLeft().subtract({ x: 5, y: 5 }); 235 | const end = bp.bottomRight().add({ x: 5, y: 5 }); 236 | for (let x = start.x; x <= end.x; x++) { 237 | for (let y = start.y; y <= end.y; y++) { 238 | if (shouldPlaceBeacon(bp, x, y)) { 239 | let canPlace = true; 240 | for (let i = -1; i <= 1; i++) { 241 | for (let j = -1; j <= 1; j++) { 242 | if (bp.findEntity({ x: x + i, y: y + j })) canPlace = false; 243 | } 244 | } 245 | if (canPlace) { 246 | const beacon = bp.createEntity('beacon', { x: x - 1, y: y - 1 }); 247 | beacon.modules['speed_module_3'] = 2; 248 | } 249 | } 250 | } 251 | } 252 | 253 | tempPipes.forEach(pipe => bp.removeEntity(pipe)); 254 | } 255 | 256 | module.exports = placeBeacons 257 | -------------------------------------------------------------------------------- /tools/oilOutpost.js: -------------------------------------------------------------------------------- 1 | const Blueprint = require('factorio-blueprint'); 2 | const aStar = require('a-star'); 3 | const Victor = require('victor'); 4 | 5 | const generateDefenses = require('./lib/defenses'); 6 | const generateTrainStation = require('./lib/trainStation'); 7 | const fixRail = require('./lib/fixRail'); 8 | const addBeacons = require('./lib/addBeacons'); 9 | 10 | const PUMPJACK_EXIT_DIRECTION = { 11 | 0: { x: 2, y: -1 }, 12 | 2: { x: 3, y: 0 }, 13 | 4: { x: 0, y: 3 }, 14 | 6: { x: -1, y: 2 } 15 | }; 16 | 17 | const SIDES = [ 18 | { x: 1, y: 0 }, 19 | { x: -1, y: 0 }, 20 | { x: 0, y: 1 }, 21 | { x: 0, y: -1 } 22 | ]; 23 | 24 | const DIRECTION_FROM_OFFSET = { 25 | '0,-1': 0, 26 | '1,0': 2, 27 | '0,1': 4, 28 | '-1,0': 6 29 | }; 30 | 31 | function useOrDefault(value, def) { 32 | return isNaN(parseInt(value)) || value == undefined ? def : parseInt(value); 33 | } 34 | 35 | const MAX_UNDERGROUND_REACH = 11; // Includes underground pipes 36 | 37 | module.exports = function(string, opt = {}) { 38 | if (!string) throw new Error('You must provide a blueprint string with pumpjacks!'); 39 | 40 | const NAME = opt.name || 'Oil Outpost - %pumpjacks% Pumpjacks'; 41 | 42 | // Directions 43 | const TRAIN_SIDE = useOrDefault(opt.trainSide, 1); 44 | const TRAIN_DIRECTION = useOrDefault(opt.trainDirection, 2); 45 | 46 | const FLIP_ALL = TRAIN_SIDE == TRAIN_DIRECTION + 1 || (TRAIN_SIDE == 0 && TRAIN_DIRECTION == 3); 47 | const ROTATE_ALL = (TRAIN_DIRECTION + 2) % 4; 48 | 49 | if (Math.abs(TRAIN_SIDE - TRAIN_DIRECTION) % 2 == 0) throw new Error('opt.trainSide and opt.trainDirection must be perpendicular.'); 50 | 51 | // General 52 | const PUMPJACK_NAME = opt.pumpjackName != undefined ? opt.pumpjackName : 'pumpjack'; 53 | const MODULE = opt.module; 54 | const INCLUDE_RADAR = opt.includeRadar != undefined ? opt.includeRadar : true; 55 | const INCLUDE_LIGHTS = opt.includeLights != undefined ? opt.includeLights : false; 56 | const TANKS = useOrDefault(opt.tanks, 2); 57 | const USE_BEACONS = !!opt.beacons; 58 | 59 | // Defenses 60 | const TURRETS_ENABLED = opt.turrets != undefined ? opt.turrets : true; 61 | const TURRET_SPACING = useOrDefault(opt.turretSpacing, 8); 62 | const USE_LASER_TURRETS = opt.laserTurrets == undefined ? true : !!opt.laserTurrets; 63 | 64 | const WALLS_ENABLED = opt.walls != undefined ? !!opt.walls : true; 65 | const WALL_SPACE = useOrDefault(opt.wallSpace, 5); 66 | const WALL_THICKNESS = WALLS_ENABLED ? useOrDefault(opt.wallThickness, 1) : 0; 67 | 68 | // Trains 69 | const INCLUDE_TRAIN_STATION = opt.includeTrainStation != undefined ? opt.includeTrainStation : true; 70 | const LOCOMOTIVES = useOrDefault(opt.locomotiveCount, 1); 71 | const WAGONS = useOrDefault(opt.wagonCount, 2); 72 | const SINGLE_HEADED_TRAIN = opt.exitRoute || false; 73 | const ADDITIONAL_SPACE = useOrDefault(opt.addtionalStationSpace, 0); 74 | 75 | // Bot info 76 | const BOT_BASED = opt.botBased || false; 77 | const REQUEST_TYPE = opt.requestItem || 'iron_ore'; 78 | const REQUEST_AMOUNT = useOrDefault(opt.requestAmount, 4800); 79 | 80 | // Tiles 81 | const CONCRETE = opt.concrete || ''; 82 | const BORDER_CONCRETE = opt.borderConcrete || ''; 83 | const TRACK_CONCRETE = opt.trackConcrete || ''; 84 | 85 | const newEntityData = {}; 86 | 87 | if (CONCRETE) newEntityData[CONCRETE] = { type: 'tile' } 88 | if (BORDER_CONCRETE && !newEntityData[BORDER_CONCRETE]) newEntityData[BORDER_CONCRETE] = { type: 'tile' } 89 | if (TRACK_CONCRETE && !newEntityData[TRACK_CONCRETE]) newEntityData[TRACK_CONCRETE] = { type: 'tile' } 90 | if (PUMPJACK_NAME && !newEntityData[PUMPJACK_NAME]) newEntityData[PUMPJACK_NAME] = { type: 'item', width: 3, height: 3 } 91 | Blueprint.setEntityData(newEntityData); 92 | 93 | function getPumpjackOutput(pumpjack) { 94 | const ROTATE_OFFSET = ((4 - ROTATE_ALL) % 4); 95 | const ORDER = [0, -1, 2, 3]; 96 | if (pumpjack.direction % 4 == 0) ORDER.reverse(); 97 | let offset = { 98 | x: PUMPJACK_EXIT_DIRECTION[(pumpjack.direction + ROTATE_ALL * 2) % 8].x, 99 | y: PUMPJACK_EXIT_DIRECTION[(pumpjack.direction + 100 | ROTATE_ALL * 2) % 8].y 101 | }; 102 | offset.x = ORDER[(ORDER.indexOf(offset.x) + ROTATE_OFFSET) % ORDER.length]; 103 | offset.y = ORDER[(ORDER.indexOf(offset.y) + ROTATE_OFFSET) % ORDER.length]; 104 | if (FLIP_ALL) offset.x = 2 - offset.x; 105 | 106 | return pumpjack.position.clone().add(offset); 107 | } 108 | 109 | const templateBp = new Blueprint(string); 110 | let bp = new Blueprint(); 111 | 112 | const center = templateBp.center(); 113 | templateBp.fixCenter({ x: center.x - (center.x % 2), y: center.y - (center.y % 2)}) 114 | 115 | bp.placeBlueprint(templateBp, { x: 0, y: 0 }, (4 - ROTATE_ALL) % 4); 116 | 117 | if (FLIP_ALL) { 118 | bp.entities.forEach(e => { 119 | /*if (e.name == 'train_stop') { 120 | e.position.x = -e.position.x + e.size.x; 121 | return; 122 | }*/ 123 | e.position.x = -e.position.x - e.size.x; 124 | }); 125 | /*bp.tiles.forEach(t => { 126 | t.position.x = -t.position.x - 1; 127 | });*/ 128 | bp.fixCenter({ x: 1, y: 0 }); 129 | } 130 | 131 | bp = new Blueprint(bp.toObject()); 132 | 133 | bp.fixCenter({ x: 0.5, y: 0.5 }); // Tracks in a blueprint always offsets everything by 0.5, so let's keep it clean 134 | 135 | const alignmentTracks = bp.entities.filter(ent => ent.name == 'straight_rail'); 136 | 137 | if (alignmentTracks.length != 1) throw new Error('Your blueprint must contain exactly one track for correct alignment.'); 138 | 139 | const alignment = { 140 | x: alignmentTracks[0].position.x + (TANKS % 2 == 0 ? 1 : 0), 141 | y: alignmentTracks[0].position.y + (LOCOMOTIVES % 2 == 0 ? 1 : 0) + 1 // Add 1 because target is 1 off grid in Y 142 | }; 143 | 144 | alignmentTracks[0].remove(); 145 | 146 | if (alignment.x < 0) alignment.x += Math.ceil(-alignment.x / 2) * 2; 147 | if (alignment.y < 0) alignment.y += Math.ceil(-alignment.y / 2) * 2; 148 | alignment.x %= 2; 149 | alignment.y %= 2; 150 | 151 | 152 | 153 | bp.entities.forEach(ent => { 154 | if (ent.name != PUMPJACK_NAME) { 155 | let errStr = PUMPJACK_NAME == 'pumpjack' ? '' : ' (Name: '+PUMPJACK_NAME+')' 156 | throw new Error('The blueprint must only contain pumpjacks'+errStr+' and one track!'); 157 | } 158 | else if (bp.findEntity(getPumpjackOutput(ent))) { 159 | throw new Error('A pumpjack is facing into another pumpjack!'); 160 | } 161 | }); 162 | 163 | if (USE_BEACONS) addBeacons(bp, getPumpjackOutput); 164 | 165 | let target = new Victor(bp.topRight().x + 1, (bp.topRight().y + bp.bottomRight().y) / 2); 166 | 167 | target.x += -(target.x % 2) + alignment.x 168 | target.y += -(target.y % 2) + alignment.y; 169 | 170 | bp.entities.filter(ent => ent.name == PUMPJACK_NAME).forEach(pumpjack => { 171 | let powered = false; 172 | bp.entities.filter(ent => ent.name == 'medium_electric_pole').forEach(pole => { 173 | if (powered) return; 174 | if ((pole.position.x - pumpjack.position.x <= 5) && 175 | (pole.position.y - pumpjack.position.y <= 5) && 176 | (pumpjack.position.x - pole.position.x <= 3) && 177 | (pumpjack.position.y - pole.position.y <= 3)) { 178 | powered = true; 179 | } 180 | }); 181 | if (powered) return; 182 | 183 | const output = getPumpjackOutput(pumpjack); 184 | const electricPoleLocations = []; 185 | for (let x = -1; x <= 3; x++) { 186 | for (let y = -1; y <= 3; y++) { 187 | if (x != -1 && x != 3 && y != -1 && y != 3) continue; // Only on edges 188 | const pos = { x: x + pumpjack.position.x, y: y + pumpjack.position.y } 189 | if (pos.x == output.x && pos.y == output.y) continue; 190 | if (x == output.x || y == output.y) electricPoleLocations.push(pos); 191 | else electricPoleLocations.unshift(pos); 192 | } 193 | } 194 | for (let i = 0; i < electricPoleLocations.length; i++) { 195 | const x = electricPoleLocations[i].x; 196 | const y = electricPoleLocations[i].y; 197 | if (bp.findEntity({ x, y })) continue; 198 | let blocking = false; 199 | SIDES.forEach(side => { 200 | const ent = bp.findEntity({ x: x + side.x, y: y + side.y }); 201 | if (!ent || ent.name != PUMPJACK_NAME) return; 202 | const otherOutput = getPumpjackOutput(ent); 203 | if (otherOutput.x == x && otherOutput.y == y) blocking = true; 204 | }); 205 | if (!blocking) { 206 | bp.createEntity('medium_electric_pole', { x, y }); 207 | break; 208 | } 209 | } 210 | }); 211 | 212 | bp.entities.filter(ent => ent.name == PUMPJACK_NAME).forEach(pumpjack => { 213 | if (bp.findEntity(getPumpjackOutput(pumpjack))) return; 214 | // if (bp.findEntity(getPumpjackOutput(pumpjack))) bp.findEntity(getPumpjackOutput(pumpjack)).remove(); 215 | const result = aStar({ 216 | start: getPumpjackOutput(pumpjack), 217 | isEnd: (node) => { 218 | if (node.x == target.x && node.y == target.y) return true; 219 | for (let i = 0; i < SIDES.length; i++) { 220 | const entity = bp.findEntity(node.clone().add(SIDES[i])); 221 | if (entity && entity.name == 'pipe') { 222 | return true; 223 | } 224 | } 225 | return false; 226 | }, 227 | neighbor: (node) => { 228 | return SIDES.map(side => node.clone().add(side)) 229 | .filter(pos => !bp.findEntity(pos) || bp.findEntity(pos).name == 'beacon') 230 | .filter(pos => pos.x <= target.x); 231 | }, 232 | distance: (nodeA, nodeB) => { 233 | let scale = 1.0; 234 | if (USE_BEACONS) { 235 | const entityA = bp.findEntity(nodeA); 236 | const entityB = bp.findEntity(nodeB); 237 | if ((entityA && entityA.name == 'beacon') || (entityB && entityB.name == 'beacon')) { 238 | scale = 100; 239 | } 240 | } 241 | return (Math.abs(nodeA.x - nodeB.x) * 0.98 + Math.abs(nodeA.y - nodeB.y)) * scale; 242 | }, 243 | heuristic: (node) => { 244 | return 0; //Math.abs(node.x - target.x) + Math.abs(node.y - target.y); 245 | }, 246 | hash: (node) => { 247 | return node.x + ',' + node.y 248 | }, 249 | timeout: 5000 250 | }); 251 | if (result.status != 'success') { 252 | if (result.status == 'noPath') throw new Error('Could not create path for all pipes!'); 253 | else throw new Error('Took too long to generate pipe paths!'); 254 | } 255 | result.path.forEach(pos => { 256 | if (bp.findEntity(pos)) bp.findEntity(pos).remove(); 257 | bp.createEntity('pipe', pos, 0, true); 258 | }); 259 | }); 260 | 261 | function simplifyPipes(bp, start, minLength = 3) { 262 | const checked = {}; 263 | const stack = [start]; 264 | 265 | const straights = []; // List of straight sets of pipe 266 | 267 | while (stack.length > 0) { 268 | const pos = stack.pop(); 269 | const entity = bp.findEntity(pos); 270 | if (checked[pos.x + ',' + pos.y] || !entity || entity.name != 'pipe') continue; 271 | checked[pos.x + ',' + pos.y] = true; 272 | 273 | const sidePipes = SIDES.map(side => bp.findEntity(pos.clone().add(side))) 274 | .filter(ent => !!ent) 275 | .filter(ent => ent.name == 'pipe' || (ent.name == PUMPJACK_NAME && (getPumpjackOutput(ent).x == pos.x && getPumpjackOutput(ent).y == pos.y))) 276 | .map(ent => ent.position); 277 | 278 | if (pos.from) { 279 | let shouldUnderground = sidePipes.length == 2 && (sidePipes[0].x - pos.x == pos.x - sidePipes[1].x) && (sidePipes[0].y - pos.y == pos.y - 280 | sidePipes[1].y) 281 | 282 | const offsetX = pos.from.x - pos.x; 283 | const offsetY = pos.from.y - pos.y; 284 | const direction = DIRECTION_FROM_OFFSET[offsetX + ',' + offsetY]; 285 | 286 | if (pos.from.underground && pos.from.underground.length != MAX_UNDERGROUND_REACH && shouldUnderground) { 287 | pos.from.underground.push(pos); 288 | pos.underground = pos.from.underground; 289 | } else if (shouldUnderground && (!pos.from.underground || pos.from.underground.length == MAX_UNDERGROUND_REACH)) { 290 | pos.underground = [pos]; 291 | pos.underground.direction = direction; 292 | straights.push(pos.underground); 293 | } 294 | } else if (sidePipes.length == 1 && sidePipes[0].x == pos.x - 1) { 295 | pos.underground = [pos]; 296 | pos.underground.direction = 2; 297 | straights.push(pos.underground); 298 | } 299 | 300 | SIDES.forEach(side => { 301 | const newPos = pos.clone().add(side); 302 | newPos.from = pos; 303 | stack.push(newPos); 304 | }); 305 | } 306 | 307 | straights.filter(s => s.length >= minLength).forEach(straight => { 308 | straight.forEach(pos => bp.findEntity(pos).remove()); 309 | bp.createEntity('pipe_to_ground', straight[0], straight.direction); 310 | bp.createEntity('pipe_to_ground', straight[straight.length - 1], (straight.direction + 4) % 8); 311 | }); 312 | } 313 | 314 | simplifyPipes(bp, target); 315 | 316 | const lowerX = bp.topLeft().x; 317 | let upperY = bp.bottomLeft().y; 318 | let lowerY = Math.min(bp.topLeft().y, INCLUDE_RADAR ? -3 : 0); 319 | 320 | let trainStopLocation = null; 321 | 322 | if (INCLUDE_TRAIN_STATION) { 323 | trainStopLocation = generateTrainStation(bp, 324 | { x: target.x + 3 + 3 * TANKS, y: target.y - 2 }, 325 | Math.max(bp.bottomRight().y, target.y + 2 + WAGONS * 7 + ((SINGLE_HEADED_TRAIN ? 0 : 1) * LOCOMOTIVES) * 7), 326 | { 327 | LOCOMOTIVES, 328 | TRACK_CONCRETE, 329 | SINGLE_HEADED_TRAIN, 330 | WALL_SPACE, 331 | WALL_THICKNESS, 332 | INCLUDE_RADAR, 333 | INCLUDE_LIGHTS 334 | }); 335 | lowerY = Math.min(bp.topLeft().y, INCLUDE_RADAR ? -3 : 0); 336 | //move the uppery if neccessary 337 | upperY = Math.max(bp.bottomLeft().y - 1 - WALL_SPACE - WALL_THICKNESS); 338 | const CONNECT_OFFSET = TANKS % 2 == 0 ? -2 : 0; // Target connects two lower depending on tank directions 339 | 340 | for (let i = 0; i < WAGONS; i++) { 341 | for (let j = 0; j < TANKS; j++) { 342 | const pos = { x: target.x + 1 + j * 3, y: target.y + CONNECT_OFFSET + i * 7 }; 343 | bp.createEntity('storage_tank', pos, ((TANKS - j) % 2 == 0) ^ FLIP_ALL ? 2 : 0, true); 344 | upperY = Math.max(upperY, pos.y + 3); // +3 for size of storage_tank 345 | if (INCLUDE_RADAR && i == 0 && j == TANKS - 1) { 346 | bp.createEntity('radar', { x: pos.x, y: pos.y - 3 }); 347 | for (let i = 0; i < 5; i++) { 348 | try { 349 | bp.createEntity('medium_electric_pole', { x: pos.x - 1, y: pos.y - 1 - i }); 350 | break; 351 | } catch (e) {} 352 | } 353 | } 354 | } 355 | } 356 | 357 | for (let i = 0; i < WAGONS; i++) { 358 | bp.createEntity('pump', { x: target.x + 1 + 3 * TANKS, y: target.y + CONNECT_OFFSET + 2 + i * 7 }, 2); 359 | bp.createEntity('medium_electric_pole', { x: target.x + 1 + 3 * TANKS, y: target.y + CONNECT_OFFSET + 2 + i * 7 + 1 }); 360 | } 361 | 362 | for (let i = 1; i < WAGONS; i++) { 363 | bp.createEntity('pipe_to_ground', { x: target.x + 3 * TANKS, y: target.y + CONNECT_OFFSET + 3 + (i - 1) * 7 }, 0); 364 | bp.createEntity('pipe_to_ground', { x: target.x + 3 * TANKS, y: target.y + CONNECT_OFFSET + 3 + (i - 1) * 7 + 2 }, 4); 365 | for (let j = 0; j < 3; j++) { 366 | bp.createEntity('pipe', { x: target.x + 3 * TANKS - j, y: target.y + CONNECT_OFFSET + 3 + (i - 1) * 7 + 3 }); 367 | } 368 | } 369 | } else { 370 | trainStopLocation = { x: target.x + 3 + 3 * TANKS, y: target.y - 2 }; 371 | } 372 | 373 | const upperX = bp.topRight().x + ADDITIONAL_SPACE; 374 | 375 | generateDefenses(bp, { lowerX, upperX, lowerY, upperY }, { 376 | TURRETS_ENABLED, 377 | TURRET_SPACING, 378 | USE_LASER_TURRETS, 379 | WALL_SPACE, 380 | WALL_THICKNESS, 381 | CONCRETE, 382 | BORDER_CONCRETE, 383 | INCLUDE_LIGHTS 384 | }); 385 | 386 | if (MODULE) { 387 | bp.entities.filter(ent => ent.name == PUMPJACK_NAME).forEach(ent => { 388 | ent.modules[MODULE] = 2; 389 | }); 390 | } 391 | 392 | bp.fixCenter(); 393 | 394 | fixRail(bp); 395 | 396 | if (FLIP_ALL) { 397 | bp.entities.forEach(e => { 398 | if (e.name == 'train_stop') { 399 | e.position.x = -e.position.x + e.size.x; 400 | return; 401 | } 402 | e.position.x = -e.position.x - e.size.x; 403 | if (e.name != PUMPJACK_NAME) { 404 | if (e.direction == 2 || e.direction == 6) { 405 | e.direction = e.direction == 2 ? 6 : 2; 406 | } 407 | } 408 | }); 409 | bp.tiles.forEach(t => { 410 | t.position.x = -t.position.x - 1; 411 | }); 412 | bp.fixCenter({ x: 1, y: 0 }); 413 | } 414 | 415 | const finalBp = new Blueprint(); 416 | 417 | finalBp.placeBlueprint(bp, { x: 0, y: 0 }, ROTATE_ALL); 418 | finalBp.name = NAME.replace('%pumpjacks%', finalBp.entities.filter(e => e.name == PUMPJACK_NAME).length); 419 | 420 | return finalBp.encode(); 421 | } 422 | -------------------------------------------------------------------------------- /tools/outpost.js: -------------------------------------------------------------------------------- 1 | const Blueprint = require('factorio-blueprint'); 2 | const generateDefenses = require('./lib/defenses'); 3 | const generateTrainStation = require('./lib/trainStation'); 4 | const fixRail = require('./lib/fixRail'); 5 | 6 | 7 | const BALANCER = { 8 | 1: '0eNqFjs0KgzAQhN9lzhGs2h/yKqUUbZeyoJuQrEWRvHsTe+mtl4VZZj6+DcM4kw8sCruBH04i7HVD5Jf0Y/np6gkWrDTBQPqpJFp8oBgrDb1E74JWA42KZMDypAX2kG4GJMrK9CXuYb3LPA0UcuEfy8C7mOdOikVG1gZrvqmQdxv7I2/wphD38qnrmmPdtufmktIHq6tMlw==', 9 | 2: '0eNp1jsEKg0AQQ/8l57VoUaHzK6UUbYcyoOOyOxZF9t/r2ksvPSYkL9nQDzP7IGqgDfKYNIKuG6K8tBuyZ6tnEMR4hIN2Y1a8+MAxFtEPYsYByUH0yQuoSjcHVhMT/rIOsd51Hvs9SdV/ioOf4l6cNC/vsKI8NQ4rqEwZe5ygn88Obw7xyLd1fa4v5blt2pQ+qhFIlw==', 10 | 4: '0eNqdlt1uwyAMhd/F13TChKRtXmWapv6gCiklCMjUqsq7jzTqVq1ul/gKkYSPg4+Nc4Ft0xkfrEtQX8DuWhehfr9AtAe3aYZn6ewN1GCTOYIAtzkOM3PywcS4SGHjom9DWmxNk6AXYN3enKDGXkyGRN/YlEy4W676DwHGJZusGQVdJ+dP1x23+csa/5MiwLcxL2/dsH9GLpSAcx6KfpD2B6eei3oE4Vs5ojSFKh5QXT5TOIQ2j8+04U2buIWq7ZLvhog+bKAZG8gZ/HJGLOTLUFSzTcIXHi3ZlisKt5pj+c85kUKt5yvDmzIBexvMbnxXEXCUs+mShCsKjlyLyJii4nokJ2gtuHDSNNRc1ySJK/l1j79lad2TqsSKmQa02iX7EpkkdsVMKzmhHtZMNpkFSnKTiiwAhZxGQrckxbmgaFUF40qnRWlm8LOu3NGv/b++++cQ8GVCHL3WWum1VFVZ9f032HrobA==', 11 | 6: '0eJy1mt1um0AQRl8l2mtcAfuTllepqshOVikSBrSso1gW714wuGrUsYHZ/a6iGNscfzs+DOO9iEN1sq0ray+Kiyhfm7oTxc+L6Mr3el+Nj/lza0UhSm+PIhH1/jj+Zz9bZ7tu592+7trG+d3BVl70iSjrN/spiqxPwt8k738lwta+9KWdsB6/MhFt0w1PburxnOMbJOIsip0a3vKtdPZ1OqSSvzx1expfNnzsejo6nObSXx/wrqleDvb3/qNs3PToFeX8Up+OB+vmz7iRKCOJ8khEOYMohRJJBtFuDkl/RUojISkOUg5F0gRS11al98NRIp9vmly0WDiGk5CEJvTMQVJQpO/bFk2CF+0HJyENTShLOUwGy0RZ+8GyafCyZRxnzzbKvyKZWEgcac9IEnSp5Ug7JVOKtnDbpD1f0wZ1gxaNkvaDuk5vdS1B8bCMTVdRNCZK2WuZUGVEefs09L/u3TXD34Ue6S5Vc/JBnSRL3YqEitbdchruW18CWr6co+8bE6jMc8rfK0pKLVCFlhSr8dbYkqIkviKqJXeGRsXqwA22qlhCN9hvH0fos88zzHU45/ThE1KKCUlSKl+u8RQak+S4PKViikbEMXkGJWINT8haioYUMjzJQPVNOXztjAlV3iyBk9UULSaWvyW0mjaOUOYGJUXdQ0lmLy4XaimwG1AhYxTQl06xenGDLHDFasU1ssAVy+Aau3IchU8GRxFxBD4RgfpKxdF3Bs2IY++MyijWrZPaJu+pd/tv/BWNhjX+hgakWdom6zoaEmXtFRe4ha9/4PVNU+JePQLL7kUVSsVyt4QuH6Xu1YMdkCt10Ajl7uoFQlECXw2FSipggoLKiTURN9CUmE345CjQ8MvwZiizolBQG3/KvG0bQO0aCBiFoxKiHP7oBnNOCIVD+XvFlgHUgrEGJwaa0MZfMTU4oYAfMUH7FwzL2BkUidV7KyTSM6v3llCkgIkJCilgYoJCkuOez+s+0eKfvamJ+LCuu52p2g9ow3Hz5JsnI/r+D4x8yxE=', 12 | 8: '0eNqlmd2O2jAQhV9l5euk8n8SXqVaVbBrrSxBEiXOahHKu9cpKW13h2CfXiEC/pg59hyPzYUdjpPrB98Gtrsw/9K1I9t9v7DRv7X74/IsnHvHduzdD2GKTwrW7k/Lg+s3yprNBfPtq/tgOzEX4Eg5PxfMtcEH764B/Hpz/tFOp4MbIvo22n30gxvHMgz7duy7IZQHdwyR3ndjHN61y09HZCkLdo4vZl6i+oST+Ti1gVNfcGN/9CHEz4i4vpkrSlMonR0Z3wjM5OcpNnA2J0++mWeVHdnWfNbZtK00m4w0xWaWgmcHpjcCE/llsLVshcxIVG0nquCKUiRPwzxJ8r5WwhStZ3gbuvj6wEJkNKrVzLop9FNg1C9kFcdt2dDZV6gL0MnXKI6OrkG0FBlaSo7WMxmwFCiOlFNK1LuW5F/94F6uH1kKrgBx5e/kU7TVqIekRG9Qv6OVtiiOXgcVoK3K0Ta/0MrVB8S/4kqK3qB0TjYt+UVW6hX3cCEogXiEprW4Ke/bO8IrvKETpDYK7jcTJlJppF+kJ9GA1s4TwrRw95giArzH0TOGtoApQuQ0hOsyiN3vY7DmQJWIB0Vy35403D6SkmuJujONUyCOLAyNbnMJ60Eb1DnpxC2KIzdNXaHelbDB6xo1WjrWBsWRShqOpk7jkE2Mr7yEejQStMCEiTIKNe8UuMZPAJ98y6boZMCGWySkYsF9I0WmCrb3uyrdbYFMDR8c/jqT3cc3oJ8mzIFFb0lS2AJkk35lJXyCSBHZKnQnIM87Fr5EUQnC/seNikrRwiJNMnk5ZSvYx5MirXE3TOI3yLUrfeuKXqyQ66sSOX3xGhdNksCVK50h2kXScWnghpSOy6CtCXl1W1m0tGkcehijaTXae9C4Bly09F8FHGwmaBq6vdA0CW6ykfZcMB/cKQ7985dewY77OCw+q59C91THB+9uGK+errU0UnGl6nn+CfOVfEw=' 13 | }; 14 | 15 | 16 | 17 | 18 | const DIRECTIONS = { 19 | up: 0, 20 | right: 1, 21 | down: 2, 22 | left: 3 23 | }; 24 | 25 | function useOrDefault(value, def) { 26 | return isNaN(parseInt(value)) || value == undefined ? def : parseInt(value); 27 | } 28 | 29 | function isUpwardsFacingBelt(ent) { 30 | const names = ['transport_belt', 'splitter', 'underground_belt']; 31 | let nameOk = false; 32 | names.forEach(function(name) { 33 | if (ent.name.indexOf(name) > -1) { 34 | nameOk = true; 35 | } 36 | }); 37 | 38 | return nameOk && ent.direction == Blueprint.UP && (!ent.directionType || ent.directionType == 'input'); 39 | } 40 | 41 | //places medium poles along x axis in between given posistions to connect medium poles at (x1, y) and (x2, y) 42 | function connectPoles_x(bp, x1, x2, y) { 43 | const POLE_REACH = 8; 44 | const d = Math.abs(x1 - x2); 45 | const xmin = Math.min(x1, x2); 46 | const xmax = Math.max(x1, x2); 47 | if (d <= POLE_REACH) 48 | return; 49 | 50 | //try to place a pole as far from xmax as possible 51 | for (let i = xmax - POLE_REACH; i <= xmax - 1; i++) { 52 | const pos = { x: i, y: y }; 53 | if (bp.findEntity(pos) == null) { 54 | bp.createEntity('medium_electric_pole', pos); 55 | return connectPoles_x(bp, xmin, i, y); 56 | } 57 | } 58 | } 59 | 60 | 61 | module.exports = function(string, opt = {}) { 62 | 63 | const NAME = opt.name || 'Ore Outpost - %drills% Drills'; 64 | 65 | // Directions 66 | const TRAIN_DIRECTION = useOrDefault(opt.trainDirection, 2); 67 | const TRAIN_SIDE = useOrDefault(opt.trainSide, 1); 68 | 69 | // General 70 | const SPACE_BETWEEN_MINERS = useOrDefault(opt.minerSpace, 1); 71 | const MINING_DRILL_NAME = opt.miningDrillName || 'electric_mining_drill'; 72 | const MODULE = opt.module; 73 | const USE_STACKER_INSERTER = opt.useStackInserters != undefined ? !!opt.useStackInserters : true; 74 | const USE_FILTER_INSERTER = opt.useFilterInserters != undefined ? !!opt.useFilterInserters : true; 75 | const INCLUDE_RADAR = opt.includeRadar != undefined ? opt.includeRadar : true; 76 | const INCLUDE_LIGHTS = opt.includeLights != undefined ? opt.includeLights : false; 77 | const MINER_SIZE = useOrDefault(opt.minerSize, 3); 78 | 79 | // Defenses 80 | const TURRETS_ENABLED = opt.turrets != undefined ? opt.turrets : true; 81 | const TURRET_SPACING = useOrDefault(opt.turretSpacing, 8); 82 | const USE_LASER_TURRETS = opt.laserTurrets == undefined ? true : !!opt.laserTurrets; 83 | 84 | const WALLS_ENABLED = opt.walls != undefined ? !!opt.walls : true; 85 | const WALL_SPACE = useOrDefault(opt.wallSpace, 5); 86 | const WALL_THICKNESS = WALLS_ENABLED ? useOrDefault(opt.wallThickness, 1) : 0; 87 | 88 | // Trains 89 | const LOAD_FROM_BOTH_SIDES = opt.doubleLoading; 90 | const INCLUDE_TRAIN_STATION = opt.includeTrainStation != undefined ? opt.includeTrainStation : true; 91 | const LOCOMOTIVES = useOrDefault(opt.locomotiveCount, 2); 92 | const FINAL_LANES = useOrDefault(opt.cargoWagonCount, 4) * (LOAD_FROM_BOTH_SIDES ? 2 : 1); 93 | const LOADING_BAYS = useOrDefault(opt.cargoWagonCount, 4); 94 | const SINGLE_HEADED_TRAIN = opt.exitRoute || false; 95 | const ADDITIONAL_SPACE = useOrDefault(opt.addtionalStationSpace, 0); 96 | 97 | // Bot info 98 | const BOT_BASED = opt.botBased || false; 99 | const REQUEST_TYPE = opt.requestItem || 'iron_ore'; 100 | const REQUEST_AMOUNT = useOrDefault(opt.requestAmount, 4800); 101 | const ROBOPORTS = opt.roboports || false; 102 | 103 | // Tiles 104 | const CONCRETE = opt.concrete || ''; 105 | const BORDER_CONCRETE = opt.borderConcrete || ''; 106 | const TRACK_CONCRETE = opt.trackConcrete || ''; 107 | 108 | // Belt shenanigans 109 | const UNDERGROUND_BELT = !!opt.undergroundBelts || false; 110 | const COMPACT = (UNDERGROUND_BELT && !!opt.compact) || false; 111 | 112 | let BELT_NAME = (opt.beltName || '').replace('transport_belt', ''); 113 | if (BELT_NAME.length > 0 && BELT_NAME[BELT_NAME.length - 1] != '_') BELT_NAME += '_'; 114 | 115 | const newEntityData = {}; 116 | newEntityData[REQUEST_TYPE] = { type: 'item' }; 117 | newEntityData[BELT_NAME + 'transport_belt'] = { type: 'item', width: 1, height: 1 }; 118 | newEntityData[BELT_NAME + 'splitter'] = { type: 'item', width: 2, height: 1 }; 119 | newEntityData[BELT_NAME + 'underground_belt'] = { type: 'item', width: 1, height: 1, directionType: true }; 120 | 121 | newEntityData[MINING_DRILL_NAME] = { type: 'item', width: MINER_SIZE, height: MINER_SIZE }; 122 | 123 | if (CONCRETE) newEntityData[CONCRETE] = { type: 'tile' } 124 | if (BORDER_CONCRETE && !newEntityData[BORDER_CONCRETE]) newEntityData[BORDER_CONCRETE] = { type: 'tile' } 125 | if (TRACK_CONCRETE && !newEntityData[TRACK_CONCRETE]) newEntityData[TRACK_CONCRETE] = { type: 'tile' } 126 | Blueprint.setEntityData(newEntityData); 127 | 128 | const PROVIDED_BALANCER = opt.balancer; 129 | 130 | if (!PROVIDED_BALANCER && !BALANCER[FINAL_LANES] && !BOT_BASED) { 131 | throw new Error('I don\'t have a ' + FINAL_LANES + 'x' + FINAL_LANES + 132 | ' balancer available, so you must provide the blueprint string for one. Use express belt and have it face upwards.'); 133 | } 134 | 135 | 136 | 137 | if (Math.abs(TRAIN_DIRECTION - TRAIN_SIDE) % 2 == 0 && !BOT_BASED) { 138 | throw new Error('trainSide direction must be perpendicular to trainDirection.'); 139 | } 140 | 141 | let templateBlueprint = null; 142 | 143 | try { 144 | templateBlueprint = new Blueprint(string); 145 | } catch (e) { 146 | throw new Error('Error loading blueprint: ' + e.message); 147 | } 148 | 149 | if (templateBlueprint.entities.length < 2 || templateBlueprint.entities.some(e => e.name !== 'stone_wall')) { 150 | throw new Error('Blueprint must contain only walls and must contain at least two.'); 151 | } 152 | 153 | const topLeft = { 154 | x: Math.min(...templateBlueprint.entities.map(e => e.position.x)), 155 | y: Math.min(...templateBlueprint.entities.map(e => e.position.y)), 156 | }; 157 | 158 | const bottomRight = { 159 | x: Math.max(...templateBlueprint.entities.map(e => e.position.x)), 160 | y: Math.max(...templateBlueprint.entities.map(e => e.position.y)), 161 | }; 162 | 163 | if (bottomRight.x - topLeft.x < 3 || bottomRight.y - topLeft.y < 3) { 164 | throw new Error('Template size must be at least 5x5!'); 165 | } 166 | 167 | const size = { 168 | x: (TRAIN_DIRECTION % 2 == 0 ? bottomRight.x - topLeft.x : bottomRight.y - topLeft.y), 169 | y: (TRAIN_DIRECTION % 2 == 1 ? bottomRight.x - topLeft.x : bottomRight.y - topLeft.y) 170 | }; 171 | 172 | const bp = new Blueprint(); 173 | 174 | const Y_SIZE = (MINER_SIZE + SPACE_BETWEEN_MINERS) * 2; 175 | const X_SIZE = MINER_SIZE * 2 + (COMPACT ? 1 : 2); 176 | 177 | let balancerBlueprint = null; 178 | 179 | if (!BOT_BASED) { 180 | balancerBlueprint = new Blueprint(PROVIDED_BALANCER || BALANCER[FINAL_LANES]); 181 | 182 | balancerBlueprint.entities.forEach(ent => { 183 | if (ent.name.includes('transport_belt')) ent.name = BELT_NAME + 'transport_belt'; 184 | else if (ent.name.includes('underground_belt')) ent.name = BELT_NAME + 'underground_belt'; 185 | else if (ent.name.includes('splitter')) ent.name = BELT_NAME + 'splitter'; 186 | }); 187 | 188 | const balancerBL = balancerBlueprint.bottomLeft(); 189 | const balancerTR = balancerBlueprint.topRight(); 190 | const balancerHeight = Math.abs(balancerTR.y - balancerBL.y); 191 | const balancerWidth = Math.abs(balancerTR.x - balancerBL.x); 192 | 193 | let balancerOffsetX = 0; 194 | let balancerOffsetY = 0; 195 | 196 | //there seems to be a problem with blueprint orientation, so get min and max manually; 197 | const xmin = Math.min(balancerBL.x, balancerTR.x); 198 | const ymax = Math.max(balancerBL.y, balancerTR.y) - 1; 199 | 200 | 201 | //since some balancers may have non-rectangular form, we find leftmost upwards-facing occupied tile in the bottom row, 202 | //and assume that it is the leftmost balancer input. 203 | for (let i = 0; i < balancerWidth; i++) { 204 | const pos = { x: xmin + i, y: ymax }; 205 | let ent = balancerBlueprint.findEntity(pos); 206 | if (ent != null && isUpwardsFacingBelt(ent)) { 207 | balancerOffsetX = -i; 208 | break; 209 | } 210 | } 211 | 212 | balancerBlueprint.fixCenter(balancerBlueprint.bottomLeft().subtract({ x: balancerOffsetX, y: balancerOffsetY })); 213 | } 214 | 215 | const X_LENGTH = Math.ceil(size.x / X_SIZE); 216 | const Y_LENGTH = Math.ceil(size.y / Y_SIZE); 217 | 218 | let locationForBalancer = null; 219 | 220 | if (INCLUDE_RADAR) { 221 | bp.createEntity('radar', { x: 0, y: -3 }); 222 | if(INCLUDE_LIGHTS) 223 | bp.createEntity('small_lamp', { x: -1, y: -3 }); 224 | bp.createEntity('medium_electric_pole', {x: -1, y: -2}); 225 | } 226 | 227 | const SPLITTER_ON_LAST = Math.floor((X_LENGTH - 2) * FINAL_LANES / X_LENGTH) == Math.floor((X_LENGTH - 1) * FINAL_LANES / X_LENGTH) 228 | 229 | // Place miners, belts, and merger splitters 230 | for (let x = 0; x < X_LENGTH; x++) { 231 | const OFFSET_X = x * X_SIZE; 232 | for (let y = 0; y < Y_LENGTH; y++) { 233 | let OFFSET_Y = y * Y_SIZE; 234 | 235 | const miningDrillEntities = []; 236 | 237 | miningDrillEntities.push(bp.createEntity(MINING_DRILL_NAME, { x: OFFSET_X, y: OFFSET_Y }, Blueprint.RIGHT)); 238 | miningDrillEntities.push(bp.createEntity(MINING_DRILL_NAME, { x: OFFSET_X, y: OFFSET_Y + MINER_SIZE + SPACE_BETWEEN_MINERS }, Blueprint.RIGHT)); 239 | miningDrillEntities.push(bp.createEntity(MINING_DRILL_NAME, { x: OFFSET_X + MINER_SIZE + 1, y: OFFSET_Y }, Blueprint.LEFT)); 240 | miningDrillEntities.push(bp.createEntity(MINING_DRILL_NAME, { x: OFFSET_X + MINER_SIZE + 1, y: OFFSET_Y + MINER_SIZE + SPACE_BETWEEN_MINERS }, 241 | Blueprint.LEFT, true)); 242 | 243 | const NEED_ADDITIONAL_POLES = x == 0 && SPACE_BETWEEN_MINERS >= 2; 244 | if (!COMPACT) { 245 | const pos = { x: OFFSET_X - 1, y: OFFSET_Y + MINER_SIZE }; 246 | bp.createEntity('medium_electric_pole', pos); 247 | if (INCLUDE_LIGHTS) { 248 | const lpos = { x: OFFSET_X - 1, y: OFFSET_Y + MINER_SIZE + 1 }; 249 | bp.createEntity('small_lamp', lpos); 250 | } 251 | if (NEED_ADDITIONAL_POLES) { 252 | bp.createEntity('medium_electric_pole', { x: pos.x, y: pos.y + 5 }); 253 | if (INCLUDE_LIGHTS) { 254 | bp.createEntity('small_lamp', { x: pos.x, y: pos.y + 6 }); 255 | } 256 | } 257 | } else { 258 | const pos = { x: OFFSET_X + 3, y: OFFSET_Y + MINER_SIZE - 1 }; 259 | bp.createEntity('medium_electric_pole', pos); 260 | if (INCLUDE_LIGHTS) { 261 | const lpos = { x: OFFSET_X + 3, y: OFFSET_Y + MINER_SIZE }; 262 | bp.createEntity('small_lamp', lpos); 263 | } 264 | if (NEED_ADDITIONAL_POLES) { 265 | bp.createEntity('medium_electric_pole', { x: pos.x, y: pos.y + 5 }); 266 | if (INCLUDE_LIGHTS) { 267 | bp.createEntity('small_lamp', { x: pos.x, y: pos.y + 6 }); 268 | } 269 | } 270 | } 271 | 272 | if (MODULE) { 273 | miningDrillEntities.forEach(ent => { 274 | ent.modules[MODULE] = 3; 275 | }); 276 | } 277 | 278 | if (x == X_LENGTH - 1) { 279 | bp.createEntity('medium_electric_pole', { x: OFFSET_X + MINER_SIZE*2 + 1, y: OFFSET_Y + MINER_SIZE }); 280 | if (INCLUDE_LIGHTS) 281 | bp.createEntity('small_lamp', { x: OFFSET_X + MINER_SIZE*2 + 1, y: OFFSET_Y + MINER_SIZE + 1}); 282 | } 283 | 284 | if (!BOT_BASED) { 285 | const IS_LAST = y == Y_LENGTH - 1; 286 | if (!UNDERGROUND_BELT) { 287 | for (let i = 0; i < Y_SIZE - (IS_LAST ? SPACE_BETWEEN_MINERS + 1 : 0); i++) { 288 | bp.createEntity(BELT_NAME + 'transport_belt', { x: OFFSET_X + MINER_SIZE, y: OFFSET_Y + i + 1 }, Blueprint.DOWN); 289 | } 290 | } else { 291 | for (let i = 0; i < 2; i++) { 292 | const secondaryOffset = i * (SPACE_BETWEEN_MINERS + MINER_SIZE); 293 | const lastOffset = IS_LAST && i == 1 ? -1 - SPACE_BETWEEN_MINERS : 0; 294 | bp.createEntity(BELT_NAME + 'underground_belt', { x: OFFSET_X + MINER_SIZE, y: OFFSET_Y + 1 + secondaryOffset }, Blueprint.DOWN) 295 | .setDirectionType('input'); 296 | bp.createEntity(BELT_NAME + 'underground_belt', { x: OFFSET_X + MINER_SIZE, y: OFFSET_Y + SPACE_BETWEEN_MINERS + MINER_SIZE + 297 | secondaryOffset + lastOffset }, Blueprint.DOWN) 298 | .setDirectionType('output'); 299 | } 300 | } 301 | } else { 302 | for (let i = 0; i < 2; i++) { 303 | bp.createEntity('logistic_chest_passive_provider', { x: OFFSET_X + MINER_SIZE, y: OFFSET_Y + 1 + i * (SPACE_BETWEEN_MINERS + MINER_SIZE) }) 304 | .setBar(1); 305 | } 306 | } 307 | } 308 | let distanceOut = X_LENGTH - x - 1; 309 | 310 | const connectWithSplitter = Math.floor(x * FINAL_LANES / X_LENGTH) == Math.floor((x + 1) * FINAL_LANES / X_LENGTH); 311 | const finalLane = FINAL_LANES >= X_LENGTH ? X_LENGTH - x - 1 : FINAL_LANES - Math.floor(x * FINAL_LANES / X_LENGTH) - 1; 312 | 313 | for (let i = 0; i < distanceOut; i++) { // Go out, before going across 314 | const xPosition = OFFSET_X + MINER_SIZE; 315 | const yPosition = Y_LENGTH * Y_SIZE + i - SPACE_BETWEEN_MINERS; 316 | if (!BOT_BASED) bp.createEntity(BELT_NAME + 'transport_belt', { x: xPosition, y: yPosition }, Blueprint.DOWN); 317 | } 318 | const cutInEarly = distanceOut == 0 ? 0 : X_SIZE - finalLane; 319 | const acrossDistance = (connectWithSplitter ? X_SIZE : distanceOut * X_SIZE - cutInEarly) + 2; // Go across (either to hit splitter or go to balancer) 320 | 321 | distanceOut--; // Not going out as far to prevent belt collision and keep compact 322 | 323 | const OFFSET_Y = Y_LENGTH * Y_SIZE + distanceOut - (SPACE_BETWEEN_MINERS - 1); 324 | 325 | for (let i = 0; i < acrossDistance; i++) { 326 | const xPosition = OFFSET_X + MINER_SIZE + i; // Just getting the sign from direction data's x/y 327 | const yPosition = OFFSET_Y; 328 | if (!bp.findEntity({ x: xPosition, y: yPosition })) { 329 | if (!BOT_BASED) bp.createEntity(BELT_NAME + 'transport_belt', { x: xPosition, y: yPosition }, Blueprint.RIGHT); 330 | 331 | if (distanceOut == 0) locationForBalancer = { x: xPosition, y: yPosition }; 332 | } 333 | } 334 | if (connectWithSplitter) { // Generate spliiter 335 | const xPosition = OFFSET_X + MINER_SIZE + X_SIZE + 1; 336 | const yPosition = OFFSET_Y - 1; 337 | bp.removeEntityAtPosition({ x: xPosition, y: yPosition }); // Remove belts at splitter 338 | bp.removeEntityAtPosition({ x: xPosition, y: yPosition + 1 }); 339 | if (!BOT_BASED) bp.createEntity(BELT_NAME + 'splitter', { x: xPosition, y: yPosition }, Blueprint.RIGHT); 340 | } else { // Generate "lowering" to meet other belts 341 | const offsetBecauseSplitterOnLast = SPLITTER_ON_LAST ? 0 : 1; 342 | for (let i = 0; i < distanceOut - finalLane + offsetBecauseSplitterOnLast; i++) { 343 | const xPosition = OFFSET_X + MINER_SIZE + acrossDistance; 344 | const yPosition = OFFSET_Y - i; 345 | if (!BOT_BASED) bp.createEntity(BELT_NAME + 'transport_belt', { x: xPosition, y: yPosition }, Blueprint.UP); 346 | } 347 | 348 | for (let i = 0; i < cutInEarly + ((X_SIZE - 1) / 2); i++) { 349 | const xPosition = OFFSET_X + MINER_SIZE + acrossDistance + i; 350 | const yPosition = OFFSET_Y - distanceOut + finalLane - offsetBecauseSplitterOnLast; 351 | if (!BOT_BASED) bp.createEntity(BELT_NAME + 'transport_belt', { x: xPosition, y: yPosition }, Blueprint.RIGHT, true); 352 | 353 | if (distanceOut == -1) locationForBalancer = { x: xPosition, y: yPosition }; 354 | } 355 | } 356 | } 357 | 358 | // Place balancer 359 | if (!BOT_BASED) bp.placeBlueprint(balancerBlueprint, locationForBalancer, Blueprint.RIGHT / 2, true); 360 | 361 | let trainStopLocation = null; 362 | 363 | // Generate lanes to cargo wagons, track, and train stop 364 | if (INCLUDE_TRAIN_STATION) { 365 | let RAIL_X = null; 366 | 367 | for (let l = 0; l < LOADING_BAYS; l++) { 368 | let OFFSET_Y = locationForBalancer.y + l; 369 | let OFFSET_X = locationForBalancer.x + (!BOT_BASED ? balancerBlueprint.bottomLeft().y - balancerBlueprint.topLeft().y + 1 : 2); 370 | const START_TO_CARGO = OFFSET_Y; 371 | 372 | if (l == 0 && LOAD_FROM_BOTH_SIDES && !BOT_BASED) { 373 | 374 | for (let i = 0; i < LOADING_BAYS; i++) { 375 | for (let j = 0; j < LOADING_BAYS + 1; j++) { 376 | bp.createEntity(BELT_NAME + 'transport_belt', { x: OFFSET_X + j, y: OFFSET_Y + i + LOADING_BAYS }, Blueprint.RIGHT); 377 | } 378 | bp.createEntity(BELT_NAME + 'underground_belt', { x: OFFSET_X + LOADING_BAYS + 1, y: OFFSET_Y + i + LOADING_BAYS }, Blueprint.RIGHT).setDirectionType( 379 | 'input'); 380 | bp.createEntity(BELT_NAME + 'underground_belt', { x: OFFSET_X + LOADING_BAYS + 6, y: OFFSET_Y + i + LOADING_BAYS }, Blueprint.RIGHT).setDirectionType( 381 | 'output'); 382 | bp.createEntity(BELT_NAME + 'transport_belt', { x: OFFSET_X + LOADING_BAYS + 7, y: OFFSET_Y + i + LOADING_BAYS }, Blueprint.RIGHT); 383 | } 384 | } 385 | for (let i = 0; i < l; i++) { 386 | const xPosition = OFFSET_X + i; 387 | const yPosition = OFFSET_Y; 388 | if (!BOT_BASED) bp.createEntity(BELT_NAME + 'transport_belt', { x: xPosition, y: yPosition }, Blueprint.RIGHT); 389 | if (!BOT_BASED && LOAD_FROM_BOTH_SIDES) bp.createEntity(BELT_NAME + 'transport_belt', { x: xPosition + 8 + LOADING_BAYS, y: yPosition + 390 | LOADING_BAYS }, Blueprint.RIGHT); 391 | } 392 | OFFSET_X += l; 393 | 394 | const distanceToCargoWagon = (LOADING_BAYS - l - 1) * 6; 395 | for (let i = 0; i < distanceToCargoWagon; i++) { 396 | const xPosition = OFFSET_X; 397 | const yPosition = OFFSET_Y - i; 398 | if (!BOT_BASED) bp.createEntity(BELT_NAME + 'transport_belt', { x: xPosition, y: yPosition }, Blueprint.UP); 399 | 400 | } 401 | if (!BOT_BASED && LOAD_FROM_BOTH_SIDES) { 402 | const distanceToCargoWagon = (l) * 8 + 1; 403 | for (let i = 0; i < distanceToCargoWagon; i++) { 404 | const xPosition = OFFSET_X; 405 | const yPosition = OFFSET_Y - i + LOADING_BAYS; 406 | bp.createEntity(BELT_NAME + 'transport_belt', { x: xPosition + 8 + LOADING_BAYS, y: yPosition }, Blueprint.UP); 407 | } 408 | } 409 | OFFSET_Y -= distanceToCargoWagon; 410 | for (let i = 0; i < LOADING_BAYS - l - 1; i++) { 411 | const xPosition = OFFSET_X + i; 412 | const yPosition = OFFSET_Y; 413 | if (!BOT_BASED) bp.createEntity(BELT_NAME + 'transport_belt', { x: xPosition, y: yPosition }, Blueprint.RIGHT); 414 | } 415 | 416 | if (!BOT_BASED && LOAD_FROM_BOTH_SIDES) { 417 | for (let i = 0; i < LOADING_BAYS - l - 1; i++) { 418 | const xPosition = OFFSET_X - l + i; 419 | const yPosition = OFFSET_Y; 420 | bp.createEntity(BELT_NAME + 'transport_belt', { x: xPosition + LOADING_BAYS + 9, y: yPosition }, Blueprint.LEFT); 421 | } 422 | 423 | 424 | 425 | } 426 | OFFSET_X += LOADING_BAYS - l - 1; 427 | const inserterType = USE_STACKER_INSERTER ? (USE_FILTER_INSERTER ? 'stack_filter_inserter' : 'stack_inserter') : (USE_FILTER_INSERTER ? 428 | 'filter_inserter' : 'fast_inserter'); 429 | for (let i = 0; i < 6; i++) { 430 | const xPosition = OFFSET_X; 431 | const yPosition = OFFSET_Y - i; 432 | if (!BOT_BASED) bp.createEntity(BELT_NAME + 'transport_belt', { x: xPosition, y: yPosition }, Blueprint.UP); 433 | if (!BOT_BASED && LOAD_FROM_BOTH_SIDES) bp.createEntity(BELT_NAME + 'transport_belt', { x: xPosition + 9, y: yPosition }, Blueprint.UP); 434 | 435 | if (i == 0 && l == LOADING_BAYS - 1) { 436 | bp.createEntity('medium_electric_pole', { x: xPosition + 3, y: yPosition + 1 }); 437 | if (INCLUDE_LIGHTS) 438 | try{ 439 | bp.createEntity('small_lamp', { x: xPosition + 2, y: yPosition + 1 }); 440 | }catch(e){ 441 | bp.createEntity('small_lamp', { x: xPosition + 3, y: yPosition + 2 }); 442 | } 443 | 444 | if (LOAD_FROM_BOTH_SIDES) { 445 | bp.createEntity('medium_electric_pole', { x: xPosition + 6, y: yPosition + 1 }); 446 | if (INCLUDE_LIGHTS) 447 | try{ 448 | bp.createEntity('small_lamp', { x: xPosition + 7, y: yPosition + 1 }); 449 | }catch(e){ 450 | bp.createEntity('small_lamp', { x: xPosition + 6, y: yPosition + 2 }); 451 | } 452 | 453 | } 454 | } else if (i == 5) { 455 | bp.createEntity('medium_electric_pole', { x: xPosition + 3, y: yPosition - 1 }); 456 | if (INCLUDE_LIGHTS) 457 | bp.createEntity('small_lamp', { x: xPosition + 2, y: yPosition - 1 }); 458 | if (LOAD_FROM_BOTH_SIDES) { 459 | bp.createEntity('medium_electric_pole', { x: xPosition + 6, y: yPosition - 1 }); 460 | if (INCLUDE_LIGHTS) 461 | bp.createEntity('small_lamp', { x: xPosition + 7, y: yPosition - 1 }); 462 | } 463 | } 464 | if (!BOT_BASED) bp.createEntity('fast_inserter', { x: xPosition + 1, y: yPosition }, Blueprint.LEFT); // Grab FROM left 465 | if (!BOT_BASED && LOAD_FROM_BOTH_SIDES) bp.createEntity('fast_inserter', { x: xPosition + 8, y: yPosition }, Blueprint.RIGHT); 466 | 467 | if (!BOT_BASED) bp.createEntity('steel_chest', { x: xPosition + 2, y: yPosition }); 468 | else bp.createEntity('logistic_chest_requester', { x: xPosition + 2, y: yPosition }) 469 | .setRequestFilter(1, REQUEST_TYPE, REQUEST_AMOUNT); 470 | if (LOAD_FROM_BOTH_SIDES) { 471 | if (!BOT_BASED) bp.createEntity('steel_chest', { x: xPosition + 7, y: yPosition }); 472 | else bp.createEntity('logistic_chest_requester', { x: xPosition + 7, y: yPosition }) 473 | .setRequestFilter(1, REQUEST_TYPE, REQUEST_AMOUNT); 474 | } 475 | bp.createEntity(inserterType, { x: xPosition + 3, y: yPosition }, Blueprint.LEFT); 476 | if (LOAD_FROM_BOTH_SIDES) bp.createEntity(inserterType, { x: xPosition + 6, y: yPosition }, Blueprint.RIGHT); 477 | } 478 | OFFSET_Y -= 6; 479 | OFFSET_X += 4; 480 | 481 | if (l == 0) { 482 | RAIL_X = OFFSET_X; 483 | trainStopLocation = generateTrainStation(bp, { x: OFFSET_X, y: OFFSET_Y }, START_TO_CARGO + ((SINGLE_HEADED_TRAIN ? 0 : 1) * LOCOMOTIVES) + (LOADING_BAYS * (LOAD_FROM_BOTH_SIDES ? 2 : 1)), { 484 | LOCOMOTIVES, 485 | TRACK_CONCRETE, 486 | SINGLE_HEADED_TRAIN, 487 | WALL_SPACE, 488 | WALL_THICKNESS, 489 | INCLUDE_RADAR, 490 | INCLUDE_LIGHTS 491 | }); 492 | 493 | if (ROBOPORTS) { 494 | for (let i = 0; i < FINAL_LANES * 2; i++) { 495 | const xPosition = OFFSET_X + (LOAD_FROM_BOTH_SIDES ? 4 : 2); 496 | const yPosition = OFFSET_Y - Math.ceil(FINAL_LANES / 2); 497 | bp.createEntity('roboport', { x: xPosition, y: yPosition + i * 4 }); 498 | if (LOAD_FROM_BOTH_SIDES) bp.createEntity('roboport', { x: xPosition + 4, y: yPosition + i * 4 }); 499 | } 500 | } 501 | } 502 | } 503 | 504 | //place a pole aligned with miners grid to connect miners grid with train station 505 | const miners_pole_x = (X_LENGTH - 1) * X_SIZE + MINER_SIZE * 2 + 1; 506 | const miners_pole_y = (Y_LENGTH - 1) * Y_SIZE + MINER_SIZE; 507 | const station_pole_x = RAIL_X - 1; 508 | connectPoles_x(bp, miners_pole_x, station_pole_x + 1, miners_pole_y); 509 | 510 | } else { 511 | trainStopLocation = { x: locationForBalancer.x + (!BOT_BASED ? balancerBlueprint.bottomLeft().y - balancerBlueprint.topLeft().y + 1 : 2) + 512 | FINAL_LANES, y: locationForBalancer.y - 5 }; 513 | } 514 | 515 | // Place walls and laser turrets 516 | 517 | const lowerX = -2; 518 | const upperX = trainStopLocation.x + 2 + (ROBOPORTS ? 4 : 0) + (LOAD_FROM_BOTH_SIDES ? LOADING_BAYS +1 : 0) + ADDITIONAL_SPACE; 519 | 520 | const lowerY = Math.min(INCLUDE_RADAR ? -3 : 0, trainStopLocation.y - (SINGLE_HEADED_TRAIN ? Math.max(0, trainStopLocation.y) : 0)) - 1; 521 | const upperY = Y_LENGTH * Y_SIZE + Math.max(LOADING_BAYS, X_LENGTH) + (SINGLE_HEADED_TRAIN ? 0 : LOCOMOTIVES * 7) + (LOAD_FROM_BOTH_SIDES ? LOADING_BAYS : 0); 522 | 523 | generateDefenses(bp, { lowerX, upperX, lowerY, upperY }, { 524 | TURRETS_ENABLED, 525 | TURRET_SPACING, 526 | USE_LASER_TURRETS, 527 | WALL_SPACE, 528 | WALL_THICKNESS, 529 | CONCRETE, 530 | BORDER_CONCRETE, 531 | INCLUDE_LIGHTS 532 | }); // Pass in same opt, same names for defenses 533 | 534 | bp.fixCenter(); 535 | fixRail(bp); 536 | 537 | if (TRAIN_SIDE == TRAIN_DIRECTION + 1 || (TRAIN_SIDE == 0 && TRAIN_DIRECTION == 3)) { 538 | bp.entities.forEach(e => { 539 | if (e.name == 'train_stop') { 540 | e.position.x = -e.position.x + e.size.x; 541 | return; 542 | } 543 | e.position.x = -e.position.x - e.size.x; 544 | if (e.direction == 2 || e.direction == 6) { 545 | e.direction = e.direction == 2 ? 6 : 2; 546 | } 547 | }); 548 | bp.tiles.forEach(t => { 549 | t.position.x = -t.position.x - 1; 550 | }); 551 | bp.fixCenter({ x: 1, y: 0 }); 552 | } 553 | 554 | const finalBp = new Blueprint(); 555 | 556 | finalBp.placeBlueprint(bp, { x: 0, y: 0 }, (TRAIN_DIRECTION + 2) % 4, true); 557 | finalBp.name = NAME.replace('%drills%', finalBp.entities.filter(e => e.name == MINING_DRILL_NAME).length); 558 | 559 | return finalBp.encode(); 560 | } 561 | --------------------------------------------------------------------------------