├── .gitignore ├── README.md ├── constructionPlanner.js ├── countType.js ├── extend.js ├── factory.js ├── main.js ├── performRoles.js ├── roleManager.js ├── role_prototype.js ├── roles_archer.js ├── roles_builder.js ├── roles_guard.js ├── roles_harvester.js ├── roles_healer.js ├── roles_miner.js ├── roles_miner_helper.js ├── roles_scavenger.js ├── roles_transporter.js ├── spawner.js └── sync.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | atlassian-ide-plugin.xml 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Overview 2 | ======= 3 | This library includes a number of things, the main two of which are the are the **roles** functionality and the **spawner**. 4 | The spawner mainly uses what is known as a role to make human readable aliases to spawn off of, as well as containing some 5 | methods to help managing the spawner. The roles are a set of classes that are made to be sharable between projects, so if 6 | you want to share your particular body parts config, or the ai, for a role with someone else, it should be easy to simply 7 | plug and play. 8 | 9 | Spawner 10 | ======= 11 | The spawner module has 4 methods for use. These are 12 | 13 | - spawn(role, memory) 14 | - canSpawn(spawnPoint, role) 15 | - spawnCost(role) 16 | - killAll() 17 | 18 | The **role** argument here refers to a human readable name for the list of body parts and AI for a creep. The role directly 19 | relates to the AI for that role in the file roles_{roleName}. So if you spawn a creep with the role 20 | of "miner" it's AI code will be located in roles_miner.js 21 | 22 | Roles 23 | ===== 24 | To make things simple, I've split each role up into it's own file, containing it's list of parts (and sometimes a list an 25 | array of these lists). This means that it should be relatively easy to simply add a new role without making your code for 26 | running a turn any more complicated, as the rest of the code will simply take the new role into account. 27 | 28 | Each role in this codebase extends from the role_prototype.js code, for some shared functionality. This functionality includes 29 | some events which you can handle. So far the code supports handling the following events: 30 | 31 | - onSpawn() *This is called when the spawner has started spawning* 32 | - onSpawnStart() *This is called when the spawner has started spawning* 33 | - onSpawnEnd() *This is called when the creep has finished spawning* 34 | - beforeAge *This is called when the creep has one tick left* 35 | 36 | And the main AI code should go into the 37 | 38 | - performAction(creep) 39 | 40 | method. 41 | 42 | Levels 43 | ====== 44 | I believe one of the important features for late game screeps will be having different units depending on how much energy 45 | and how many extensions you have. What units you can build with only 5 parts won't really be all competitive when put 46 | up against what you can do with, say, 12 parts instead. For this reason, this framework supports the ability to have roles 47 | automatically adjust their body parts to take in to account what you're able to build. At the moment, this is implemented 48 | by creating an array of body parts. For example, a simple list of body parts for a creep might look like: 49 | 50 | ```javascript 51 | [Game.TOUGH, Game.TOUGH, Game.MOVE, Game.ATTACK, Game.ATTACK] 52 | ``` 53 | 54 | For a simple warrior. It contains 5 parts, the maximum you can build without any extensions. However, if you wanted to 55 | have your warriors to be built tougher if you have the extensions, you might define your parts list as such 56 | 57 | ```javascript 58 | [ 59 | [Game.TOUGH, Game.TOUGH, Game.MOVE, Game.ATTACK, Game.ATTACK], 60 | [Game.TOUGH, Game.TOUGH, Game.MOVE, Game.ATTACK, Game.ATTACK, Game.RANGED_ATTACK], 61 | [Game.TOUGH, Game.TOUGH, Game.MOVE, Game.ATTACK, Game.ATTACK, Game.RANGED_ATTACK, Game.HEAL], 62 | [Game.TOUGH, Game.TOUGH, Game.TOUGH, Game.MOVE, Game.ATTACK, Game.ATTACK, Game.RANGED_ATTACK, Game.HEAL] 63 | ] 64 | ``` 65 | 66 | And when the spawner attempts to spawn your warrior, if you have no extensions, it'll try to spawn the first one, if it 67 | has one extension with >= 200 energy, it'll hit up the second, if you have two, the third, and if you have four, it will 68 | try to spawn the last definition. This way, when you set up your scripts to make sure that when a warrior dies, he should be 69 | replaced, it will pick the definition that fits your needs without you having to add complex logic. 70 | 71 | Further more, you can see what kind of creep will be spawned with your current capabilities manually in the console. It's as simple 72 | as using the command 73 | 74 | ```javascript 75 | require('roleManager').getRoleBodyParts('warrior') 76 | ``` 77 | 78 | And it will send you a list of body parts that suit the current situation. The logic for selecting what level can also be 79 | overriden by changing the .prototype.getParts() function for a role. For example, if you wanted to select a random defintion 80 | for a warrior, instead of scaling it normally, in your roles_warrior.js file you could define 81 | 82 | ```javascript 83 | warrior.prototype.getParts = function() 84 | { 85 | var key = Math.floor(Math.random() * this.parts.length); 86 | return this.parts[key]; 87 | } 88 | ``` -------------------------------------------------------------------------------- /constructionPlanner.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | buildRoads: function(from, to) 3 | { 4 | var path = Game.getRoom('1-1').findPath(from, to, { ignoreCreeps: true }); 5 | for(var i in path) 6 | { 7 | var result = Game.getRoom('1-1').createConstructionSite(path[i].x, path[i].y, Game.STRUCTURE_ROAD); 8 | } 9 | }, 10 | 11 | buildRoadToAllSources: function() 12 | { 13 | var sources = Game.spawns.Spawn1.room.find(Game.SOURCES); 14 | 15 | for(var i in sources) 16 | { 17 | this.buildRoads(Game.spawns.Spawn1.pos, sources[i].pos); 18 | } 19 | }, 20 | 21 | expandRampartsOutwards: function() 22 | { 23 | var ramparts = Game.getRoom('1-1').find(Game.MY_STRUCTURES, { 24 | filter: function(struct) 25 | { 26 | return struct.structureType == Game.STRUCTURE_RAMPART 27 | } 28 | }); 29 | 30 | for(var i in ramparts) 31 | { 32 | var rampart = ramparts[i]; 33 | 34 | var positions = [ 35 | [rampart.pos.x - 1, rampart.pos.y], 36 | [rampart.pos.x, rampart.pos.y - 1], 37 | [rampart.pos.x, rampart.pos.y - 1], 38 | [rampart.pos.x, rampart.pos.y + 1], 39 | [rampart.pos.x - 1, rampart.pos.y - 1], 40 | [rampart.pos.x + 1, rampart.pos.y - 1], 41 | [rampart.pos.x - 1, rampart.pos.y + 1], 42 | [rampart.pos.x - 1, rampart.pos.y - 1] 43 | ]; 44 | 45 | for(var i in positions) 46 | { 47 | var pos = positions[i]; 48 | var tile = Game.getRoom('1-1').lookAt(pos[0], pos[1]); 49 | var build = true; 50 | for(var tilei in tile) 51 | { 52 | var thing = tile[tilei]; 53 | if(thing.type == 'structure' && thing.structure.structureType == Game.STRUCTURE_RAMPART) 54 | build = false; 55 | if(thing.type == 'constructionSite') 56 | build = false; 57 | } 58 | 59 | if(build) 60 | Game.getRoom('1-1').createConstructionSite(pos[0], pos[1], Game.STRUCTURE_RAMPART); 61 | } 62 | } 63 | } 64 | }; -------------------------------------------------------------------------------- /countType.js: -------------------------------------------------------------------------------- 1 | module.exports = function(type, qued) 2 | { 3 | if(qued == undefined) 4 | qued = false; 5 | 6 | //Get the current room, then find all creeps in that room by their role 7 | var room = Game.getRoom('1-1'); 8 | 9 | var count = room.find(Game.MY_CREEPS, { 10 | filter: function(creep) 11 | { 12 | if(creep.memory.role == type) 13 | return true; 14 | 15 | return false; 16 | } 17 | }).length; 18 | 19 | if(qued) 20 | { 21 | var spawns = Game.spawns; 22 | 23 | for(var i in spawns) 24 | { 25 | var spawn = spawns[i]; 26 | if(spawn.spawning !== null 27 | && spawn.spawning !== undefined 28 | && Memory.creeps[spawn.spawning.name].role == type) { 29 | count++; 30 | } 31 | } 32 | 33 | 34 | 35 | count += Memory.spawnQue.filter(function(qued) 36 | { 37 | return qued == type; 38 | }).length; 39 | } 40 | 41 | return count; 42 | }; -------------------------------------------------------------------------------- /extend.js: -------------------------------------------------------------------------------- 1 | /** 2 | * http://javascriptweblog.wordpress.com/2011/05/31/a-fresh-look-at-javascript-mixins/ 3 | * 4 | * @param destination 5 | * @param source 6 | * @returns destination 7 | */ 8 | module.exports = function (destination, source) { 9 | for (var k in source) { 10 | if (!destination.hasOwnProperty(k)) { 11 | destination[k] = source[k]; 12 | } 13 | } 14 | return destination; 15 | }; -------------------------------------------------------------------------------- /factory.js: -------------------------------------------------------------------------------- 1 | var countType = require('countType'); 2 | 3 | module.exports ={ 4 | init: function() 5 | { 6 | if(Memory.factoryInit != undefined) 7 | return; 8 | 9 | Memory.factoryInit = true; 10 | this.memory(); 11 | }, 12 | 13 | run: function() 14 | { 15 | this.spawnRequiredScreeps(); 16 | }, 17 | 18 | memory: function() { 19 | if(Memory.spawnQue == undefined) 20 | Memory.spawnQue = [ ]; 21 | 22 | if(Memory.sources == undefined) 23 | Memory.sources = { }; 24 | 25 | if(Memory.requiredScreeps == undefined) 26 | { 27 | Memory.requiredScreeps = [ 28 | //Survival 29 | 'miner', //1 30 | 'archer', //1 31 | 'miner', //2 32 | 'archer', //2 33 | 'healer', //1 34 | 'archer', //3 35 | 'miner', //3 36 | 'builder', 37 | 'transporter', 38 | 'transporter', 39 | 'archer', //4 40 | 'healer', //2 41 | 'archer', //5 42 | 'archer', //6 43 | 'miner', //4 44 | 'archer', //7 45 | 'archer', //8 46 | 'archer', //9 47 | 'healer', //3 48 | 'miner', //5 49 | 'builder', 50 | 'transporter', 51 | 'transporter', 52 | 'archer', //10 53 | 'archer', //11 54 | 'healer' //4 55 | 56 | //Tutorial 57 | // 'miner', 58 | // 'miner', 59 | // 'miner', 60 | // 'miner', 61 | // 'miner', 62 | ]; 63 | } 64 | }, 65 | 66 | spawnRequiredScreeps: function() 67 | { 68 | var requiredScreeps = Memory.requiredScreeps; 69 | 70 | var gatheredScreeps = { }; 71 | for(var index in requiredScreeps) 72 | { 73 | var type = requiredScreeps[index]; 74 | if(gatheredScreeps[type] == undefined) 75 | gatheredScreeps[type] = 0; 76 | 77 | var neededToSkip = gatheredScreeps[type] + 1; 78 | 79 | var found = countType(type, true); 80 | if(neededToSkip > countType(type, true)) 81 | { 82 | Memory.spawnQue.push(type); 83 | } 84 | 85 | gatheredScreeps[type]++; 86 | } 87 | }, 88 | 89 | buildArmyWhileIdle: function() 90 | { 91 | for(var i in Game.spawns) 92 | { 93 | var spawn = Game.spawns[i]; 94 | if(!spawn.spawning && Memory.spawnQue.length == 0 && spawn.energy / spawn.energyCapacity >= .6) { 95 | var archers = countType('archer', true); 96 | var healers = countType('healer', true); 97 | 98 | if(healers / archers < .25) 99 | require('spawner').spawn('healer', { }, spawn); 100 | else 101 | require('spawner').spawn('archer', { }, spawn); 102 | } 103 | } 104 | } 105 | }; -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | var performRoles = require('performRoles'); 2 | var spawner = require('spawner'); 3 | var countType = require('countType'); 4 | var factory = require('factory'); 5 | 6 | factory.init(); 7 | factory.run(); 8 | 9 | spawner.spawnNextInQue(); 10 | 11 | factory.buildArmyWhileIdle(); 12 | 13 | performRoles(Game.creeps); -------------------------------------------------------------------------------- /performRoles.js: -------------------------------------------------------------------------------- 1 | module.exports = function(creeps) 2 | { 3 | var roleManager = require('roleManager'); 4 | var roles = { }; 5 | 6 | //For each creep, check if they have a role. If they do, load and run it 7 | for(var name in creeps) 8 | { 9 | var creep = creeps[name]; 10 | if(creep.spawning || creep.memory.role == undefined || (creep.memory.active !== undefined && !creep.memory.active)) 11 | continue; 12 | 13 | var role = creep.memory.role; 14 | 15 | if(roleManager.roleExists(role)) 16 | role = roleManager.getRole(role); 17 | 18 | var role = Object.create(role); 19 | role.setCreep(creep); 20 | try { role.run(); } catch(e) { }; 21 | } 22 | }; -------------------------------------------------------------------------------- /roleManager.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roleExists: function(role){ 3 | try 4 | { 5 | require("roles_" + role); 6 | return true; 7 | } 8 | catch(e) 9 | { 10 | return false; 11 | } 12 | }, 13 | 14 | getRole: function(role) 15 | { 16 | if(!this.roleExists(role)) 17 | return false; 18 | 19 | var proto = require('role_prototype'); 20 | 21 | var roleObject = require("roles_" + role); 22 | roleObject = require('extend')(roleObject, proto); 23 | return roleObject; 24 | }, 25 | 26 | getRoleBodyParts: function(role) 27 | { 28 | if(!this.roleExists(role)) 29 | return false; 30 | 31 | var role = this.getRole(role); 32 | 33 | if(role.getParts !== undefined) 34 | return role.getParts.call(role); 35 | else 36 | return role.prototype.getParts.call(role); 37 | } 38 | }; -------------------------------------------------------------------------------- /role_prototype.js: -------------------------------------------------------------------------------- 1 | var proto = { 2 | /** 3 | * The creep for this role 4 | * 5 | * @type creep 6 | */ 7 | creep: null, 8 | 9 | /** 10 | * Set the creep for this role 11 | * 12 | * @param {Creep} creep 13 | */ 14 | setCreep: function(creep) 15 | { 16 | this.creep = creep; 17 | return this; 18 | }, 19 | 20 | run: function() 21 | { 22 | if(this.creep.memory.onSpawned == undefined) { 23 | this.onSpawn(); 24 | this.creep.memory.onSpawned = true; 25 | } 26 | 27 | this.action(this.creep); 28 | 29 | if(this.creep.ticksToLive == 1) 30 | this.beforeAge(); 31 | }, 32 | 33 | handleEvents: function() 34 | { 35 | if(this.creep.memory.onSpawned == undefined) { 36 | this.onSpawnStart(); 37 | this.onSpawn(); 38 | this.creep.memory.onSpawned = true; 39 | } 40 | 41 | if(this.creep.memory.onSpawnEnd == undefined && !this.creep.spawning) { 42 | this.onSpawnEnd(); 43 | this.creep.memory.onSpawnEnd = true; 44 | } 45 | }, 46 | 47 | getParts: function() { 48 | var _ = require('lodash'); 49 | 50 | var extensions = Game.getRoom('1-1').find(Game.MY_STRUCTURES, { 51 | filter: function(structure) 52 | { 53 | return (structure.structureType == Game.STRUCTURE_EXTENSION && structure.energy >= 200); 54 | } 55 | }).length; 56 | 57 | var parts = _.cloneDeep(this.parts); 58 | if(typeof parts[0] != "object") 59 | return this.parts; 60 | 61 | parts.reverse(); 62 | 63 | for(var i in parts) 64 | { 65 | if((parts[i].length - 5) <= extensions) { 66 | return parts[i]; 67 | } 68 | } 69 | }, 70 | 71 | action: function() { }, 72 | 73 | onSpawn: function() { }, 74 | 75 | onSpawnStart: function() { }, 76 | 77 | onSpawnEnd: function() { }, 78 | 79 | beforeAge: function() { }, 80 | 81 | /** 82 | * All credit goes to Djinni 83 | * @url https://bitbucket.org/Djinni/screeps/ 84 | */ 85 | rest: function(civilian) 86 | { 87 | var creep = this.creep; 88 | 89 | var distance = 4; 90 | var restTarget = creep.pos.findNearest(Game.MY_SPAWNS); 91 | 92 | if(!civilian) { 93 | var flags = Game.flags; 94 | for (var i in flags) { 95 | var flag = flags[i]; 96 | if (creep.pos.inRangeTo(flag, distance) || creep.pos.findPathTo(flag).length > 0) { 97 | restTarget = flag; 98 | break; 99 | } 100 | } 101 | } 102 | 103 | // var flag = Game.flags['Flag1']; 104 | // if(flag !== undefined && civilian !== true) 105 | // restTarget = flag; 106 | // 107 | // var flag2 = Game.flags['Flag2']; 108 | // if(flag !== undefined && civilian !== true && !creep.pos.inRangeTo(flag, distance) && !creep.pos.findPathTo(flag).length) 109 | // restTarget = flag2; 110 | 111 | if (creep.getActiveBodyparts(Game.HEAL)) { 112 | // distance = distance - 1; 113 | } 114 | else if (creep.getActiveBodyparts(Game.RANGED_ATTACK)) { 115 | // distance = distance - 1; 116 | } 117 | if (creep.pos.findPathTo(restTarget).length > distance) { 118 | creep.moveTo(restTarget); 119 | } 120 | }, 121 | 122 | /** 123 | * All credit goes to Djinni 124 | * @url https://bitbucket.org/Djinni/screeps/ 125 | */ 126 | rangedAttack: function(target) { 127 | var creep = this.creep; 128 | 129 | if(!target) 130 | target = creep.pos.findNearest(Game.HOSTILE_CREEPS); 131 | 132 | if(target) { 133 | if (target.pos.inRangeTo(creep.pos, 3) ) { 134 | creep.rangedAttack(target); 135 | return target; 136 | } 137 | } 138 | return null; 139 | }, 140 | 141 | keepAwayFromEnemies: function() 142 | { 143 | var creep = this.creep; 144 | 145 | var target = creep.pos.findNearest(Game.HOSTILE_CREEPS); 146 | if(target !== null && target.pos.inRangeTo(creep.pos, 3)) 147 | creep.moveTo(creep.pos.x + creep.pos.x - target.pos.x, creep.pos.y + creep.pos.y - target.pos.y ); 148 | }, 149 | 150 | /** 151 | * All credit goes to Djinni 152 | * @url https://bitbucket.org/Djinni/screeps/ 153 | */ 154 | kite: function(target) { 155 | var creep = this.creep; 156 | 157 | if (target.pos.inRangeTo(creep.pos, 2)) { 158 | creep.moveTo(creep.pos.x + creep.pos.x - target.pos.x, creep.pos.y + creep.pos.y - target.pos.y ); 159 | return true; 160 | } else if (target.pos.inRangeTo(creep.pos, 3)) { 161 | return true; 162 | } 163 | else { 164 | creep.moveTo(target); 165 | return true; 166 | } 167 | 168 | return false; 169 | }, 170 | 171 | getRangedTarget: function() 172 | { 173 | var creep = this.creep; 174 | 175 | var closeArchers = creep.pos.findNearest(Game.HOSTILE_CREEPS, { 176 | filter: function(enemy) 177 | { 178 | return enemy.getActiveBodyparts(Game.RANGED_ATTACK) > 0 179 | && creep.pos.inRangeTo(enemy, 3); 180 | } 181 | }); 182 | 183 | if(closeArchers != null) 184 | return closeArchers; 185 | 186 | var closeMobileMelee = creep.pos.findNearest(Game.HOSTILE_CREEPS, { 187 | filter: function(enemy) 188 | { 189 | return enemy.getActiveBodyparts(Game.ATTACK) > 0 190 | && enemy.getActiveBodyparts(Game.MOVE) > 0 191 | && creep.pos.inRangeTo(enemy, 3); 192 | } 193 | }); 194 | 195 | if(closeMobileMelee != null) 196 | return closeMobileMelee; 197 | 198 | var closeHealer = creep.pos.findNearest(Game.HOSTILE_CREEPS, { 199 | filter: function(enemy) 200 | { 201 | return enemy.getActiveBodyparts(Game.HEAL) > 0 202 | && enemy.getActiveBodyparts(Game.MOVE) > 0 203 | && creep.pos.inRangeTo(enemy, 3); 204 | } 205 | }); 206 | 207 | if(closeHealer != null) 208 | return closeHealer; 209 | 210 | return creep.pos.findNearest(Game.HOSTILE_CREEPS); 211 | } 212 | }; 213 | 214 | module.exports = proto; -------------------------------------------------------------------------------- /roles_archer.js: -------------------------------------------------------------------------------- 1 | var proto = require('role_prototype'); 2 | 3 | var archer = { 4 | parts: [ 5 | [Game.RANGED_ATTACK, Game.RANGED_ATTACK, Game.MOVE, Game.MOVE], 6 | [Game.RANGED_ATTACK, Game.RANGED_ATTACK, Game.RANGED_ATTACK, Game.MOVE, Game.MOVE, Game.MOVE], 7 | [Game.RANGED_ATTACK, Game.RANGED_ATTACK, Game.RANGED_ATTACK, Game.RANGED_ATTACK, Game.MOVE, Game.MOVE, Game.MOVE, Game.MOVE], 8 | [Game.RANGED_ATTACK, Game.RANGED_ATTACK, Game.RANGED_ATTACK, Game.RANGED_ATTACK, Game.RANGED_ATTACK, Game.MOVE, Game.MOVE, Game.MOVE, Game.MOVE, Game.MOVE], 9 | ], 10 | 11 | /** 12 | * Here we want Archer to automatically scale to however many extensions we have 13 | * @returns {Array} 14 | */ 15 | getParts: function() 16 | { 17 | var _= require('lodash'); 18 | 19 | var partsAllowed = Game.getRoom('1-1').find(Game.MY_STRUCTURES, { 20 | filter: function(structure) 21 | { 22 | return (structure.structureType == Game.STRUCTURE_EXTENSION && structure.energy >= 200); 23 | } 24 | }).length; 25 | partsAllowed += 5; 26 | 27 | var modulo = partsAllowed % 2; 28 | partsAllowed -= modulo; 29 | partsAllowed /= 2; 30 | 31 | if(partsAllowed > 5) 32 | partsAllowed = 5; 33 | 34 | var parts = [ ]; 35 | for(var i = 0; i < partsAllowed; i++) { 36 | parts.unshift(Game.RANGED_ATTACK); 37 | parts.push(Game.MOVE); 38 | } 39 | 40 | return parts; 41 | }, 42 | 43 | /** 44 | * @TODO: We need to get archers to prioritise their targets better 45 | */ 46 | action: function() 47 | { 48 | var creep = this.creep; 49 | 50 | var target = this.getRangedTarget(); 51 | if(target !== null) 52 | 53 | creep.rangedAttack(target); 54 | 55 | //If there's not a target near by, let's go search for a target if need be 56 | if(target === null) 57 | return this.rest(); 58 | 59 | this.kite(target); 60 | creep.rangedAttack(target); 61 | } 62 | }; 63 | 64 | module.exports = archer; -------------------------------------------------------------------------------- /roles_builder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @TODO: Make it more carry heavy, make it have helpers 3 | * @type {{parts: *[], getParts: getParts, action: action}} 4 | */ 5 | var builder = { 6 | parts: [ 7 | [Game.WORK,Game.WORK,Game.CARRY,Game.CARRY,Game.MOVE], 8 | // [Game.WORK,Game.WORK,Game.CARRY,Game.CARRY,Game.MOVE, Game.MOVE, Game.CARRY], 9 | // [Game.WORK,Game.WORK,Game.CARRY,Game.CARRY,Game.MOVE, Game.MOVE, Game.CARRY, Game.MOVE], 10 | // [Game.WORK,Game.WORK,Game.CARRY,Game.CARRY,Game.MOVE, Game.MOVE, Game.CARRY, Game.MOVE, Game.WORK], 11 | // [Game.WORK,Game.WORK,Game.CARRY,Game.CARRY,Game.MOVE, Game.MOVE, Game.CARRY, Game.MOVE, Game.WORK, Game.MOVE], 12 | // [Game.WORK,Game.WORK,Game.CARRY,Game.CARRY,Game.MOVE, Game.MOVE, Game.CARRY, Game.MOVE, Game.WORK, Game.MOVE, Game.CARRY] 13 | ], 14 | 15 | // getParts: function() 16 | // { 17 | // var _= require('lodash'); 18 | // 19 | // var partsAllowed = Game.getRoom('1-1').find(Game.MY_STRUCTURES, { 20 | // filter: function(structure) 21 | // { 22 | // return (structure.structureType == Game.STRUCTURE_EXTENSION && structure.energy >= 200); 23 | // } 24 | // }).length; 25 | // 26 | // var parts = [ Game.WORK, Game.WORK, Game.WORK, Game.CARRY, Game.MOVE ]; 27 | // var modulo = partsAllowed % 2; 28 | // partsAllowed -= modulo; 29 | // partsAllowed /= 2; 30 | // 31 | // if(partsAllowed > 5) 32 | // partsAllowed = 5; 33 | // 34 | // for(var i = 0; i < partsAllowed; i++) 35 | // parts.push(Game.MOVE, Game.CARRY); 36 | // 37 | // return parts; 38 | // 39 | // return this.prototype.getParts.call(this); 40 | // }, 41 | 42 | action: function() 43 | { 44 | var creep = this.creep; 45 | 46 | //If out of energy, go to spawn and recharge 47 | if(creep.energy == 0) { 48 | var closestSpawn = creep.pos.findNearest(Game.MY_SPAWNS, { 49 | filter: function(spawn) 50 | { 51 | return spawn.energy > 0 && creep.pos.inRangeTo(spawn, 3); 52 | } 53 | }); 54 | 55 | if(closestSpawn) { 56 | creep.moveTo(closestSpawn); 57 | closestSpawn.transferEnergy(creep); 58 | } 59 | } 60 | else { 61 | //First, we're going to check for damaged ramparts. We're using ramparts as the first line of defense 62 | //and we want them nicely maintained. This is especially important when under attack. The builder will 63 | //repair the most damaged ramparts first 64 | var structures = creep.room.find(Game.STRUCTURES); 65 | var damagedRamparts = [ ]; 66 | 67 | for(var index in structures) 68 | { 69 | var structure = structures[index]; 70 | if(structure.structureType == 'rampart' && structure.hits < (structure.hitsMax - 50)) 71 | damagedRamparts.push(structure); 72 | } 73 | 74 | damagedRamparts.sort(function(a, b) 75 | { 76 | return(a.hits - b.hits); 77 | }); 78 | 79 | if(damagedRamparts.length) 80 | { 81 | creep.moveTo(damagedRamparts[0]); 82 | creep.repair(damagedRamparts[0]); 83 | 84 | return; 85 | } 86 | 87 | //Next we're going to look for general buildings that have less than 50% health, and we'll go to repair those. 88 | //We set it at 50%, because we don't want builders abandoning their duty every time a road gets walked on 89 | var halfBroken = creep.room.find(Game.STRUCTURES); 90 | var toRepair = [ ]; 91 | for(var index in halfBroken) 92 | if((halfBroken[index].hits / halfBroken[index].hitsMax) < 0.5) 93 | toRepair.push(halfBroken[index]); 94 | 95 | if(toRepair.length) 96 | { 97 | var structure = toRepair[0]; 98 | creep.moveTo(structure); 99 | creep.repair(structure); 100 | 101 | return; 102 | } 103 | 104 | //If no repairs are needed, we're just going to go find some structures to build 105 | var targets = creep.pos.findNearest(Game.CONSTRUCTION_SITES); 106 | if(targets) { 107 | 108 | if(!creep.pos.isNearTo(targets)) 109 | creep.moveTo(targets); 110 | 111 | if(creep.pos.inRangeTo(targets, 0)) 112 | creep.suicide(); 113 | 114 | creep.build(targets); 115 | return; 116 | } 117 | 118 | var target = this.rangedAttack(); 119 | if(target) 120 | { 121 | this.kite(target); 122 | } 123 | 124 | this.rest(true); 125 | } 126 | } 127 | } 128 | 129 | module.exports = builder; -------------------------------------------------------------------------------- /roles_guard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The Guard hasn't been improved in a while, I've mostly just moved on to archers for now. I'll come back and 3 | * work on this one later 4 | * @param creep 5 | */ 6 | var guard = { 7 | parts: [ 8 | [Game.TOUGH, Game.MOVE, Game.ATTACK, Game.ATTACK] 9 | ], 10 | 11 | action: function() 12 | { 13 | var creep = this.creep; 14 | 15 | var targets = creep.room.find(Game.HOSTILE_CREEPS); 16 | if (targets.length) { 17 | creep.moveTo(targets[0]); 18 | creep.attack(targets[0]); 19 | } 20 | else { 21 | this.rest(); 22 | } 23 | } 24 | }; 25 | 26 | module.exports = guard; -------------------------------------------------------------------------------- /roles_harvester.js: -------------------------------------------------------------------------------- 1 | /** 2 | * These are simple creatures, they just find an active source and harvest it 3 | * @param creep 4 | */ 5 | var harvester = { 6 | parts: [ 7 | [Game.MOVE, Game.MOVE, Game.CARRY, Game.WORK] 8 | ], 9 | 10 | action: function () { 11 | var creep = this.creep; 12 | 13 | if(creep.energy < creep.energyCapacity) { 14 | var sources = creep.pos.findNearest(Game.SOURCES); 15 | creep.moveTo(sources); 16 | creep.harvest(sources); 17 | } 18 | else { 19 | var target = creep.pos.findNearest(Game.MY_SPAWNS); 20 | 21 | creep.moveTo(target); 22 | creep.transferEnergy(target); 23 | } 24 | } 25 | }; 26 | 27 | module.exports = harvester; -------------------------------------------------------------------------------- /roles_healer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Because shit happens 3 | * @param creep 4 | */ 5 | var proto = require('role_prototype'); 6 | 7 | var healer = { 8 | parts: [ 9 | [Game.MOVE, Game.MOVE, Game.HEAL, Game.HEAL], 10 | [Game.MOVE, Game.MOVE, Game.MOVE, Game.HEAL, Game.HEAL, Game.HEAL] 11 | ], 12 | 13 | action: function() 14 | { 15 | var creep = this.creep; 16 | var needsHealing = [ ]; 17 | 18 | this.keepAwayFromEnemies(); 19 | 20 | //Find my creeps that are hurt. If they're hurt, heal them. 21 | //If there aren't any hurt, we're going to try and get the healers 22 | //to tick near the guards, so that they're close by when the battle starts 23 | var target = creep.pos.findNearest(Game.MY_CREEPS, { 24 | filter: function(t) 25 | { 26 | return t.hits < t.hitsMax 27 | } 28 | }); 29 | 30 | if(target) 31 | { 32 | creep.moveTo(target); 33 | creep.heal(target); 34 | } 35 | else { 36 | this.rest(); 37 | } 38 | } 39 | }; 40 | 41 | module.exports = healer; -------------------------------------------------------------------------------- /roles_miner.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This guy just finds a source, and stays near it. His job is just to mine away and let the energy fall on the ground 3 | * 4 | * @TODO: See if we can't implement preffered spawn spots close to their source 5 | * @param creep 6 | */ 7 | var miner = { 8 | parts: [ 9 | [Game.MOVE, Game.WORK, Game.WORK, Game.WORK, Game.WORK], 10 | [Game.MOVE, Game.WORK, Game.WORK, Game.WORK, Game.WORK, Game.WORK] 11 | ], 12 | 13 | getOpenSource: function() 14 | { 15 | var creep = this.creep; 16 | 17 | var source = creep.pos.findNearest(Game.SOURCES, { 18 | filter: function(source) 19 | { 20 | if(Memory.sources[source.id] == undefined || Memory.sources[source.id].miner == undefined || Memory.sources[source.id].miner == creep.id) 21 | return true; 22 | 23 | if(Game.getObjectById(Memory.sources[source.id].miner) == null) 24 | return true; 25 | 26 | return false; 27 | } 28 | }); 29 | 30 | return source; 31 | }, 32 | 33 | setSourceToMine: function(source) 34 | { 35 | var creep = this.creep; 36 | 37 | if(!source) 38 | return; 39 | 40 | if(Memory.sources[source.id] == undefined) 41 | Memory.sources[source.id] = { id: source.id }; 42 | 43 | Memory.sources[source.id].miner = creep.id; 44 | creep.memory.source = source.id; 45 | 46 | var helperSpawn = source.pos.findNearest(Game.MY_SPAWNS); 47 | var steps = helperSpawn.pos.findPathTo(source).length * 2; 48 | var creepsNeeded = Math.round((steps * 8) / 100); 49 | 50 | if(creepsNeeded > 5) 51 | creepsNeeded = 5; 52 | 53 | for(var i = 0; i < creepsNeeded; i++) 54 | Memory.spawnQue.unshift({ type: 'miner_helper', memory: { 55 | miner: creep.id 56 | }}); 57 | 58 | creep.memory.helpersNeeded = creepsNeeded; 59 | }, 60 | 61 | onSpawn: function() 62 | { 63 | var creep = this.creep; 64 | 65 | creep.memory.isNearSource = false; 66 | creep.memory.helpers = []; 67 | 68 | var source = this.getOpenSource(); 69 | this.setSourceToMine(source); 70 | 71 | creep.memory.onCreated = true; 72 | }, 73 | 74 | action: function() 75 | { 76 | var creep = this.creep; 77 | 78 | //Basically, each miner can empty a whole source by themselves. Also, since they're slow, we don't have them 79 | //moving away from the source when it's empty, it'd regenerate before they got to another one. 80 | //For this, we assign one miner to one source, and they stay with it 81 | var source = Game.getObjectById(creep.memory.source); 82 | 83 | if(source == null) { 84 | var source = this.getOpenSource(); 85 | 86 | if(!source) 87 | return; 88 | 89 | this.setSourceToMine(source); 90 | } 91 | 92 | if(creep.pos.inRangeTo(source, 5)) 93 | creep.memory.isNearSource = true; 94 | else 95 | creep.memory.isNearSource = false; 96 | 97 | if(Memory.sources[source.id] == undefined) 98 | Memory.sources[source.id] = { id: source.id }; 99 | 100 | Memory.sources[source.id].miner = creep.id; 101 | 102 | creep.moveTo(source); 103 | creep.harvest(source); 104 | } 105 | }; 106 | 107 | module.exports = miner; -------------------------------------------------------------------------------- /roles_miner_helper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This guys does the other half of energy collection. The miner gets it from the source, and the helper does the 3 | * transportation. We don't want them just going for the nearest source, as that means that if we have more than one 4 | * miner, all the helpers will only go for the first miner. To counter this, we assign them to a miner the same way 5 | * we assign a miner to a source 6 | */ 7 | 8 | var helper = { 9 | parts: [ 10 | [Game.MOVE, Game.CARRY, Game.MOVE, Game.CARRY], 11 | [Game.MOVE, Game.CARRY, Game.MOVE, Game.CARRY, Game.MOVE, Game.CARRY] 12 | ], 13 | 14 | assignMiner: function () { 15 | var creep = this.creep; 16 | 17 | var miner = creep.pos.findNearest(Game.MY_CREEPS, { 18 | filter: function (miner) { 19 | if (miner.memory.role == 'miner' && miner.memory.helpers.length < miner.memory.helpersNeeded) 20 | return true; 21 | 22 | return false; 23 | } 24 | }); 25 | 26 | if (miner == undefined) 27 | return; 28 | 29 | creep.memory.miner = miner.id; 30 | miner.memory.helpers.push(creep.id); 31 | }, 32 | 33 | /** 34 | * @TODO: Make helpers smarter about avoiding miners, instead of just waiting till they're 5 tiles away 35 | * @TODO: When spawns are at .25, and extensions have >= 200, help builders before filling shit up 36 | */ 37 | action: function () { 38 | var creep = this.creep; 39 | 40 | if (creep.memory.courier !== undefined && creep.memory.courier == true) { 41 | creep.memory.courier = false; 42 | return; 43 | } 44 | 45 | //If this helper isn't assigned to a miner, find one and assign him to it. If it is assigned to a miner, 46 | //then find that miner by his id 47 | if (creep.memory.miner == undefined) 48 | this.assignMiner(); 49 | 50 | var miner = Game.getObjectById(creep.memory.miner); 51 | 52 | if (miner == null) { 53 | creep.suicide(); 54 | return; 55 | } 56 | 57 | //If we can still pick up energy, let's do that 58 | if (creep.energy < creep.energyCapacity) { 59 | if (creep.pos.isNearTo(miner)) { 60 | var energy = creep.pos.findInRange(Game.DROPPED_ENERGY, 1)[0]; 61 | creep.pickup(energy); 62 | } 63 | else { 64 | if (miner.memory.isNearSource) 65 | creep.moveTo(miner); 66 | } 67 | 68 | return; 69 | } 70 | 71 | var target = null; 72 | 73 | //Okay, everything below is for dropping energy off 74 | 75 | if (!target) { 76 | var spawn = creep.pos.findNearest(Game.MY_SPAWNS); 77 | 78 | //If we found it, set it as our target 79 | if (spawn) 80 | target = spawn; 81 | } 82 | 83 | //Let's get the direction we want to go in 84 | var targetDirection = creep.pos.findPathTo(target, { ignoreCreeps: true })[0].direction; 85 | 86 | //Let's look for a courier in that direction. We'll check on making sure they're the right 87 | //role, if they can hold any energy, if they're in range and if they're in the same direction 88 | var leftDir = targetDirection - 1; 89 | var rightDir = targetDirection + 1; 90 | 91 | if (leftDir < 1) 92 | leftDir += 8; 93 | if (leftDir > 8) 94 | leftDir -= 8; 95 | 96 | if (rightDir < 1) 97 | rightDir += 8; 98 | if (rightDir > 8) 99 | rightDir -= 8; 100 | 101 | var courier = creep.pos.findNearest(Game.MY_CREEPS, { 102 | filter: function (possibleTarget) { 103 | return ( 104 | possibleTarget.memory.role == creep.memory.role 105 | // && possibleTarget.memory.miner == creep.memory.miner 106 | && possibleTarget.energy < possibleTarget.energyCapacity 107 | && creep.pos.inRangeTo(possibleTarget, 1) 108 | && ( 109 | creep.pos.getDirectionTo(possibleTarget) == targetDirection 110 | || creep.pos.getDirectionTo(possibleTarget) == leftDir 111 | || creep.pos.getDirectionTo(possibleTarget) == rightDir 112 | ) 113 | ); 114 | } 115 | }); 116 | 117 | //If we found a courier, make that courier our new target 118 | if (courier !== null && !creep.pos.isNearTo(target)) { 119 | target = courier; 120 | target.memory.courier = true; 121 | } 122 | 123 | //If we're near to the target, either give it our energy or drop it 124 | if (creep.pos.isNearTo(target)) { 125 | if (target.energy < target.energyCapacity) { 126 | creep.transferEnergy(target); 127 | } 128 | else 129 | creep.dropEnergy(); 130 | } 131 | //Let's do the moving 132 | else { 133 | creep.moveTo(target); 134 | } 135 | } 136 | }; 137 | 138 | module.exports = helper; -------------------------------------------------------------------------------- /roles_scavenger.js: -------------------------------------------------------------------------------- 1 | var scavenger = { 2 | parts: [ 3 | [Game.CARRY, Game.CARRY, Game.MOVE, Game.MOVE] 4 | ], 5 | 6 | action: function() 7 | { 8 | var creep = this.creep; 9 | 10 | var droppedEnergy = creep.pos.findNearest(Game.DROPPED_ENERGY, { 11 | filter: function(en) { 12 | var pickup = true; 13 | var tile = creep.room.lookAt(en); 14 | for(var i in tile) 15 | { 16 | if(tile[i].type == "creep" && tile[i].creep.memory && tile[i].creep.memory.role == "miner") 17 | pickup = false; 18 | } 19 | 20 | return pickup; 21 | } 22 | }); 23 | 24 | if(droppedEnergy == null || creep.energy == creep.energyCapacity) 25 | { 26 | var nearestSpawn = creep.pos.findNearest(Game.MY_SPAWNS, { 27 | filter: function(spawn) 28 | { 29 | return spawn.energy < spawn.energyCapacity; 30 | } 31 | }); 32 | 33 | creep.moveTo(nearestSpawn); 34 | creep.transferEnergy(nearestSpawn); 35 | } 36 | else 37 | { 38 | creep.moveTo(droppedEnergy); 39 | creep.pickup(droppedEnergy); 40 | } 41 | } 42 | }; 43 | 44 | module.exports = scavenger; -------------------------------------------------------------------------------- /roles_transporter.js: -------------------------------------------------------------------------------- 1 | var transporter = { 2 | parts: [ 3 | [Game.CARRY, Game.CARRY, Game.MOVE, Game.MOVE] 4 | ], 5 | 6 | action: function() 7 | { 8 | var creep = this.creep; 9 | 10 | //@TODO: Balance Spawns here 11 | 12 | if(creep.energy == 0) 13 | { 14 | var closestSpawn = creep.pos.findNearest(Game.MY_SPAWNS, { 15 | filter: function(spawn) 16 | { 17 | return spawn.energy > 0; 18 | } 19 | }); 20 | 21 | creep.moveTo(closestSpawn); 22 | closestSpawn.transferEnergy(creep); 23 | 24 | return; 25 | } 26 | 27 | var target = null; 28 | 29 | //Transfer to builder 30 | if (!target) { 31 | var builderToHelp = creep.pos.findNearest(Game.MY_CREEPS, { 32 | filter: function (builder) { 33 | return builder.memory.role == "builder" 34 | && builder.energy < ( builder.energyCapacity - 10); 35 | } 36 | }); 37 | 38 | if (builderToHelp) 39 | target = builderToHelp; 40 | } 41 | 42 | if(!target) 43 | { 44 | var extension = creep.pos.findNearest(Game.MY_STRUCTURES, { 45 | filter: function(structure) 46 | { 47 | return structure.structureType == Game.STRUCTURE_EXTENSION && 48 | structure.energy < structure.energyCapacity; 49 | } 50 | }); 51 | 52 | if(extension) 53 | target = extension; 54 | } 55 | 56 | //Go to target and give it energy 57 | if (creep.pos.isNearTo(target)) { 58 | if (target.energy < target.energyCapacity) { 59 | creep.transferEnergy(target); 60 | } 61 | } 62 | else { 63 | creep.moveTo(target); 64 | } 65 | } 66 | }; 67 | 68 | module.exports = transporter; -------------------------------------------------------------------------------- /spawner.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | { 3 | initSpawnQue: function() 4 | { 5 | if(Memory.spawnQue == undefined) 6 | Memory.spawnQue = [ ]; 7 | }, 8 | 9 | addToQue: function(creep, unshift) 10 | { 11 | this.initSpawnQue(); 12 | 13 | if(unshift != undefined && unshift === true) 14 | Memory.spawnQue.unshift(creep); 15 | else 16 | Memory.spawnQue.push(creep); 17 | }, 18 | 19 | spawnNextInQue: function() 20 | { 21 | this.initSpawnQue(); 22 | 23 | if(!Memory.spawnQue.length) 24 | return; 25 | 26 | var spawns = Game.getRoom('1-1').find(Game.MY_SPAWNS, { 27 | filter: function(spawn) 28 | { 29 | return spawn.spawning === undefined || spawn.spawning === null; 30 | } 31 | }); 32 | 33 | if(!spawns.length) 34 | return; 35 | 36 | var role = Memory.spawnQue[0]; 37 | 38 | if(typeof role == "string") 39 | { 40 | role = { type: role, memory: { } }; 41 | } 42 | 43 | var me = this; 44 | var toSpawnAt = spawns.filter(function(spawn) 45 | { 46 | return me.canSpawn(spawn, role.type); 47 | }); 48 | 49 | if(! toSpawnAt.length) 50 | return; 51 | 52 | toSpawnAt = toSpawnAt[0]; 53 | 54 | this.spawn(role.type, role.memory, toSpawnAt); 55 | 56 | Memory.spawnQue.shift(); 57 | }, 58 | 59 | spawn: function(role, memory, spawnPoint) 60 | { 61 | if(!spawnPoint) 62 | spawnPoint = Game.spawns.Spawn1; 63 | 64 | var manager = require('roleManager'); 65 | 66 | if(!manager.roleExists(role)) 67 | { 68 | return; 69 | } 70 | 71 | if(!this.canSpawn(spawnPoint, role)) 72 | { 73 | return; 74 | } 75 | 76 | if(memory == undefined) 77 | memory = { }; 78 | 79 | memory['role'] = role; 80 | 81 | var nameCount = 0; 82 | var name = null; 83 | while(name == null) 84 | { 85 | nameCount++; 86 | var tryName = role + nameCount; 87 | if(Game.creeps[tryName] == undefined) 88 | name = tryName; 89 | } 90 | 91 | console.log('Spawning ' + role); 92 | spawnPoint.createCreep(manager.getRoleBodyParts(role), name, memory); 93 | }, 94 | 95 | canSpawn: function(spawnPoint, role) 96 | { 97 | if(typeof spawnPoint == "string" && role == undefined) 98 | { 99 | role = spawnPoint; 100 | spawnPoint = Game.spawns.Spawn1; 101 | } 102 | 103 | return spawnPoint.energy >= this.spawnCost(role) 104 | && (spawnPoint.spawning == null 105 | || spawnPoint.spawning == undefined); 106 | }, 107 | 108 | spawnCost: function(role) 109 | { 110 | var manager = require('roleManager'); 111 | var parts = manager.getRoleBodyParts(role); 112 | 113 | var total = 0; 114 | for(var index in parts) 115 | { 116 | var part = parts[index]; 117 | switch(part) 118 | { 119 | case Game.MOVE: 120 | total += 50 121 | break; 122 | 123 | case Game.WORK: 124 | total += 20 125 | break; 126 | 127 | case Game.CARRY: 128 | total += 50 129 | break; 130 | 131 | case Game.ATTACK: 132 | total += 100 133 | break; 134 | 135 | case Game.RANGED_ATTACK: 136 | total += 150 137 | break; 138 | 139 | case Game.HEAL: 140 | total += 200 141 | break; 142 | 143 | case Game.TOUGH: 144 | total += 5 145 | break; 146 | } 147 | } 148 | 149 | return total; 150 | }, 151 | 152 | killAll: function(role) 153 | { 154 | for(var i in Game.creeps) { 155 | if(role == undefined || Game.creeps[i].memory.role == role) 156 | Game.creeps[i].suicide(); 157 | } 158 | } 159 | } -------------------------------------------------------------------------------- /sync.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var http = require('http'); 3 | var path = require('path'); 4 | var fs = require('fs'); 5 | var URL = require('url'); 6 | 7 | // This all runs in the browser 8 | var clientSide = function() { 9 | // Grab reference to the commit button 10 | var buttons = Array.prototype.slice.call(document.body.getElementsByTagName('button')).filter(function(el) { 11 | return el.getAttribute('ng:disabled') === '!Script.dirty'; 12 | }); 13 | var commitButton = buttons[0]; 14 | 15 | // Override lodash's cloneDeep which is called from inside the internal reset method 16 | var modules; 17 | _.cloneDeep = function(cloneDeep) { 18 | return function(obj) { 19 | if (typeof obj.main === 'string' && modules) { 20 | // Monkey patch! 21 | return modules; 22 | } 23 | return cloneDeep.apply(this, arguments); 24 | }; 25 | }(_.cloneDeep); 26 | 27 | // Wait for changes to local filesystem 28 | function update(now) { 29 | var req = new XMLHttpRequest; 30 | req.onreadystatechange = function() { 31 | if (req.readyState === 4) { 32 | if (req.status === 200) { 33 | modules = JSON.parse(req.responseText); 34 | commitButton.disabled = false; 35 | commitButton.click(); 36 | } 37 | setTimeout(update.bind(this, false), req.status === 200 ? 0 : 1000); 38 | } 39 | }; 40 | req.open('GET', 'http://localhost:9090/'+ (now ? 'get' : 'wait'), true); 41 | req.send(); 42 | }; 43 | update(true); 44 | 45 | // Look for console messages 46 | var sconsole = document.body.getElementsByClassName('console-messages-list')[0]; 47 | var lastMessage; 48 | setInterval(function() { 49 | var nodes = sconsole.getElementsByClassName('console-message'); 50 | var messages = []; 51 | var found = false; 52 | for (var ii = 0; ii < nodes.length; ++ii) { 53 | var el = nodes[ii]; 54 | if (el.innerHTML === lastMessage) { 55 | found = true; 56 | } else if (found) { 57 | var ts = el.getElementsByClassName('timestamp')[0]; 58 | ts = ts && ts.firstChild.nodeValue; 59 | var msg = el.getElementsByTagName('span')[0].childNodes; 60 | var txt = ''; 61 | for (var jj = 0; jj < msg.length; ++jj) { 62 | if (msg[jj].tagName === 'BR') { 63 | txt += '\n'; 64 | } else { 65 | txt += msg[jj].nodeValue; 66 | } 67 | } 68 | messages.push([ts, txt]); 69 | } 70 | } 71 | if (messages.length) { 72 | var req = new XMLHttpRequest; 73 | req.open('GET', 'http://localhost:9090/log?log='+ encodeURIComponent(JSON.stringify(messages)), true); 74 | req.send(); 75 | } 76 | lastMessage = nodes.length && nodes[nodes.length - 1].innerHTML; 77 | }, 100); 78 | }; 79 | 80 | // Set up watch on directory changes 81 | var modules = {}; 82 | var writeListener; 83 | fs.readdirSync('.').forEach(function(file) { 84 | if (file !== 'sync.js' && /\.js$/.test(file)) { 85 | modules[file.replace(/\.js$/, '')] = fs.readFileSync(file, 'utf8'); 86 | } 87 | }); 88 | fs.watch(__dirname, function(ev, file) { 89 | if (file !== 'sync.js' && /\.js$/.test(file)) { 90 | modules[file.replace(/\.js$/, '')] = fs.readFileSync(file, 'utf8'); 91 | if (writeListener) { 92 | process.nextTick(writeListener); 93 | writeListener = undefined; 94 | } 95 | } 96 | }); 97 | 98 | // Localhost HTTP server 99 | var server = http.createServer(function(req, res) { 100 | var path = URL.parse(req.url, true); 101 | switch (path.pathname) { 102 | case '/inject': 103 | res.writeHead(200, { 'Content-Type': 'text/javascript' }); 104 | res.end('~'+ clientSide.toString()+ '()'); 105 | break; 106 | 107 | case '/get': 108 | case '/wait': 109 | if (writeListener) { 110 | writeListener(); 111 | } 112 | writeListener = function() { 113 | res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); 114 | res.end(JSON.stringify(modules)); 115 | }; 116 | if (req.url === '/get') { 117 | writeListener(); 118 | } 119 | break; 120 | 121 | case '/log': 122 | res.writeHead(200, { 'Access-Control-Allow-Origin': '*' }); 123 | res.end(); 124 | var messages = JSON.parse(path.query.log); 125 | for (var ii = 0; ii < messages.length; ++ii) { 126 | console.log(messages[ii][0], messages[ii][1]); 127 | } 128 | break; 129 | 130 | default: 131 | res.writeHead(400); 132 | res.end(); 133 | break; 134 | } 135 | }); 136 | server.timeout = 0; 137 | server.listen(9090); 138 | console.log( 139 | "Paste this into JS debug console in Screeps (*not* the Screeps console):\n"+ 140 | "var s = document.createElement('script');s.src='http://localhost:9090/inject';document.body.appendChild(s);" 141 | ); 142 | --------------------------------------------------------------------------------