TooAngel/screeps

View on GitHub
src/prototype_creep_resources.js

Summary

Maintainability
F
4 days
Test Coverage
'use strict';

Creep.pickableResources = function(creep) {
  return (object) => creep.pos.isNearTo(object);
};

Creep.prototype.universalBeforeStorage = function() {
  this.creepLog(`universalBeforeStorage`);
  if (this.store.getFreeCapacity() === 0 && this.store.getUsedCapacity(RESOURCE_ENERGY) === 0) {
    const resourceToDrop = Object.keys(this.store)[0];
    this.drop(resourceToDrop, this.store[resourceToDrop]);
  }
  const methods = [];

  methods.push(Creep.getEnergy);

  if (this.room.controller && (this.room.controller.ticksToDowngrade < CONTROLLER_DOWNGRADE[this.room.controller.level] / 10 || this.room.controller.level === 1)) {
    methods.push(Creep.upgradeControllerTask);
  }
  if (!this.room.isConstructingSpawnEmergency()) {
    const universals = this.room.findCreep('universal').map((creep) => creep.name);
    universals.sort();
    if (universals.indexOf(this.name) < 2) {
      methods.push(Creep.transferEnergy);
    }
  }

  const structures = this.room.findBuildingConstructionSites();
  if (structures.length > 0) {
    methods.push(Creep.constructTask);
  }

  if (this.room.controller && this.room.controller.level < 9) {
    methods.push(Creep.upgradeControllerTask);
  } else {
    methods.push(Creep.repairStructure);
  }
  // this.say('startup', true);
  Creep.execute(this, methods);
  return true;
};

Creep.prototype.checkCarryEnergyForBringingBackToStorage = function(otherCreep) {
  let offset = 0;
  if (otherCreep) {
    offset = otherCreep.store.getUsedCapacity();
  }

  // define minimum carryPercentage to move back to storage
  let carryPercentage = config.carry.carryPercentageHighway;
  if (this.room.name === this.memory.routing.targetRoom) {
    carryPercentage = config.carry.carryPercentageExtern;
  }
  if (this.inBase()) {
    carryPercentage = config.carry.carryPercentageBase;
  }

  return offset + this.store.getUsedCapacity() > carryPercentage * this.store.getCapacity();
};

Creep.prototype.pickupWhileMoving = function() {
  if (this.inBase() && this.memory.routing.pathPos < 2) {
    return false;
  }

  if (_.sum(this.store) === this.store.getCapacity()) {
    return false;
  }

  const resources = this.room.find(FIND_DROPPED_RESOURCES, {
    filter: Creep.pickableResources(this),
  });

  if (resources.length > 0) {
    const resource = resources[0];
    const amount = this.pickupOrWithdrawFromSourcer(resource);
    return _.sum(this.store) + amount > 0.5 * this.store.getCapacity();
  }

  if (this.room.name === this.memory.routing.targetRoom) {
    const containers = this.pos.findInRangeStructuresWithUsableEnergy(1);

    for (const container of containers) {
      // To avoid carry withdrawing energy from base storage
      if (container.structureType === STRUCTURE_STORAGE && container.id !== this.memory.routing.targetId) {
        continue;
      }
      if (container.store[RESOURCE_ENERGY]) {
        this.withdraw(container, RESOURCE_ENERGY);
        return container.store[RESOURCE_ENERGY] > 9;
      }
    }
  }
  return false;
};

Creep.prototype.withdrawEnergyFromTarget = function(target) {
  let returnValue = false;
  const returnCode = this.withdraw(target, RESOURCE_ENERGY);
  if (returnCode === OK) {
    returnValue = true;
  }
  return returnValue;
};

Creep.prototype.withdrawResourcesFromTarget = function(target) {
  let returnValue = false;
  const resources = Object.keys(target.store);
  for (const resource of resources) {
    if (!returnValue) {
      returnValue = this.withdraw(target, resource) === OK;
    }
  }
  return returnValue;
};

