commit 35bcd8d435c152d5e0463e93d033b6eb7c39932b Author: Kevin Alberts Date: Fri Nov 29 15:19:38 2019 +0100 Initial diff --git a/creep.custom.js b/creep.custom.js new file mode 100644 index 0000000..109f4d7 --- /dev/null +++ b/creep.custom.js @@ -0,0 +1,76 @@ +var customCreep = function() { + StructureSpawn.prototype.spawnCustomCreep = function(role, name, energy, behaviourLevel) { + var body_parts = []; + + var body_pre = []; + var body_component = []; + var body_post = [] + var body_pre_cost = 0; + var body_component_cost = 0; + var body_post_cost = 0; + + switch(role) { + case 'upgrader': + body_component = [WORK, CARRY, CARRY, MOVE]; + body_component_cost = 250; + break; + case 'harvester': + body_pre = [CARRY, CARRY, MOVE]; + body_pre_cost = 150; + body_component = [WORK]; + body_component_cost = 100; + break; + case 'refiller': + body_pre = [WORK, MOVE]; + body_pre_cost = 150; + body_component = [CARRY, CARRY, MOVE]; + body_component_cost = 150; + break; + case 'wallRepairer': + body_component = [WORK, CARRY, CARRY, MOVE, MOVE, MOVE]; + body_component_cost = 350; + break; + case 'pickup': + body_pre = [WORK, MOVE]; + body_pre_cost = 150; + body_component = [CARRY, CARRY, MOVE]; + body_component_cost = 150; + break; + case 'repairer': + body_component = [WORK, CARRY, CARRY, MOVE, MOVE]; + body_component_cost = 300; + break; + case 'builder': + body_component = [WORK, CARRY, CARRY, MOVE]; + body_component_cost = 250; + break; + default: + body_component = [WORK, CARRY, MOVE]; + body_component_cost = 200; + break; + } + + var num_body_part_patches = Math.floor((energy - (body_pre_cost + body_post_cost)) / body_component_cost); + + // Make pre-body + for(var part of body_pre) { + body_parts.push(part); + } + + // Make component body + for(var i = 0; i < num_body_part_patches; i++) { + for(var part of body_component) { + body_parts.push(part); + } + } + + // Make post-body + for(var part of body_post) { + body_parts.push(part); + } + + return this.spawnCreep(body_parts, name, {memory: {role: role}}); + } +} + +module.exports = customCreep; \ No newline at end of file diff --git a/energy.harvest.js b/energy.harvest.js new file mode 100644 index 0000000..2488456 --- /dev/null +++ b/energy.harvest.js @@ -0,0 +1,26 @@ +var harvester = require('energy.harvest.roundrobin'); + +var do_collect = function(creep, done_state, done_state_message) { + var source = null; + if(creep.memory.source_id) { + source = Game.getObjectById(creep.memory.source_id); + } else { + source = harvester.get_source(creep); + } + + if(creep.store.getFreeCapacity() > 0) { + if(creep.harvest(source) == ERR_NOT_IN_RANGE) { + creep.moveTo(source, {visualizePathStyle: {stroke: '#ffaa00'}}); + } + } else { + creep.memory.state = done_state; + harvester.release_source(creep); + creep.say(done_state_message); + } +} + +var baseHarvester = { + do_collect: do_collect, +} + +module.exports = baseHarvester; \ No newline at end of file diff --git a/energy.harvest.roundrobin.js b/energy.harvest.roundrobin.js new file mode 100644 index 0000000..d94bbf1 --- /dev/null +++ b/energy.harvest.roundrobin.js @@ -0,0 +1,33 @@ +var get_source = function(creep) { + var sources = creep.room.find(FIND_SOURCES); + var minSource = null; + var minSourceCount = null; + + for(let source of sources) { + var amount_at_source = Memory.rooms[creep.room.name].sources[source.id] - Memory.rooms[creep.room.name].sources_spots[source.id]; + if (minSourceCount == null || amount_at_source < minSourceCount) { + minSourceCount = amount_at_source; + minSource = source; + } + } + + Memory.rooms[creep.room.name].sources[minSource.id] += 1; + creep.memory.source_id = minSource.id; + return minSource; +} + +var release_source = function(creep) { + if(creep.memory.source_id) { + if(Memory.rooms[creep.room.name].sources[creep.memory.source_id] > 0){ + Memory.rooms[creep.room.name].sources[creep.memory.source_id] -= 1; + } + delete creep.memory.source_id; + } +} + +var roundRobinHarvester = { + get_source: get_source, + release_source: release_source +} + +module.exports = roundRobinHarvester; \ No newline at end of file diff --git a/energy.storage.js b/energy.storage.js new file mode 100644 index 0000000..619c585 --- /dev/null +++ b/energy.storage.js @@ -0,0 +1,30 @@ +// Finds the closest container that has something in it +var find_container = function(creep) { + return creep.pos.findClosestByPath(FIND_STRUCTURES, { + filter: (s) => { + return (s.structureType == STRUCTURE_CONTAINER || + s.structureType == STRUCTURE_STORAGE) && + s.store.getUsedCapacity(RESOURCE_ENERGY) > 0; + } + }); +} + +var do_collect = function(creep, done_state, done_state_message) { + var container = find_container(creep); + if (container) { + if(creep.withdraw(container, RESOURCE_ENERGY) == ERR_NOT_IN_RANGE) { + creep.moveTo(container, {visualizePathStyle: {stroke: '#ffaa00'}}); + } + } + if (creep.store.getFreeCapacity() == 0) { + // If we are full with energy, switch to the next state + creep.memory.state = done_state; + creep.say(done_state_message); + } +} + +var baseCollector = { + do_collect: do_collect, +} + +module.exports = baseCollector; \ No newline at end of file diff --git a/idle.base.js b/idle.base.js new file mode 100644 index 0000000..41373fd --- /dev/null +++ b/idle.base.js @@ -0,0 +1,43 @@ +var do_idle = function(creep) { + // No structure needs filling and my charge is full, move to an idle location + if (!creep.memory.idlePosition) { + var idlePosition; + switch (creep.room.name){ + case "E16N2": + min_x = 36; + min_y = 7; + max_x = 40; + max_y = 8; + + idlePosition = new RoomPosition(Math.floor(Math.random() * (max_x - min_x)) + min_x, Math.floor(Math.random() * (max_y - min_y)) + min_y, creep.room.name); + break; + default: + // Calculate a good idle position next to the closest spawn station + idlePosition = creep.pos.findClosestByRange(FIND_MY_SPAWNS).pos; + idlePosition.x = idlePosition.x - (Math.floor(Math.random() * 4) + 2); // Number between 2 and 4 + idlePosition.y = idlePosition.y + (Math.floor(Math.random() * 5) - 2); // Number between -2 and 2 + break; + } + creep.memory.idlePosition = {'x': idlePosition.x, 'y': idlePosition.y}; + } + + // If we are not at the idle position, move there. + if(!creep.pos.isEqualTo(creep.memory.idlePosition.x, creep.memory.idlePosition.y)) { + var result = creep.moveTo(new RoomPosition(creep.memory.idlePosition.x, creep.memory.idlePosition.y, creep.room.name), {visualizePathStyle: {stroke: '#aaaaaa'}}); + if (result == ERR_NO_PATH) { + creep.say('No path'); + delete creep.memory.idlePosition; + } + } +} + +var clear_idle = function(creep) { + delete creep.memory.idlePosition; +} + +var baseIdler = { + do_idle: do_idle, + clear_idle: clear_idle, +} + +module.exports = baseIdler; \ No newline at end of file diff --git a/main.js b/main.js new file mode 100644 index 0000000..02229b0 --- /dev/null +++ b/main.js @@ -0,0 +1,266 @@ +var _ = require('lodash'); + +var roleBase = require('role.base'); +var roleIdle = require('role.idle'); +var roleHarvester = require('role.harvester'); +var roleHarvesterV2 = require('role.harvester.v2'); +var roleRefiller = require('role.refiller'); +var roleUpgrader = require('role.upgrader'); +var roleBuilder = require('role.builder'); +var roleRepairer = require('role.repairer'); +var roleWallRepairer = require('role.wallrepairer'); +var rolePickup = require('role.pickup'); + +/* About behaviour levels: +Level -1 + Special level to make all creeps move to an idle location. + +Level 1 + The starting level, this can be run from the start of the map. + All bots will mine the resources they need, then do their task. + +Level 2 + This introduces refillers, overhauls harvesters and changes the behaviour of the rest. + Harvesters will deposit mined energy into a container or storage unit placed nearby. + Refillers will take energy to structures that need them. + Other creeps will take energy from the container/storage instead of mining them themselves. + + Requirements: Containers placed next to all free spots of the sources in the room. + Harvesters will then place themselves between the container and the source. + For example: (S = source, H = harvester, C = container, X = wall, _ = empty) + __C__ + _HHH_ + CHSXX + _HXXX + __XXX +*/ +var behaviourLevel = 2; + +require('creep.custom')(); + +// Profiler - https://github.com/screepers/screeps-profiler +const profiler = require('screeps-profiler'); +// Monkey-patch in the profiler +profiler.enable(); + +module.exports.loop = function () { + // Wrap my code in the profiler + profiler.wrap(function() { + + // Setup variables with in-game objects for use in the script + var towers = _.filter(Game.structures, {structureType: STRUCTURE_TOWER}); + var harvesters = _.filter(Game.creeps, (creep) => creep.memory.role == 'harvester'); + var refillers = _.filter(Game.creeps, (creep) => creep.memory.role == 'refiller'); + var builders = _.filter(Game.creeps, (creep) => creep.memory.role == 'builder'); + var upgraders = _.filter(Game.creeps, (creep) => creep.memory.role == 'upgrader'); + var repairers = _.filter(Game.creeps, (creep) => creep.memory.role == 'repairer'); + var wallRepairers = _.filter(Game.creeps, (creep) => creep.memory.role == 'wallRepairer'); + var pickups = _.filter(Game.creeps, (creep) => creep.memory.role == 'pickup'); + + // Cleanup memory + for(var name in Memory.creeps) { + if(!Game.creeps[name]) { + roleBase.clearMemory(name); + delete Memory.creeps[name]; + console.log('Clearing non-existing creep memory:', name); + } + } + + if(Memory.rooms == undefined) {Memory.rooms = {};} + for (var name in Game.rooms) { + if(Memory.rooms[name] == undefined) { + Memory.rooms[name] = {}; + } + if(Memory.rooms[name].repairs == undefined) { + Memory.rooms[name].repairs = [] + } + if(Memory.rooms[name].sources == undefined) { + Memory.rooms[name].sources = {}; + for(let source of Game.rooms[name].find(FIND_SOURCES)) { + Memory.rooms[name].sources[source.id] = 0; + } + } + if(Memory.rooms[name].sources_spots == undefined) { + Memory.rooms[name].sources_spots = {}; + for(let source of Game.rooms[name].find(FIND_SOURCES)) { + var num_spots = 0; + for (let x of [-1, 0, 1]) { + for (let y of [-1, 0, 1]) { + if(!(x == 0 && y == 0)){ + var pos = new RoomPosition(source.pos.x + x, source.pos.y + y, source.pos.roomName); + if(pos.lookFor(LOOK_TERRAIN) == "plain"){ + num_spots += 1; + } + } + } + } + Memory.rooms[name].sources_spots[source.id] = num_spots; + } + } + } + + // Tower auto-repair structures nearby + for(var tower_id in towers) { + var tower = towers[tower_id]; + // var closestDamagedStructure = tower.pos.findClosestByRange(FIND_STRUCTURES, { + // filter: (structure) => structure.hits < structure.hitsMax + // }); + // if(closestDamagedStructure) { + // tower.repair(closestDamagedStructure); + // } + + var closestHostile = tower.pos.findClosestByRange(FIND_HOSTILE_CREEPS); + if(closestHostile) { + tower.attack(closestHostile); + } + } + + // Auto-spawn new creeps + var to_spawn = []; + + var gameTime = Game.time.toString(); + var name_index = gameTime.substring(gameTime.length-4, gameTime.length); + + if(harvesters.length < 2) { + to_spawn.push(['harvester', 'Harvey' + name_index, [WORK, CARRY, MOVE]]); + } + // Refillers only necessary from behaviour level 2 + if(behaviourLevel >= 2) { + if(refillers.length < 2) { + to_spawn.push(['refiller', 'Reffy' + name_index, [WORK, CARRY, MOVE]]); + } + } + if(builders.length < 1) { + to_spawn.push(['builder', 'Bob' + name_index, [WORK, CARRY, MOVE]]); + } + if(repairers.length < 2) { + to_spawn.push(['repairer', 'Reppy' + name_index, [WORK, CARRY, MOVE]]); + } + if(upgraders.length < 1) { + to_spawn.push(['upgrader', 'Uppy' + name_index, [WORK, CARRY, MOVE]]); + } + if(wallRepairers.length < 1) { + to_spawn.push(['wallRepairer', 'Wally' + name_index, [WORK, CARRY, MOVE]]); + } + if(pickups.length < 1) { + to_spawn.push(['pickup', 'Picky' + name_index, [WORK, CARRY, MOVE]]); + } + + if(to_spawn.length > 0) { + for(var spawner_id in Game.spawns) { + var spawner = Game.spawns[spawner_id]; + if(!spawner.spawning){ + var entry = to_spawn.shift(); + console.log("Spawning " + entry[0]); + var spawned = false; + var max_available_energy = spawner.store.getCapacity(RESOURCE_ENERGY); + for(let extension of _.filter(Game.structures, {structureType: STRUCTURE_EXTENSION, room: spawner.room})) { + max_available_energy += extension.store.getCapacity(RESOURCE_ENERGY); + } + //max_available_energy = 300; + + var result = spawner.spawnCustomCreep(entry[0], entry[1], max_available_energy, behaviourLevel); + + if (result == OK) { + spawner.room.visual.text('Spawning ' + entry[1] + ' /w ' + max_available_energy + " energy.", spawner.pos.x + 1, spawner.pos.y, {align: 'left', opacity: 0.8}); + } + + // If there are no harvesters any more, and there is not enough energy, spawn a basic harvester. + if(harvesters.length == 0 && result == ERR_NOT_ENOUGH_ENERGY) { + result = spawner.spawnCreep([WORK, CARRY, MOVE], "BasicHarvester", {memory: {role: 'harvester'}}); + if (result == OK) { + spawner.room.visual.text('Spawning BasicHarvester.'); + } + } + // If there are no refillers any more, and there is not enough energy, spawn a basic refiller. + if(refillers.length == 0 && result == ERR_NOT_ENOUGH_ENERGY) { + result = spawner.spawnCreep([WORK, CARRY, MOVE], "BasicRefiller", {memory: {role: 'refiller'}}); + if (result == OK) { + spawner.room.visual.text('Spawning BasicRefiller.'); + } + } + + if (result != OK) { + switch (result) { + case ERR_BUSY: + console.log("Could not spawn " + entry[1] + ", spawner is busy."); + break; + case ERR_NOT_ENOUGH_ENERGY: + console.log("Could not spawn " + entry[1] + ", not enough energy."); + break; + case ERR_RCL_NOT_ENOUGH: + console.log("Could not spawn " + entry[1] + ", RCL not high enough."); + break; + default: + console.log("Could not spawn " + entry[1] + ", error code " + result); + break; + } + } else { + spawned = true; + } + } + } + if(!spawned){ + console.log("Could not spawn, all spawners are busy or errored."); + } + } + + // Execute creep ticks + for(var name in Game.creeps) { + var creep = Game.creeps[name]; + + var continue_with_role = roleBase.run(creep, behaviourLevel); + + // If we are at behaviour level -1, move all bots to an idle position + if (behaviourLevel == -1) { + roleIdle.run(creep, behaviourLevel); + } else { + // Else, continue with the normal roles + if(continue_with_role) { + switch(creep.memory.role) { + case "harvester": + if (behaviourLevel >= 2) { + roleHarvesterV2.run(creep, behaviourLevel); + } else { + roleHarvester.run(creep, behaviourLevel); + } + break; + case "refiller": + if (behaviourLevel >= 2) { + roleRefiller.run(creep, behaviourLevel); + } else { + // No defined behaviour for refillers on level 1, just act as a Harvester. + roleHarvester.run(creep, behaviourLevel); + } + break; + case "upgrader": + roleUpgrader.run(creep, behaviourLevel); + break; + case "builder": + roleBuilder.run(creep, behaviourLevel); + break; + case "repairer": + roleRepairer.run(creep, behaviourLevel); + break; + case "wallRepairer": + roleWallRepairer.run(creep, behaviourLevel); + break; + case "pickup": + rolePickup.run(creep, behaviourLevel); + break; + default: + console.log("Creep with role " + creep.memory.role + " has no defined behaviour."); + } + } + } + } + + // Print used CPU ticks every 10 game ticks + if(Game.time % 100 == 0) { + if(Game.cpu.limit){ + console.log("Used " + Game.cpu.getUsed() + " / " + Game.cpu.limit + "(L) / " + Game.cpu.tickLimit + " (B) CPU this tick. Bucket level: " + Game.cpu.bucket); + } + } + + }); +} \ No newline at end of file diff --git a/role.base.js b/role.base.js new file mode 100644 index 0000000..5677f97 --- /dev/null +++ b/role.base.js @@ -0,0 +1,40 @@ +var roleBase = { + clearMemory: function(creep_name) { + var memory = Memory.creeps[creep_name]; + console.log("Cleaning up memory for (late) creep " + creep_name); + + if(memory.source_id) { + for(var room in Game.rooms) { + if(Memory.rooms[room].sources[memory.source_id] != undefined){ + if(Memory.rooms[room].sources[memory.source_id] > 0){ + Memory.rooms[room].sources[memory.source_id] -= 1; + } + } + } + delete memory.source_id; + } + + if(memory.repairId) { + for(var room in Game.rooms) { + _.remove(Memory.rooms[room].repairs, (n) => n == memory.repairId); + delete memory.repairId; + delete memory.repairTarget; + } + } + }, + + /** + @param {Creep} creep + @param {int} behaviourLevel + **/ + run: function(creep, behaviourLevel) { + if(creep.ticksToLive <= 2) { + creep.say("Halp X.X!"); + return false; + } + + return true; + } +}; + +module.exports = roleBase; \ No newline at end of file diff --git a/role.builder.js b/role.builder.js new file mode 100644 index 0000000..2c37dd7 --- /dev/null +++ b/role.builder.js @@ -0,0 +1,69 @@ +var idler = require('idle.base'); +var harvester = require('energy.harvest'); +var collector = require('energy.storage'); + +var get_targets = function(creep) { + return creep.room.find(FIND_CONSTRUCTION_SITES); +} + +var roleBuilder = { + + /** + @param {Creep} creep + @param {int} behaviourLevel + **/ + run: function(creep, behaviourLevel) { + + switch (creep.memory.state) { + case "idle": + idler.do_idle(creep); + if (get_targets(creep).length > 0){ + // If there are any buildings to construct, switch back to the building state + idler.clear_idle(creep); + creep.memory.state = "build_structure"; + creep.say("building"); + } + if (creep.store.getFreeCapacity() > 0) { + // If we are not full with energy, switch to the get_energy state + idler.clear_idle(creep); + creep.memory.state = "get_energy"; + creep.say('Reloading...'); + } + break; + + case "build_structure": + var targets = get_targets(creep); + if(targets.length > 0) { + if(creep.build(targets[0]) == ERR_NOT_IN_RANGE) { + creep.moveTo(targets[0], {visualizePathStyle: {stroke: '#ffffff'}}); + } + } else { + // Else, go idle + creep.memory.state = "idle"; + creep.say('idling'); + } + if (creep.store.getUsedCapacity() == 0) { + // If we have no energy, switch to the get_energy state + creep.memory.state = "get_energy"; + creep.say('Reloading...'); + } + break; + + case "get_energy": + var energy_source; + if(behaviourLevel >= 2) { + energy_source = collector; + } else { + energy_source = harvester; + } + energy_source.do_collect(creep, "build_structure", "building"); + break; + + default: + creep.memory.state = "get_energy"; + creep.say("Reloading..."); + } + } +}; + +module.exports = roleBuilder; \ No newline at end of file diff --git a/role.harvester.js b/role.harvester.js new file mode 100644 index 0000000..e03bd85 --- /dev/null +++ b/role.harvester.js @@ -0,0 +1,68 @@ +var idler = require('idle.base'); +var harvester = require('energy.harvest'); + +var get_targets = function(creep) { + return creep.room.find(FIND_STRUCTURES, { + filter: (structure) => { + return (structure.structureType == STRUCTURE_EXTENSION || + structure.structureType == STRUCTURE_SPAWN || + structure.structureType == STRUCTURE_TOWER) && + structure.store.getFreeCapacity(RESOURCE_ENERGY) > 0; + } + }); +} + +var roleHarvester = { + + /** + @param {Creep} creep + @param {int} behaviourLevel + **/ + run: function(creep, behaviourLevel) { + switch (creep.memory.state) { + case "idle": + idler.do_idle(creep); + if (get_targets(creep).length > 0){ + // If there are any buildings to charge, switch back to the charge state + idler.clear_idle(creep); + creep.memory.state = "charge_structure"; + creep.say("Charging..."); + } + if (creep.store.getFreeCapacity() > 0) { + // If we are not full with energy, switch to the get_energy state + idler.clear_idle(creep); + creep.memory.state = "get_energy"; + creep.say('Harvesting...'); + } + break; + + case "charge_structure": + var targets = get_targets(creep); + if(targets.length > 0) { + if(creep.transfer(targets[0], RESOURCE_ENERGY) == ERR_NOT_IN_RANGE) { + creep.moveTo(targets[0], {visualizePathStyle: {stroke: '#449900'}}); + } + } else { + // Else, go idle + creep.memory.state = "idle"; + creep.say('Idling...'); + } + if (creep.store.getUsedCapacity() == 0) { + // If we are not full with energy, switch to the get_energy state + creep.memory.state = "get_energy"; + creep.say('Harvesting...'); + } + break; + + case "get_energy": + harvester.do_collect(creep, "charge_structure", "Charging..."); + break; + + default: + creep.memory.state = "get_energy"; + creep.say('Harvesting...'); + } + } +}; + +module.exports = roleHarvester; \ No newline at end of file diff --git a/role.harvester.v2.js b/role.harvester.v2.js new file mode 100644 index 0000000..bd77c9b --- /dev/null +++ b/role.harvester.v2.js @@ -0,0 +1,204 @@ +var idler = require('idle.base'); + +// Finds a new spot for the harvester to mine +var find_spot = function(creep) { + var sources = creep.room.find(FIND_SOURCES); + var possible_spots = {}; + var num_spots = {}; + var open_spots = {} + + for(let source of sources) { + for (let x of [-1, 0, 1]) { + for (let y of [-1, 0, 1]) { + if(!(x == 0 && y == 0)){ + var pos = new RoomPosition(source.pos.x + x, source.pos.y + y, source.pos.roomName); + if(pos.lookFor(LOOK_TERRAIN) == "plain"){ + if(num_spots[source.id] == undefined) { + num_spots[source.id] = 0; + } + num_spots[source.id] += 1; + if(pos.lookFor(LOOK_CREEPS).length == 0) { + if(possible_spots[source.id] == undefined) { + possible_spots[source.id] = []; + } + possible_spots[source.id].push([pos.x, pos.y, pos.roomName, source.id]); + if(open_spots[source.id] == undefined) { + open_spots[source.id] = 0; + } + open_spots[source.id] += 1; + } + } + } + } + } + } + + var best_source = null; + var best_occupied = null; + for (let source of sources) { + if (num_spots[source.id] != undefined && open_spots[source.id] != undefined){ + var occupied = num_spots[source.id] - open_spots[source.id]; + if ((best_occupied == null || occupied < best_occupied) && possible_spots[source.id] != undefined && possible_spots[source.id].length > 0) { + best_occupied = occupied; + best_source = source; + } + } + } + + if (possible_spots[best_source.id].length > 0){ + return possible_spots[best_source.id][0]; + } + + return null; +} + +// Finds the container near the current spot of the creep +var find_container = function(creep) { + if (creep.memory.spot) { + for (let x of [-1, 0, 1]) { + for (let y of [-1, 0, 1]) { + if(!(x == 0 && y == 0)){ + var pos = new RoomPosition(creep.memory.spot[0] + x, creep.memory.spot[1] + y, creep.memory.spot[2]); + var structs = pos.lookFor(LOOK_STRUCTURES); + for(let struct of structs) { + if (struct.structureType == STRUCTURE_CONTAINER || struct.structureType == STRUCTURE_STORAGE) { + return struct.id; + } + } + } + } + } + } + return null; +} + +var roleHarvester = { + + /** + @param {Creep} creep + @param {int} behaviourLevel + **/ + run: function(creep, behaviourLevel) { + switch (creep.memory.state) { + case "idle": + idler.do_idle(creep); + if(creep.memory.idle_time == undefined){ + creep.memory.idle_time = 0; + } + creep.memory.idle_time += 1; + + // If we've been idling for a while, check if there is a spot available + if (creep.memory.idle_time > 10) { + idler.clear_idle(creep); + delete creep.memory.idle_time; + creep.memory.state = "find_spot"; + creep.say("Looking..."); + } + break; + + case "find_spot": + // If we don't have a spot yet, find one + if (!creep.memory.spot) { + creep.memory.spot = find_spot(creep); + creep.memory.container = find_container(creep); + } + // If no spot was found, go back to idling + if (!creep.memory.spot) { + creep.memory.state = "idle"; + creep.say("No spot!"); + } else { + creep.memory.state = "walk_to_spot"; + creep.say("Walking..."); + } + break; + + case "walk_to_spot": + if (creep.memory.spot) { + if (creep.pos.x != creep.memory.spot[0] || creep.pos.y != creep.memory.spot[1]) { + creep.moveTo(creep.memory.spot[0], creep.memory.spot[1], {visualizePathStyle: {stroke: '#0044aa'}}) + // Check if our spot is still available + if ((new RoomPosition(creep.memory.spot[0], creep.memory.spot[1], creep.memory.spot[2])).lookFor(LOOK_CREEPS).length > 0){ + creep.memory.spot = null; + creep.memory.container = null; + creep.memory.state = "idle"; + creep.say("Occupied:("); + } + } else { + // We arrived! Start mining. + creep.memory.state = "mine_energy"; + creep.say("Mining..."); + } + } else { + // We don't have a spot + creep.memory.state = "find_spot"; + creep.say("Looking..."); + } + break; + + case "mine_energy": + if (creep.memory.spot) { + var source = Game.getObjectById(creep.memory.spot[3]); + if (source) { + if(creep.store.getFreeCapacity() > 0) { + if(creep.harvest(source) == ERR_NOT_IN_RANGE) { + // Source is gone? + creep.memory.spot = null; + creep.memory.container = null; + creep.memory.state = "find_spot"; + creep.say("Looking..."); + } + } else { + // We are full, dump the energy into the container + creep.memory.state = "dump_energy"; + creep.say("Dumping..."); + } + } else { + // No source? + creep.memory.spot = null; + creep.memory.container = null; + creep.memory.state = "find_spot"; + creep.say("Looking..."); + } + } else { + // We don't have a spot + creep.memory.state = "find_spot"; + creep.say("Looking..."); + } + break; + + case "dump_energy": + if (creep.memory.container) { + var container = Game.getObjectById(creep.memory.container); + if (container) { + if (container.store.getFreeCapacity(RESOURCE_ENERGY) > 0 && creep.store.getUsedCapacity() > 0) { + creep.transfer(container, RESOURCE_ENERGY); + } + } else { + // No container? + creep.memory.spot = null; + creep.memory.container = null; + creep.memory.state = "find_spot"; + creep.say("Looking..."); + } + if (creep.store.getUsedCapacity() == 0) { + // Everything dumped, go back to mining + creep.memory.state = "mine_energy"; + creep.say("Mining..."); + } + } else { + // No container? + creep.memory.spot = null; + creep.memory.container = null; + creep.memory.state = "find_spot"; + creep.say("Looking..."); + } + break; + + default: + creep.memory.state = "find_spot"; + creep.say('Looking...'); + } + } +}; + +module.exports = roleHarvester; diff --git a/role.idle.js b/role.idle.js new file mode 100644 index 0000000..5fd5e17 --- /dev/null +++ b/role.idle.js @@ -0,0 +1,14 @@ +var idler = require('idle.base'); + +var roleIdle = { + + /** + @param {Creep} creep + @param {int} behaviourLevel + **/ + run: function(creep, behaviourLevel) { + idler.do_idle(creep); + } +}; + +module.exports = roleIdle; \ No newline at end of file diff --git a/role.pickup.js b/role.pickup.js new file mode 100644 index 0000000..871b2d6 --- /dev/null +++ b/role.pickup.js @@ -0,0 +1,227 @@ +var idler = require('idle.base'); + +// Finds energy laying around on the map +var find_energy = function(creep) { + if (!creep.memory.spot) { + max_energy = 0; + max_energy_obj = null; + + for(let x of creep.room.find(FIND_DROPPED_RESOURCES, {filter: (s) => {return s.resourceType == RESOURCE_ENERGY}})){ + if(x.amount > max_energy) { + if(x.pos.lookFor(LOOK_CREEPS).length == 0){ + max_energy = x.amount; + max_energy_obj = x; + } + } + } + + for (let x of creep.room.find(FIND_TOMBSTONES, {filter: (t) => {return t.store.getUsedCapacity(RESOURCE_ENERGY) > 0}})) { + if(x.store.getUsedCapacity(RESOURCE_ENERGY) > max_energy) { + if(x.pos.lookFor(LOOK_CREEPS).length == 0){ + max_energy = x.store.getUsedCapacity(RESOURCE_ENERGY); + max_energy_obj = x; + } + } + } + + if(max_energy_obj != null){ + return [max_energy_obj.pos.x, max_energy_obj.pos.y, max_energy_obj.room.name, max_energy_obj.id]; + } + } +} + +// Finds the container near the current spot of the creep +var find_container = function(creep) { + var container = creep.pos.findClosestByRange(FIND_STRUCTURES, { + filter: (x) => { + return (x.structureType == STRUCTURE_CONTAINER || x.structureType == STRUCTURE_STORAGE) && x.store.getFreeCapacity(RESOURCE_ENERGY) > 0; + } + }); + if (container) { + return container.id; + } else { + return null; + } +} + +var rolePickup = { + + /** + @param {Creep} creep + @param {int} behaviourLevel + **/ + run: function(creep, behaviourLevel) { + switch (creep.memory.state) { + case "idle": + idler.do_idle(creep); + if(creep.memory.idle_time == undefined){ + creep.memory.idle_time = 0; + } + creep.memory.idle_time += 1; + + // If we've been idling for a while, check if there is a spot available + if (creep.memory.idle_time > 5) { + delete creep.memory.idle_time; + if(creep.store.getUsedCapacity() > 0){ + creep.memory.state = "walk_to_container"; + creep.say("Dropping..."); + } else { + creep.memory.state = "find_spot"; + creep.say("Looking..."); + } + } + break; + + case "find_spot": + // If we don't have a spot yet, find one + if (!creep.memory.spot) { + creep.memory.spot = find_energy(creep); + creep.memory.container = find_container(creep); + } + // If no spot was found, go back to idling + if (!creep.memory.spot) { + creep.memory.state = "idle"; + creep.say("No drop!"); + } else { + creep.memory.state = "walk_to_spot"; + creep.say("Walking..."); + } + break; + + case "walk_to_spot": + if (creep.memory.spot) { + if(creep.memory.spot[0] == null || creep.memory.spot[1] == null || creep.memory.spot[2] == null){ + // We don't have a spot + creep.memory.spot = null; + creep.memory.container = null; + creep.memory.state = "find_spot"; + creep.say("Looking..."); + } else { + if (creep.pos.x != creep.memory.spot[0] || creep.pos.y != creep.memory.spot[1]) { + creep.moveTo(creep.memory.spot[0], creep.memory.spot[1], {visualizePathStyle: {stroke: '#0044aa'}}) + // Check if our spot is still available + if ((new RoomPosition(creep.memory.spot[0], creep.memory.spot[1], creep.memory.spot[2])).lookFor(LOOK_CREEPS).length > 0){ + creep.memory.spot = null; + creep.memory.container = null; + creep.memory.state = "idle"; + creep.say("Occupied:("); + } + } else { + // We arrived! Start grabbing. + creep.memory.state = "grab_energy"; + creep.say("Grabbing..."); + } + } + } else { + // We don't have a spot + creep.memory.state = "find_spot"; + creep.say("Looking..."); + } + break; + + case "walk_to_container": + if (creep.memory.spot) { + delete creep.memory.spot; + } + if (creep.memory.container) { + + var container = Game.getObjectById(creep.memory.container); + + if (creep.pos.x != container.pos.x || creep.pos.y != container.pos.y) { + creep.moveTo(container, {visualizePathStyle: {stroke: '#0044aa'}}); + } else { + // We arrived! Start dropping. + creep.memory.state = "drop_energy"; + creep.say("Grabbing..."); + } + } else { + // We don't have a container + creep.memory.container = find_container(creep); + if(!creep.memory.container){ + creep.memory.state = "idle" + creep.say("No container:("); + } + } + break; + + case "grab_energy": + if (creep.memory.spot) { + + var target = Game.getObjectById(creep.memory.spot[3]); + if(target instanceof Tombstone) { + creep.withdraw(target, RESOURCE_ENERGY); + } else if (target instanceof Resource) { + creep.pickup(target); + } else { + creep.say("Unkn.Tgt."); + } + + // If we have more space, look for a new spot + if(creep.store.getFreeCapacity > 0) { + creep.memory.spot = null; + creep.memory.container = null; + creep.memory.state = "find_spot"; + creep.say("Looking..."); + // Else, go to drop off + } else { + // We are full, dump the energy into the container + creep.memory.state = "drop_energy"; + creep.say("Dumping..."); + } + } else { + // We don't have a spot + creep.memory.state = "find_spot"; + creep.say("Looking..."); + } + break; + + case "drop_energy": + if (creep.memory.container) { + var container = Game.getObjectById(creep.memory.container); + if (container) { + if (container.store.getFreeCapacity(RESOURCE_ENERGY) > 0 && creep.store.getUsedCapacity() > 0) { + if(creep.transfer(container, RESOURCE_ENERGY) == ERR_NOT_IN_RANGE) { + creep.moveTo(container); + } + } else if (container.store.getFreeCapacity(RESOURCE_ENERGY) == 0) { + // Find new container + creep.memory.container = find_container(creep); + if(!creep.memory.container){ + creep.memory.state = "idle" + creep.say("No container:("); + } + } + } else { + // No container? + creep.memory.container = find_container(creep); + if(!creep.memory.container){ + creep.memory.state = "idle" + creep.say("No container:("); + } + } + if (creep.store.getUsedCapacity() == 0) { + // Everything dumped, search for new energy + creep.memory.spot = null; + creep.memory.container = null; + creep.memory.state = "find_spot"; + creep.say("Grabbing..."); + } + } else { + // No container? + creep.memory.spot = null; + creep.memory.container = null; + creep.memory.state = "find_spot"; + creep.say("Looking..."); + } + break; + + default: + creep.memory.spot = null; + creep.memory.container = null; + creep.memory.state = "find_spot"; + creep.say('Looking...'); + } + } +}; + +module.exports = rolePickup; diff --git a/role.refiller.js b/role.refiller.js new file mode 100644 index 0000000..aaa0eeb --- /dev/null +++ b/role.refiller.js @@ -0,0 +1,78 @@ +var idler = require('idle.base'); +var collector = require('energy.storage'); + +// Finds the buildings that need energy +var find_targets = function(creep) { + var ref = creep.pos.findClosestByRange(FIND_STRUCTURES, { + filter: (structure) => { + return (structure.structureType == STRUCTURE_EXTENSION || + structure.structureType == STRUCTURE_SPAWN || + structure.structureType == STRUCTURE_TOWER) && + structure.store.getFreeCapacity(RESOURCE_ENERGY) > 0; + } + }); + + if (!ref) { + ref = creep.pos.findClosestByRange(FIND_STRUCTURES, { + filter: (s) => { + return (s.structureType == STRUCTURE_STORAGE && s.store.getFreeCapacity(RESOURCE_ENERGY) > 0); + } + }) + } + return ref; +} + +var roleRefiller = { + + /** + @param {Creep} creep + @param {int} behaviourLevel + **/ + run: function(creep, behaviourLevel) { + switch (creep.memory.state) { + case "idle": + idler.do_idle(creep); + if (find_targets(creep)){ + // If there are any buildings to charge, switch back to the charge state + idler.clear_idle(creep); + creep.memory.state = "charge_structure"; + creep.say("Charging..."); + } + if (creep.store.getFreeCapacity() > 0) { + // If we are not full with energy, switch to the get_energy state + idler.clear_idle(creep); + creep.memory.state = "get_energy"; + creep.say('Reloading...'); + } + break; + + case "charge_structure": + var target = find_targets(creep); + if(target) { + if(creep.transfer(target, RESOURCE_ENERGY) == ERR_NOT_IN_RANGE) { + creep.moveTo(target, {visualizePathStyle: {stroke: '#449900'}}); + } + } else { + // Else, go idle + creep.memory.state = "idle"; + creep.say('Idling...'); + } + if (creep.store.getUsedCapacity() == 0) { + // If we are not full with energy, switch to the get_energy state + creep.memory.state = "get_energy"; + creep.say('Reloading...'); + } + break; + + case "get_energy": + collector.do_collect(creep, "charge_structure", "Charging..."); + break; + + default: + creep.memory.state = "idle"; + creep.say('Idling...'); + } + } +}; + +module.exports = roleRefiller; diff --git a/role.repairer.js b/role.repairer.js new file mode 100644 index 0000000..6847bf9 --- /dev/null +++ b/role.repairer.js @@ -0,0 +1,143 @@ +var idler = require('idle.base'); +var wallRepairer = require('role.wallrepairer'); +var harvester = require('energy.harvest'); +var collector = require('energy.storage'); + +var get_target = function(creep) { + var result; + var repairTarget; + + result = creep.pos.findClosestByRange(FIND_MY_STRUCTURES, { + filter: (structure) => structure.hits < structure.hitsMax && structure.hits < 10000 && !Memory.rooms[creep.room.name].repairs.includes(structure.id) + }); + if(result){ + repairTarget = Math.min(11000, result.hitsMax); + } + + if(!result) { + result = creep.pos.findClosestByRange(FIND_STRUCTURES, { + filter: (structure) => structure.hits < structure.hitsMax && (structure.structureType == STRUCTURE_CONTAINER || structure.structureType == STRUCTURE_STORAGE) && structure.hits < 10000 && !Memory.rooms[creep.room.name].repairs.includes(structure.id) + }); + if(result){ + repairTarget = Math.min(11000, result.hitsMax); + } + } + + if(!result) { + result = creep.pos.findClosestByRange(FIND_STRUCTURES, { + filter: (structure) => structure.hits < structure.hitsMax && structure.structureType != STRUCTURE_WALL && structure.hits < 10000 && !Memory.rooms[creep.room.name].repairs.includes(structure.id) + }); + if(result){ + repairTarget = Math.min(11000, result.hitsMax); + } + } + + if(!result) { + result = creep.pos.findClosestByRange(FIND_STRUCTURES, { + filter: (structure) => structure.hits < structure.hitsMax && (structure.structureType == STRUCTURE_CONTAINER || structure.structureType == STRUCTURE_STORAGE) && !Memory.rooms[creep.room.name].repairs.includes(structure.id) + }); + if(result){ + repairTarget = result.hitsMax; + } + } + + if(!result) { + result = creep.pos.findClosestByRange(FIND_STRUCTURES, { + filter: (structure) => structure.hits < structure.hitsMax && structure.structureType != STRUCTURE_WALL && !Memory.rooms[creep.room.name].repairs.includes(structure.id) + }); + if(result){ + repairTarget = result.hitsMax; + } + } + + // If no result can be found, go repair a wall or something + if(!result){ + result = wallRepairer.get_target(creep); + repairTarget = -1; + } + + if(result){ + creep.memory.repairTarget = repairTarget; + creep.memory.repairId = result.id + Memory.rooms[creep.room.name].repairs.push(result.id); + } + + return result +} + +var roleRepairer = { + + /** + @param {Creep} creep + @param {int} behaviourLevel + **/ + run: function(creep, behaviourLevel) { + switch (creep.memory.state) { + case "idle": + idler.do_idle(creep); + if (get_target(creep)){ + // If there are any buildings to repair, switch back to the repair state + idler.clear_idle(creep); + creep.memory.state = "repair_structure"; + creep.say("repairing"); + } + if (creep.store.getFreeCapacity() > 0) { + // If we are not full with energy, switch to the get_energy state + idler.clear_idle(creep); + creep.memory.state = "get_energy"; + creep.say('Reloading...'); + } + break; + + case "repair_structure": + if(creep.memory.repairId){ + var struct = Game.getObjectById(creep.memory.repairId); + if (struct.hits >= creep.memory.repairTarget) { + _.remove(Memory.rooms[creep.room.name].repairs, (n) => n == creep.memory.repairId); + delete creep.memory.repairId; + delete creep.memory.repairTarget; + } + } + + if (!creep.memory.repairId) { + creep.memory.repairId = get_target(creep).id; + } + var closestDamagedStructure = Game.getObjectById(creep.memory.repairId); + if(closestDamagedStructure) { + if (creep.repair(closestDamagedStructure) == ERR_NOT_IN_RANGE) { + creep.moveTo(closestDamagedStructure, {visualizePathStyle: {stroke: '#FF9999'}}); + } + } else { + // Else, go idle + creep.memory.state = "idle"; + creep.say('idling'); + } + if (creep.store.getUsedCapacity() == 0) { + // If we are out of energy, get more! + creep.memory.state = "get_energy"; + creep.say('Reloading...'); + // Forget what we were doing, so we can pick the best repair option when we are done recharging + _.remove(Memory.rooms[creep.room.name].repairs, (n) => n == creep.memory.repairId); + delete creep.memory.repairId; + delete creep.memory.repairTarget; + } + break; + + case "get_energy": + var energy_source; + if(behaviourLevel >= 2) { + energy_source = collector; + } else { + energy_source = harvester; + } + energy_source.do_collect(creep, "repair_structure", "Repairing..."); + break; + + default: + creep.memory.state = "repair_structure"; + creep.say('repairing'); + } + } +}; + +module.exports = roleRepairer; \ No newline at end of file diff --git a/role.upgrader.js b/role.upgrader.js new file mode 100644 index 0000000..000b407 --- /dev/null +++ b/role.upgrader.js @@ -0,0 +1,63 @@ +var idler = require('idle.base'); +var harvester = require('energy.harvest'); +var collector = require('energy.storage'); + +var roleUpgrader = { + + /** + @param {Creep} creep + @param {int} behaviourLevel + **/ + run: function(creep, behaviourLevel) { + switch (creep.memory.state) { + case "idle": + idler.do_idle(creep); + if (creep.room.controller.progress < creep.room.controller.progressTotal){ + // If there are any buildings to upgrade, switch back to the upgrading state + idler.clear_idle(creep); + creep.memory.state = "upgrade_structure"; + creep.say("upgrading"); + } + if (creep.store.getFreeCapacity() > 0) { + // If we are not full with energy, switch to the get_energy state + idler.clear_idle(creep); + creep.memory.state = "get_energy"; + creep.say('Reloading...'); + } + break; + + case "upgrade_structure": + if (creep.room.controller.progress < creep.room.controller.progressTotal){ + if(creep.upgradeController(creep.room.controller) == ERR_NOT_IN_RANGE) { + creep.moveTo(creep.room.controller, {visualizePathStyle: {stroke: '#ffffff'}}); + } + } else { + // Else, go idle + creep.memory.state = "idle"; + creep.say('idling'); + } + if (creep.store.getUsedCapacity() == 0) { + // If we have no energy, switch to the get_energy state + creep.memory.state = "get_energy"; + creep.say('Reloading...'); + } + break; + + case "get_energy": + var energy_source; + if(behaviourLevel >= 2) { + energy_source = collector; + } else { + energy_source = harvester; + } + energy_source.do_collect(creep, "upgrade_structure", "Upgrading..."); + break; + + default: + creep.memory.state = "get_energy"; + creep.say("Reloading..."); + } + } +}; + +module.exports = roleUpgrader; \ No newline at end of file diff --git a/role.wallrepairer.js b/role.wallrepairer.js new file mode 100644 index 0000000..2fbe361 --- /dev/null +++ b/role.wallrepairer.js @@ -0,0 +1,80 @@ +var idler = require('idle.base'); +var harvester = require('energy.harvest'); +var collector = require('energy.storage'); + +var get_target = function(creep) { + var walls = creep.room.find(FIND_STRUCTURES, { + filter: (s) => s.structureType == STRUCTURE_WALL && s.hits < s.hitsMax + }); + for(var i = 0.0001; i < 1; i += 0.0001) { + for(var wall of walls) { + if ((wall.hits / wall.hitsMax) < i) { + return wall; + } + } + } + return null; +} + +var roleRepairer = { + + get_target: get_target, + + /** + @param {Creep} creep + @param {int} behaviourLevel + **/ + run: function(creep, behaviourLevel) { + switch (creep.memory.state) { + case "idle": + idler.do_idle(creep); + if (get_target(creep)){ + // If there are any buildings to repair, switch back to the repair state + idler.clear_idle(creep); + creep.memory.state = "repair_structure"; + creep.say("Repairing..."); + } + if (creep.store.getFreeCapacity() > 0) { + // If we are not full with energy, switch to the get_energy state + idler.clear_idle(creep); + creep.memory.state = "get_energy"; + creep.say('Reloading...'); + } + break; + + case "repair_structure": + var closestDamagedStructure = get_target(creep); + if(closestDamagedStructure) { + if (creep.repair(closestDamagedStructure) == ERR_NOT_IN_RANGE) { + creep.moveTo(closestDamagedStructure, {visualizePathStyle: {stroke: '#FF9999'}}); + } + } else { + // Else, go idle + creep.memory.state = "idle"; + creep.say('Idling...'); + } + if (creep.store.getUsedCapacity() == 0) { + // If we are out of energy, get more! + creep.memory.state = "get_energy"; + creep.say('Reloading...'); + } + break; + + case "get_energy": + var energy_source; + if(behaviourLevel >= 2) { + energy_source = collector; + } else { + energy_source = harvester; + } + energy_source.do_collect(creep, "repair_structure", "Repairing..."); + break; + + default: + creep.memory.state = "repair_structure"; + creep.say('Repairing...'); + } + } +}; + +module.exports = roleRepairer; \ No newline at end of file diff --git a/screeps-profiler.js b/screeps-profiler.js new file mode 100644 index 0000000..eb88293 --- /dev/null +++ b/screeps-profiler.js @@ -0,0 +1,324 @@ +let usedOnStart = 0; +let enabled = false; +let depth = 0; + +function setupProfiler() { + depth = 0; // reset depth, this needs to be done each tick. + Game.profiler = { + stream(duration, filter) { + setupMemory('stream', duration || 10, filter); + }, + email(duration, filter) { + setupMemory('email', duration || 100, filter); + }, + profile(duration, filter) { + setupMemory('profile', duration || 100, filter); + }, + background(filter) { + setupMemory('background', false, filter); + }, + restart() { + if (Profiler.isProfiling()) { + const filter = Memory.profiler.filter; + let duration = false; + if (!!Memory.profiler.disableTick) { + // Calculate the original duration, profile is enabled on the tick after the first call, + // so add 1. + duration = Memory.profiler.disableTick - Memory.profiler.enabledTick + 1; + } + const type = Memory.profiler.type; + setupMemory(type, duration, filter); + } + }, + reset: resetMemory, + output: Profiler.output, + }; + + overloadCPUCalc(); +} + +function setupMemory(profileType, duration, filter) { + resetMemory(); + const disableTick = Number.isInteger(duration) ? Game.time + duration : false; + if (!Memory.profiler) { + Memory.profiler = { + map: {}, + totalTime: 0, + enabledTick: Game.time + 1, + disableTick, + type: profileType, + filter, + }; + } +} + +function resetMemory() { + Memory.profiler = null; +} + +function overloadCPUCalc() { + if (Game.rooms.sim) { + usedOnStart = 0; // This needs to be reset, but only in the sim. + Game.cpu.getUsed = function getUsed() { + return performance.now() - usedOnStart; + }; + } +} + +function getFilter() { + return Memory.profiler.filter; +} + +const functionBlackList = [ + 'getUsed', // Let's avoid wrapping this... may lead to recursion issues and should be inexpensive. + 'constructor', // es6 class constructors need to be called with `new` +]; + +function wrapFunction(name, originalFunction) { + function wrappedFunction() { + if (Profiler.isProfiling()) { + const nameMatchesFilter = name === getFilter(); + const start = Game.cpu.getUsed(); + if (nameMatchesFilter) { + depth++; + } + const result = originalFunction.apply(this, arguments); + if (depth > 0 || !getFilter()) { + const end = Game.cpu.getUsed(); + Profiler.record(name, end - start); + } + if (nameMatchesFilter) { + depth--; + } + return result; + } + + return originalFunction.apply(this, arguments); + } + + wrappedFunction.toString = () => + `// screeps-profiler wrapped function:\n${originalFunction.toString()}`; + + return wrappedFunction; +} + +function hookUpPrototypes() { + Profiler.prototypes.forEach(proto => { + profileObjectFunctions(proto.val, proto.name); + }); +} + +function profileObjectFunctions(object, label) { + const objectToWrap = object.prototype ? object.prototype : object; + + Object.getOwnPropertyNames(objectToWrap).forEach(functionName => { + const extendedLabel = `${label}.${functionName}`; + + const isBlackListed = functionBlackList.indexOf(functionName) !== -1; + if (isBlackListed) { + return; + } + + const descriptor = Object.getOwnPropertyDescriptor(objectToWrap, functionName); + if (!descriptor) { + return; + } + + const hasAccessor = descriptor.get || descriptor.set; + if (hasAccessor) { + const configurable = descriptor.configurable; + if (!configurable) { + return; + } + + const profileDescriptor = {}; + + if (descriptor.get) { + const extendedLabelGet = `${extendedLabel}:get`; + profileDescriptor.get = profileFunction(descriptor.get, extendedLabelGet); + } + + if (descriptor.set) { + const extendedLabelSet = `${extendedLabel}:set`; + profileDescriptor.set = profileFunction(descriptor.set, extendedLabelSet); + } + + Object.defineProperty(objectToWrap, functionName, profileDescriptor); + return; + } + + const isFunction = typeof descriptor.value === 'function'; + if (!isFunction) { + return; + } + const originalFunction = objectToWrap[functionName]; + objectToWrap[functionName] = profileFunction(originalFunction, extendedLabel); + }); + + return objectToWrap; +} + +function profileFunction(fn, functionName) { + const fnName = functionName || fn.name; + if (!fnName) { + console.log('Couldn\'t find a function name for - ', fn); + console.log('Will not profile this function.'); + return fn; + } + + return wrapFunction(fnName, fn); +} + +const Profiler = { + printProfile() { + console.log(Profiler.output()); + }, + + emailProfile() { + Game.notify(Profiler.output()); + }, + + output(numresults) { + const displayresults = !!numresults ? numresults : 20; + if (!Memory.profiler || !Memory.profiler.enabledTick) { + return 'Profiler not active.'; + } + + const endTick = Math.min(Memory.profiler.disableTick || Game.time, Game.time); + const startTick = Memory.profiler.enabledTick + 1; + const elapsedTicks = endTick - startTick; + const header = 'calls\t\ttime\t\tavg\t\tfunction'; + const footer = [ + `Avg: ${(Memory.profiler.totalTime / elapsedTicks).toFixed(2)}`, + `Total: ${Memory.profiler.totalTime.toFixed(2)}`, + `Ticks: ${elapsedTicks}`, + ].join('\t'); + return [].concat(header, Profiler.lines().slice(0, displayresults), footer).join('\n'); + }, + + lines() { + const stats = Object.keys(Memory.profiler.map).map(functionName => { + const functionCalls = Memory.profiler.map[functionName]; + return { + name: functionName, + calls: functionCalls.calls, + totalTime: functionCalls.time, + averageTime: functionCalls.time / functionCalls.calls, + }; + }).sort((val1, val2) => { + return val2.totalTime - val1.totalTime; + }); + + const lines = stats.map(data => { + return [ + data.calls, + data.totalTime.toFixed(1), + data.averageTime.toFixed(3), + data.name, + ].join('\t\t'); + }); + + return lines; + }, + + prototypes: [ + { name: 'Game', val: Game }, + { name: 'Room', val: Room }, + { name: 'Structure', val: Structure }, + { name: 'Spawn', val: Spawn }, + { name: 'Creep', val: Creep }, + { name: 'RoomPosition', val: RoomPosition }, + { name: 'Source', val: Source }, + { name: 'Flag', val: Flag }, + ], + + record(functionName, time) { + if (!Memory.profiler.map[functionName]) { + Memory.profiler.map[functionName] = { + time: 0, + calls: 0, + }; + } + Memory.profiler.map[functionName].calls++; + Memory.profiler.map[functionName].time += time; + }, + + endTick() { + if (Game.time >= Memory.profiler.enabledTick) { + const cpuUsed = Game.cpu.getUsed(); + Memory.profiler.totalTime += cpuUsed; + Profiler.report(); + } + }, + + report() { + if (Profiler.shouldPrint()) { + Profiler.printProfile(); + } else if (Profiler.shouldEmail()) { + Profiler.emailProfile(); + } + }, + + isProfiling() { + if (!enabled || !Memory.profiler) { + return false; + } + return !Memory.profiler.disableTick || Game.time <= Memory.profiler.disableTick; + }, + + type() { + return Memory.profiler.type; + }, + + shouldPrint() { + const streaming = Profiler.type() === 'stream'; + const profiling = Profiler.type() === 'profile'; + const onEndingTick = Memory.profiler.disableTick === Game.time; + return streaming || (profiling && onEndingTick); + }, + + shouldEmail() { + return Profiler.type() === 'email' && Memory.profiler.disableTick === Game.time; + }, +}; + +module.exports = { + wrap(callback) { + if (enabled) { + setupProfiler(); + } + + if (Profiler.isProfiling()) { + usedOnStart = Game.cpu.getUsed(); + + // Commented lines are part of an on going experiment to keep the profiler + // performant, and measure certain types of overhead. + + // var callbackStart = Game.cpu.getUsed(); + const returnVal = callback(); + // var callbackEnd = Game.cpu.getUsed(); + Profiler.endTick(); + // var end = Game.cpu.getUsed(); + + // var profilerTime = (end - start) - (callbackEnd - callbackStart); + // var callbackTime = callbackEnd - callbackStart; + // var unaccounted = end - profilerTime - callbackTime; + // console.log('total-', end, 'profiler-', profilerTime, 'callbacktime-', + // callbackTime, 'start-', start, 'unaccounted', unaccounted); + return returnVal; + } + + return callback(); + }, + + enable() { + enabled = true; + hookUpPrototypes(); + }, + + output: Profiler.output, + + registerObject: profileObjectFunctions, + registerFN: profileFunction, + registerClass: profileObjectFunctions, +};