diff --git a/src/creeps/harvester.js b/src/creeps/harvester.js index b4522e0..4a569ab 100644 --- a/src/creeps/harvester.js +++ b/src/creeps/harvester.js @@ -1,4 +1,10 @@ -const { sum } = require("lodash"); +const { sum, isNil } = require("lodash"); + +const STATE_INIT = 0; +const STATE_GOING_TO_SOURCE = 1; +const STATE_HARVESTING = 2; +const STATE_GOING_TO_DESTINATION = 3; +const STATE_DELIVERING = 4; var roleHarvester = { @@ -15,20 +21,45 @@ var roleHarvester = { * @return {string} name of the new creep */ spawn: function(spawn, postfix, owner_id) { - if (spawn.spawnCreep(this.recommended_parts, "harvester-"+postfix, {dryRun: true}) === OK) { - console.log("roleHarvester: Spawn "+spawn.name+" is creating harvester-"+postfix+" with recommended parts ..."); - spawn.spawnCreep(this.recommended_parts, "harvester-"+postfix, {memory: {"owner": owner_id, "role": "Harv"}}); + let creep_name = "harvester-"+postfix; + if (spawn.spawnCreep(this.recommended_parts, creep_name, {dryRun: true}) === OK) { + console.log("roleHarvester: Spawn "+spawn.name+" is creating "+creep_name+" with recommended parts ..."); + spawn.spawnCreep(this.recommended_parts, creep_name, {memory: {"owner": owner_id, "role": "Harv"}}); } - else if (spawn.spawnCreep(this.minimum_parts, "harvester-"+postfix, {dryRun: true}) === OK) { - console.log("roleHarvester: Spawn "+spawn.name+" is creating harvester-"+postfix+" with minimum parts ..."); - spawn.spawnCreep(this.minimum_parts, "harvester-"+postfix, {memory: {"owner": owner_id, "role": "Harv"}}); + else if (spawn.spawnCreep(this.minimum_parts, creep_name, {dryRun: true}) === OK) { + console.log("roleHarvester: Spawn "+spawn.name+" is creating "+creep_name+" with minimum parts ..."); + spawn.spawnCreep(this.minimum_parts, creep_name, {memory: {"owner": owner_id, "role": "Harv"}}); } else { - console.log("roleHarvester: Spawn \""+spawn.name+"\" couldn't afford a harvester! D: Result: ", spawn.spawnCreep(this.minimum_parts, "harvester-"+postfix, {dryRun: true})); + console.log("roleHarvester: Spawn \""+spawn.name+"\" couldn't afford a harvester! D: Result: ", spawn.spawnCreep(this.minimum_parts, creep_name, {dryRun: true})); throw "Not enough energy!"; } + + Memory.creeps[creep_name].harvest_spot = { + id: null, + resource_type: null, + structure_pos: null, + goto_pos: null, + }; + Memory.creeps[creep_name].destination_spot = { + id: null, + structure_pos: null, + goto_pos: null, + }; - return "harvester-"+postfix; + return creep_name; + }, + + /** + * Checks if the creep has all the parts necessary to work its role + * @param {Creep} creep + * @return bool that indicates if the creep can fullfill its role + */ + can_work: function(creep) { + if (creep.getActiveBodyparts(MOVE) >= 1 && creep.getActiveBodyparts(WORK) >= 1 && creep.getActiveBodyparts(CARRY) >= 1) { + return true; + } + return false; }, /** @@ -36,23 +67,183 @@ var roleHarvester = { * @param {Creep} creep */ run: function(creep) { - if (creep.id === undefined) { // id is undefined if the creep was created this tick + if (creep.id === undefined) { // id is undefined if the creep was created this tick, should happen only once + this.switch_state(creep, STATE_INIT); return } + console.log("roleHarvester ["+creep.name+"]: run() " + creep + " id:", creep.id); // TODO implement state machine here about what the harvester is up to. Currently it only moves to the nearest source. + switch (creep.memory.state) { + case STATE_INIT: + this.init_creep(creep); + break; + case STATE_GOING_TO_SOURCE: + if (!creep.spawning) { + this.go_to_source(creep); + } + break; + case STATE_HARVESTING: + this.harvest(creep); + break; + case STATE_GOING_TO_DESTINATION: + this.go_to_destination(creep); + break; + case STATE_DELIVERING: + this.delivering(creep); + break; + default: + console.log("roleHarvester ["+creep.name+"]: run() unknown state!"); + //this.switch_state(creep, STATE_INIT); + }; + }, - let goals = _.map(creep.room.find(FIND_SOURCES), function(source) { - return {pos: source.pos, range: 1}; - }); + /** + * Switches the state of the creep by editing the memory + * @param {Creep} creep + * @param {int} state State constant as given (e.g. STATE_HARVESTING) + */ + switch_state: function(creep, state) { + console.log("roleHarvester ["+creep.name+"]: Changing state to "+state); + console.log(creep, creep.memory); + creep.memory.state = state + }, + + /** + * Inits creep and sets the assigned source to be the nearest source. + * @param {*} creep + */ + init_creep: function(creep) { // TODO make source assignment aware of other harvesters to spread them + if (creep.memory.harvest_spot.id !== null) { + // harvest spot has already been given (by overlord?). Don't have to decide on my own! + } + else { + let room_sources = creep.room.find(FIND_SOURCES); + let goals = _.map(room_sources, function(source) { + return {pos: source.pos, range: 1}; + }); + let nearest_source = PathFinder.search(creep.pos, goals); + + //console.log("roleHarvester ["+creep.name+"]: init_creep() remembering target position: " + nearest_source.path[nearest_source.path.length-1]) + for (let source of room_sources) { + if (source.pos.isNearTo(nearest_source.path[nearest_source.path.length-1])) { + creep.memory.harvest_spot.id = source.id; + creep.memory.harvest_spot.structure_pos = source.pos; + creep.memory.harvest_spot.goto_pos = nearest_source.path[nearest_source.path.length-1]; + creep.memory.harvest_spot.resource_type = RESOURCE_ENERGY; + break; + } + } + } + + if (creep.memory.destination_spot.structure_pos !== null && creep.memory.destination_spot.goto_pos !== null) { + // destination spot has already been given (by the overlord?). Don't have to decide on my own! + } + else if (creep.memory.destination_spot.structure_pos !== null) { // goto_pos === null and we have to find it on our own later + // + } + else { + console.log("roleHarvester ["+creep.name+"] init_creep(): Don't have a destination_spot and autonomously finding one is not implemented!"); + throw "No Destination spot"; + } - let nearest_source = PathFinder.search(creep.pos, goals); - console.log("roleHarvester ["+creep.name+"]: run() moving creept towards " + nearest_source.path[nearest_source.path.length-1], nearest_source); - console.log("roleHarvester ["+creep.name+"]: run() moving to direction " + nearest_source.path[0]); - creep.moveTo(nearest_source.path[0]); + console.log("roleHarvester ["+creep.name+"] init_creep(): harvest task has been setup and committed to memory!") + + this.switch_state(creep, STATE_GOING_TO_SOURCE); + }, + + /** + * Moves the creep to its assigned source. The assigned source is a roomPosition in creep.memory.harvest_target_pos + * @param {Creep} creep + */ + go_to_source: function(creep) { + if (creep.memory.harvest_spot.goto_pos === null) { // find goto pos, if needed + let structure_pos = new RoomPosition(creep.memory.harvest_spot.structure_pos.x, creep.memory.harvest_spot.structure_pos.y, creep.memory.hravest_spot.structure_pos.roomName); + let result = PathFinder.search(creep.pos, {pos: structure_pos, range: 1}); + creep.memory.harvest_spot.goto_pos = result.path[result.path.length-1]; + } + + let target_pos = new RoomPosition(creep.memory.harvest_spot.goto_pos.x, creep.memory.harvest_spot.goto_pos.y, creep.memory.harvest_spot.goto_pos.roomName); + if (creep.pos.isEqualTo(target_pos)) { + console.log("roleHarvester ["+creep.name+"]: go_to_source() arrived at target!"); + this.switch_state(creep, STATE_HARVESTING); + } + + //console.log("roleHarvester ["+creep.name+"]: go_to_source() moving creep towards " + target_pos); + creep.moveTo(target_pos, {visualizePathStyle: {stroke: '#ffaa00'}}); + }, + + /** + * Lets the creep harvest its assigned source + * @param {Creep} creep + */ + harvest: function(creep) { + if (creep.store.getFreeCapacity(creep.memory.harvest_spot.resource_type) > 0) { + let target = Game.getObjectById(creep.memory.harvest_spot.id); + let err = creep.harvest(target); + if (err != OK) { + console.log("roleHarvester [ERROR]["+creep.name+"] harvest(): Failed to harvest at target "+target.pos+" with error: "+err); + if (err != ERR_NOT_IN_RANGE) { + console.log("roleHarvester ["+creep.name+"] harvest(): Failed to harvest because of wrong goto_pos trying to recalculate and switch back to moving ..."); + creep.memory.harvest_spot.structure_pos = target.pos; + let result = PathFinder.search(creep.pos, {pos: target.pos, range: 1}); + creep.memory.harvest_spot.goto_pos = result.path[result.path.length-1]; + this.switch_state(creep, STATE_GOING_TO_SOURCE); + } + } + } + else { + this.switch_state(creep, STATE_GOING_TO_DESTINATION); + } + }, + + /** + * Goes to the delivery destination of the creep as dictated by destination_spot in its memory + * @param {Creep} creep + */ + go_to_destination: function(creep) { + if (creep.memory.destination_spot.goto_pos === null) { // find goto pos, if needed + let structure_pos = new RoomPosition(creep.memory.destination_spot.structure_pos.x, creep.memory.destination_spot.structure_pos.y, creep.memory.destination_spot.structure_pos.roomName); + let result = PathFinder.search(creep.pos, {pos: structure_pos, range: 1}); + creep.memory.destination_spot.goto_pos = result.path[result.path.length-1]; + } + + let target_pos = new RoomPosition(creep.memory.destination_spot.goto_pos.x, creep.memory.destination_spot.goto_pos.y, creep.memory.destination_spot.goto_pos.roomName); + if (creep.pos.isEqualTo(target_pos)) { + console.log("roleHarvester ["+creep.name+"]: go_to_destination() arrived at target!"); + this.switch_state(creep, STATE_DELIVERING); + } + + creep.moveTo(target_pos, {visualizePathStyle: {stroke: '#ffaa00'}}); + }, + + /** + * Lets the creep store its resources into the destination defined by the memory destination_spot + * @param {Creep} creep + */ + delivering: function(creep) { + let had_issue = false; + let storage = Game.getObjectById(creep.memory.destination_spot.id); + for (const resource_type in creep.store) { + console.log("roleHarvester ["+creep.name+"] delivering(): "+resource_type); + let err = creep.transfer(storage, resource_type); + + if (err != OK) { + had_issue = true; + console.log("roleHarvester [ERROR]["+creep.name+"] delivering(): Failed to transfer resources with error: "+err); + creep.say("ERROR!"); + } + } + + if (!had_issue) { + this.switch_state(creep, STATE_GOING_TO_SOURCE); + } + else { + // TODO what to do if delivery failed? + } }, }; diff --git a/src/overlords/setup_overlord.js b/src/overlords/setup_overlord.js index b207c5b..700fbe4 100644 --- a/src/overlords/setup_overlord.js +++ b/src/overlords/setup_overlord.js @@ -4,14 +4,14 @@ var harvester = require('creeps_harvester'); /** * Overlord that sets up a room given only a single spawn with a minimum energy to build one harvester. * - * Setup is considered finished once 3 spawns have been built and at least one creep is maintaining the RCL + * Setup is considered finished once 3 spawns have been built and at least one creep is maintaining the RCL and at least one harvester is working */ /** Checks the room and sets out the tasks to achieve the goal */ -setup_overlord.prototype.run = function () { // TODO somehow these aren't changing anything +setup_overlord.prototype.run = function () { console.log("SetupOverlord: Running \""+this.id+"\"! :) Chilling for now ..."); - console.log("SetupOverlord: has the following underlings: ", this.underlings['spawns'], this.underlings['creeps']); + console.log("SetupOverlord: \""+this.id+"\"has the following underlings: ", this.underlings['spawns'], this.underlings['creeps']); if (this.underlings['creeps'].length === 0 && this.underlings['spawns'].length === 0) { // got NOTHING to work with console.log("SetupOverlord: ERROR "+this.id+" has nothing! ;W;"); @@ -24,7 +24,8 @@ setup_overlord.prototype.run = function () { // TODO somehow these aren't chang if (spawn.store[RESOURCE_ENERGY] > harvester.min_cost) { // can afford a harvester console.log("SetupOverlord: Spawn "+spawn.name+" can afford a harvester! ("+harvester.min_cost+") \\o/"); let new_harvester_name = harvester.spawn(spawn, this.id+"-"+this.underlings['creeps'].length, this.id); - this.add_creep(new_harvester_name); // TODO replace with ID? creep.id is only defined next tick though ... + this.set_creep_destination_spot(new_harvester_name, spawn); + this.add_creep(new_harvester_name); break; } } @@ -32,4 +33,32 @@ setup_overlord.prototype.run = function () { // TODO somehow these aren't chang } }; +/** + * Set a creeps destination spot in their memory. This defines where a creep delivers their stuff to. + * The goto_pos is set to null to indicate to the creep to find their standing spot on their own. + * + * @param {string} creep name of the creep to which this deliver destination will be given + * @param {RoomPosition} target must be a game object with an id and a pos property + */ +setup_overlord.prototype.set_creep_destination_spot = function(creep, target) { + console.log("SetupOverlord ["+this.id+"]: Instructing creep \""+creep+"\" to set their destination to "+target.pos); + Memory.creeps[creep].destination_spot.id = target.id; + Memory.creeps[creep].destination_spot.structure_pos = target.pos; + Memory.creeps[creep].destination_spot.goto_pos = null; +}; + +/** + * Set a creeps destination spot in their memory. This defines where a creep delivers their stuff to. + * The goto_pos is set to null to indicate to the creep to find their standing spot on their own. + * + * @param {string} creep name of the creep to which this deliver destination will be given + * @param {RoomPosition} target must be a game object with an id and a pos property + */ + setup_overlord.prototype.set_creep_harvest_spot = function(creep, target) { + console.log("SetupOverlord ["+this.id+"]: Instructing creep \""+creep+"\" to set their destination to "+target.pos); + Memory.creeps[creep].harvest_spot.id = target.id; + Memory.creeps[creep].harvest_spot.structure_pos = target.pos; + Memory.creeps[creep].harvest_spot.goto_pos = null; +}; + module.exports = setup_overlord; \ No newline at end of file