From 35bcd8d435c152d5e0463e93d033b6eb7c39932b Mon Sep 17 00:00:00 2001 From: Kevin Alberts Date: Fri, 29 Nov 2019 15:19:38 +0100 Subject: [PATCH] Initial --- creep.custom.js | 76 ++++++++ energy.harvest.js | 26 +++ energy.harvest.roundrobin.js | 33 ++++ energy.storage.js | 30 ++++ idle.base.js | 43 +++++ main.js | 266 ++++++++++++++++++++++++++++ role.base.js | 40 +++++ role.builder.js | 69 ++++++++ role.harvester.js | 68 ++++++++ role.harvester.v2.js | 204 ++++++++++++++++++++++ role.idle.js | 14 ++ role.pickup.js | 227 ++++++++++++++++++++++++ role.refiller.js | 78 +++++++++ role.repairer.js | 143 ++++++++++++++++ role.upgrader.js | 63 +++++++ role.wallrepairer.js | 80 +++++++++ screeps-profiler.js | 324 +++++++++++++++++++++++++++++++++++ 17 files changed, 1784 insertions(+) create mode 100644 creep.custom.js create mode 100644 energy.harvest.js create mode 100644 energy.harvest.roundrobin.js create mode 100644 energy.storage.js create mode 100644 idle.base.js create mode 100644 main.js create mode 100644 role.base.js create mode 100644 role.builder.js create mode 100644 role.harvester.js create mode 100644 role.harvester.v2.js create mode 100644 role.idle.js create mode 100644 role.pickup.js create mode 100644 role.refiller.js create mode 100644 role.repairer.js create mode 100644 role.upgrader.js create mode 100644 role.wallrepairer.js create mode 100644 screeps-profiler.js 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, +};