Creep.prototype.withdrawResourcesFromTargets = function(targets, onlyEnergy) {
  let returnValue = false;
  for (const target of targets) {
    if (!returnValue) {
      returnValue = onlyEnergy ? this.withdrawEnergyFromTarget(target) : this.withdrawResourcesFromTarget(target);
    }
  }
  return returnValue;
};

Creep.prototype.withdrawTombstone = function(onlyEnergy) {
  onlyEnergy = onlyEnergy || false;
  let returnValue = false;
  // FIND_TOMBSTONES and get them empty first
  const tombstones = this.pos.findInRange(FIND_TOMBSTONES, 1);
  if (tombstones.length > 0) {
    if (onlyEnergy) {
      returnValue = this.withdrawResourcesFromTargets(tombstones, true);
    } else {
      returnValue = this.withdrawResourcesFromTargets(tombstones);
    }
  }
  return returnValue;
};

Creep.prototype.withdrawRuin = function(onlyEnergy) {
  onlyEnergy = onlyEnergy || false;
  let returnValue = false;
  // FIND_RUINS and get them empty first
  const ruins = this.pos.findInRange(FIND_RUINS, 1);
  if (ruins.length > 0) {
    if (onlyEnergy) {
      returnValue = this.withdrawResourcesFromTargets(ruins, true);
    } else {
      returnValue = this.withdrawResourcesFromTargets(ruins);
    }
  }
  return returnValue;
};

Creep.prototype.withdrawContainers = function() {
  let returnValue = false;
  const containers = this.pos.findInRangeStructures(FIND_STRUCTURES, 1, [STRUCTURE_CONTAINER]);
  if (containers.length > 0) {
    const returnCode = this.withdraw(containers[0], RESOURCE_ENERGY);
    if (returnCode === OK) {
      returnValue = true;
    }
  }
  return returnValue;
};

Creep.prototype.giveSourcersEnergy = function() {
  let returnValue = false;
  const sourcers = this.pos.findInRangeSourcer(1);

  if (sourcers.length > 0) {
    for (const resource of Object.keys(sourcers[0].store)) {
      const returnCode = sourcers[0].transfer(this, resource);
      if (returnCode === OK) {
        returnValue = true;
      }
    }
  }
  return returnValue;
};

Creep.prototype.pickupEnergy = function() {
  if (this.pickupEnergyFromGround()) {
    return true;
  }
  if (this.withdrawContainers()) {
    return true;
  }

  if (this.withdrawTombstone()) {
    return true;
  }

  if (this.withdrawRuin()) {
    return true;
  }

  return this.giveSourcersEnergy();
};

Creep.prototype.pickupEnergyFromGround = function() {
  const resources = this.pos.findInRange(FIND_DROPPED_RESOURCES, 1, {
    filter: {resourceType: RESOURCE_ENERGY},
  });
  if (resources.length > 0) {
    const resource = resources[0];
    const returnCode = this.pickup(resource);
    if (returnCode === OK) {
      if (resource.amount >= this.store.getFreeCapacity()) {
        return true;
      }
    }
    return false;
  }
};

const canStoreEnergy = function(object) {
  const structureTypes = [STRUCTURE_CONTROLLER, STRUCTURE_ROAD, STRUCTURE_WALL, STRUCTURE_RAMPART, STRUCTURE_OBSERVER];
  if (structureTypes.indexOf(object.structureType) >= 0) {
    return false;
  }
  return true;
};

const energyAcceptingLink = function(object, room) {
  if (object.structureType === STRUCTURE_LINK) {
    for (let i = 0; i < 3; i++) {
      const linkPos = room.data.positions.structure.link[i];
      if (object.pos.isEqualTo(linkPos.x, linkPos.y)) {
        return false;
      }
    }
  }
  return true;
};

const terminalAvailable = function(object) {
  if (object.structureType === STRUCTURE_TERMINAL && (object.store.energy || 0) > config.terminal.maxEnergyAmount) {
    return false;
  }
  return true;
};

const universalTarget = function(creep, object) {
  if (creep.memory.role === 'universal') {
    if (object.structureType === STRUCTURE_STORAGE || object.structureType === STRUCTURE_LINK) {
      return false;
    }
  }
  return true;
};

const filterTransferable = function(creep, object) {
  if (!canStoreEnergy(object)) {
    return false;
  }

  if (!energyAcceptingLink(object, creep.room)) {
    return false;
  }

  if (!terminalAvailable(object)) {
    return false;
  }

  if (!universalTarget(creep, object)) {
    return false;
  }

  if (object.structureType === STRUCTURE_STORAGE ?
    _.sum(object.store) + _.sum(creep.store) > object.storeCapacity :
    object.energy === object.energyCapacity) {
    return false;
  }

  return true;
};

Creep.prototype.transferAllResources = function(structure) {
  let transferred = false;
  for (const resource in this.store) {
    if (!resource) {
      continue;
    }
    const returnCode = this.transfer(structure, resource);
    if (returnCode === OK) {
      let transferableEnergy = structure.energyCapacity - structure.energy;
      if (structure.structureType === STRUCTURE_STORAGE) {
        transferableEnergy = structure.storeCapacity - _.sum(structure.store);
      }
      transferred = Math.min(this.store[resource], transferableEnergy);
    }
  }
  return transferred;
};

Creep.prototype.transferToStructures = function() {
  if (_.sum(this.store) === 0) {
    return false;
  }

  let transferred = false;
  const look = this.room.lookForAtArea(
    LOOK_STRUCTURES,
    Math.max(1, Math.min(48, this.pos.y - 1)),
    Math.max(1, Math.min(48, this.pos.x - 1)),
    Math.max(1, Math.min(48, this.pos.y + 1)),
    Math.max(1, Math.min(48, this.pos.x + 1)),
    true);
  for (const item of look) {
    if (filterTransferable(this, item.structure)) {
      if (transferred) {
        return {
          moreStructures: true,
          // TODO handle different type of resources on the structure side
          transferred: transferred,
        };
      }
      transferred = this.transferAllResources(item.structure);
    }
  }
  if (transferred) {
    return {
      moreStructures: false,
      transferred: transferred,
    };
  }
  return false;
};

Creep.prototype.getEnergyFromSourcer = function() {
  const sourcers = this.pos.findInRangeSourcer(0);
  if (sourcers.length > 0) {
    const returnCode = sourcers[0].transfer(this, RESOURCE_ENERGY);
    this.say('rr:' + returnCode);
    if (returnCode === OK) {
      return true;
    }
  }
  return false;
};

Creep.prototype.moveToSource = function(source, swarm = false) {
  this.memory.routing.reverse = false;
  if (swarm && this.pos.inRangeTo(source, 3)) {
    this.moveToMy(source.pos);
  } else if (this.room.memory.misplacedSpawn || (this.room.controller && this.room.controller.level < 2)) {
    this.moveToMy(source.pos);
  } else {
    const route = [{'name': this.room.name}];
    const routePos = 0;
    const start = 'pathStart';
    const target = source.id;
    const path = this.room.getPath(route, routePos, start, target);
    this.memory.routing.pathPos = this.getPathPos(path);
    const directions = this.getDirections(path);
    this.moveByPathMy(path, this.memory.routing.pathPos, directions);
  }
  return true;
};

Creep.prototype.harvestSource = function(source) {
  this.harvest(source);
  if (this.store.energy === this.store.getCapacity() && this.store.getCapacity() > 0) {
    const creepsWithoutEnergy = this.pos.findInRangeCreepWithoutEnergy(1);
    if (creepsWithoutEnergy.length > 0) {
      this.transfer(creepsWithoutEnergy[0], RESOURCE_ENERGY);
    }
  }

  // TODO Somehow we move before preMove, canceling here
  this.cancelOrder('move');
  this.cancelOrder('moveTo');
  return true;
};

Creep.prototype.getSourceToHarvest = function(swarmSourcesFilter) {
  let source;
  if (this.memory.source) {
    source = Game.getObjectById(this.memory.source);
    if (source === null || source.energy === 0) {
      source = this.pos.getClosestSource(swarmSourcesFilter);
    }
  } else {
    source = this.pos.getClosestSource(swarmSourcesFilter);
  }
  return source;
};

Creep.prototype.getEnergyFromSource = function() {
  let swarm = false;
  let swarmSourcesFilter;
  if (config.swarmSourceHarvestingMaxParts < this.body.filter((b) => b.type === WORK).length) {
    swarm = true;
    swarmSourcesFilter = (source) => source.pos.hasNonObstacleAdjacentPosition() || this.pos.isNearTo(source);
  }
  const source = this.getSourceToHarvest(swarmSourcesFilter);

  this.memory.source = source.id;
  const range = this.pos.getRangeTo(source);
  if (this.store.energy > 0 && range > 1) {
    this.memory.hasEnergy = true; // Stop looking and spend the energy.
    return false;
  }

  if (range <= 2 && this.getEnergyFromSourcer()) {
    return true;
  }

  if (range === 1) {
    return this.harvestSource(source);
  } else {
    if (range > 1 && range < 4) {
      // TODO avoid exits here
      this.moveTo(source);
      return true;
    }
    this.creepLog(`getEnergy.moveToSource`);
    return this.moveToSource(source, swarm);
  }
};

Creep.prototype.setHasEnergy = function() {
  if (this.memory.hasEnergy === undefined) {
    this.memory.hasEnergy = (this.store.energy === this.store.getCapacity());
  } else if (this.memory.hasEnergy && this.store.energy === 0) {
    this.memory.hasEnergy = false;
  } else if (!this.memory.hasEnergy &&
    _.sum(this.store) === this.store.getCapacity()) {
    this.memory.hasEnergy = true;
  }
};

Creep.prototype.getDroppedEnergy = function() {
  const target = this.pos.findClosestByRangeEnergy();
  if (target === null) {
    return false;
  }
  const energyRange = this.pos.getRangeTo(target.pos);
  if (energyRange <= 1) {
    this.pickupOrWithdrawFromSourcer(target);
    return true;
  }
  if (target.energy > (energyRange * 15) * (this.store.energy + 1)) {
    this.say('dropped');
    this.moveToMy(target.pos, 1);
    return true;
  }
  return false;
};

Creep.prototype.getEnergy = function() {
  if (this.store.getFreeCapacity() === 0) {
    return false;
  }
  /* State machine:
   * No energy, goes to collect energy until full.
   * Full energy, uses energy until empty.
   */
  this.pickupEnergy();
  this.setHasEnergy();

  if (this.memory.hasEnergy) {
    return false;
  }
  if (this.getDroppedEnergy()) {
    this.creepLog(`getEnergy.getDroppedEnergy`);
    return true;
  }

  if (['builder', 'universal', 'nextroomer'].includes(this.memory.role) && this.room.isConstructingSpawnEmergency()) {
    if (this.getEnergyFromAnyOfMyStructures()) {
      this.creepLog(`getEnergy.getEnergyFromAnyStructures`);
      return true;
    }
  } else {
    if (this.getEnergyFromStorage()) {
      this.creepLog(`getEnergy.getEnergyFromStorage`);
      return true;
    }
    if (this.getEnergyFromHostileStructures()) {
      this.creepLog(`getEnergy.getEnergyFromHostileStructures`);
      return true;
    }
  }

  this.creepLog(`getEnergy.getEnergyFromSource`);
  return this.getEnergyFromSource();
};

Creep.prototype.buildConstructionSite = function(target) {
  const returnCode = this.build(target);
  if (returnCode === OK) {
    this.moveRandomWithin(target.pos);
    return true;
  } else if (returnCode === ERR_NOT_ENOUGH_RESOURCES) {
    return true;
  } else if (returnCode === ERR_INVALID_TARGET) {
    this.log(`buildConstructionSite ERR_INVALID_TARGET ${JSON.stringify(target)}`);
    this.moveRandom();
    target.pos.clearPosition(target);
    return true;
  }
  this.log(`creep_resources.buildConstructionSite build returnCode: ${returnCode} target: ${JSON.stringify(target)}`);
  return false;
};

Creep.prototype.moveToAndBuildConstructionSite = function(target) {
  this.creepLog('moveToAndBuildConstructionSite');
  const range = this.pos.getRangeTo(target);
  if (range <= 3) {
    return this.buildConstructionSite(target);
  }

  // TODO is this necessary here? Maybe because of the builder
  this.memory.routing.targetId = target.id;
  this.moveToMy(target.pos, 3, true);
  return true;
};

Creep.prototype.construct = function() {
  let target;
  if (this.memory.routing.targetId) {
    target = Game.getObjectById(this.memory.routing.targetId);
    this.creepLog('Use memory target', target);
  }
  if (!target || target === null || !(target instanceof ConstructionSite)) {
    this.creepLog('No target');
    delete this.memory.routing.targetId;
    if (this.memory.role === 'nextroomer') {
      target = this.pos.findClosestByRangeStandardConstructionSites();
    } else if (this.memory.role === 'universal') {
      target = this.pos.findClosestByRangeBuildingConstructionSites();
    } else {
      target = this.pos.findClosestByRange(FIND_CONSTRUCTION_SITES);
    }
  }

  if (target === null) {
    return false;
  }
  this.creepLog('moveToAndBuildConstructionSite');
  return this.moveToAndBuildConstructionSite(target);
};

Creep.prototype.getTransferTargetStructure = function() {
  let structure = this.pos.findClosestStructureWithMissingEnergyByRange(
    (object) => object.structureType !== STRUCTURE_STORAGE && object.structureType !== STRUCTURE_LINK,
  );
  // Universal should always prefer Spawn and Extensions
  if (this.memory.role === 'universal') {
    const structureSpawnExtension = this.pos.findClosestStructureWithMissingEnergyByRange(
      (object) => (object.structureType === STRUCTURE_SPAWN || object.structureType === STRUCTURE_EXTENSION),
    );
    if (structureSpawnExtension !== null) {
      structure = structureSpawnExtension;
    }
  }
  if (structure === null) {
    if (this.room.storage && this.room.storage.my && this.memory.role !== 'builder') {
      this.memory.target = this.room.storage.id;
    } else {
      return false;
    }
  } else {
    this.memory.targetEnergyMy = structure.id;
  }
};

Creep.prototype.getTransferTarget = function() {
  if (!this.memory.targetEnergyMy) {
    this.getTransferTargetStructure();
    if (!this.memory.targetEnergyMy) {
      return false;
    }
  }

  const target = Game.getObjectById(this.memory.targetEnergyMy);
  if (!target || target.store.getFreeCapacity(RESOURCE_ENERGY) === 0) {
    delete this.memory.targetEnergyMy;
    return false;
  }
  return target;
};

Creep.prototype.transferEnergy = function() {
  const target = this.getTransferTarget();
  if (!target) {
    return false;
  }
  const range = this.pos.getRangeTo(target);
  if (range === 1) {
    const returnCode = this.transfer(target, RESOURCE_ENERGY);
    if (returnCode !== OK && returnCode !== ERR_FULL) {
      this.log('transferEnergy: ' + returnCode + ' ' +
        target.structureType + ' ' + target.pos);
    }
    delete this.memory.targetEnergyMy;
  } else {
    this.moveToMy(target.pos, 1);
  }
  return true;
};