TooAngel/screeps

View on GitHub
src/prototype_room_basebuilder.js

Summary

Maintainability
F
4 days
Test Coverage
'use strict';

/**
 * posIsIn position is in array
 *
 * @param {object} pos - The position
 * @param {array} array - Array of positions
 * @return {boolean} - position is in array
 */
function posIsIn(pos, array) {
  if (!array) {
    return false;
  }

  for (const posCheck of array) {
    // TODO when does this happen?
    if (posCheck === null) {
      throw new Error();
      //      continue;
    }
    if (pos.x === posCheck.x && pos.y === posCheck.y) {
      return true;
    }
  }
  return false;
}

/**
 * destroyStructureWall - Destroys wall structures
 *
 * @param {object} structure - The structure
 * @return {boolean} If handled
 **/
Room.prototype.destroyStructureWall = function(structure) {
  if (!structure.hits) {
    return false;
  }
  if (!this.memory.walls) {
    return false;
  }
  if (!this.memory.walls.finished) {
    return false;
  }
  if (!structure.pos.inRamparts()) {
    for (const layerId of Object.keys(this.memory.walls.layer)) {
      const layer = this.memory.walls.layer[layerId];
      for (const pos of layer) {
        if (structure.pos.isEqualTo(pos.x, pos.y)) {
          return false;
        }
      }
    }
    const flags = this.find(FIND_FLAGS, {
      filter: function(flag) {
        return flag.name.startsWith('allowWall');
      },
    });
    for (const flag of flags) {
      if (structure.pos.isEqualTo(flag.pos)) {
        return false;
      }
    }
  }
  structure.destroy();
  return true;
};

Room.prototype.destroyStructureRoad = function(structure) {
  for (const pathName of Object.keys(this.getMemoryPaths())) {
    for (const pos of this.getMemoryPath(pathName)) {
      if (structure.pos.isEqualTo(pos.x, pos.y)) {
        return false;
      }
    }
  }
  const flags = this.find(FIND_FLAGS, {
    filter: function(flag) {
      return flag.name.startsWith('allowRoad');
    },
  });
  for (const flag of flags) {
    if (structure.pos.isEqualTo(flag.pos)) {
      return false;
    }
  }
  structure.destroy();
  return true;
};

Room.prototype.buildRampartsAroundSpawns = function() {
  // Build ramparts around the spawn if wallThickness > 1
  // TODO this is not used for a long time and the spawn positions should
  // be taken from `memory.positions.spawn`
  if (config.layout.wallThickness > 1) {
    const costMatrixBase = this.getMemoryCostMatrix();
    const spawns = this.findMySpawns();

    for (const spawn of spawns) {
      for (let x = -1; x < 2; x++) {
        for (let y = -1; y < 2; y++) {
          if (spawn.pos.x + x >= 0 && spawn.pos.y + y >= 0 && spawn.pos.x + x < 50 && spawn.pos.y + y < 50) {
            const pos = new RoomPosition(spawn.pos.x + x, spawn.pos.y + y, spawn.pos.roomName);
            this.memory.walls.ramparts.push(pos);
            costMatrixBase.set(pos.x, pos.y, 0);

            const walls = pos.findInRangeWall(0);
            for (const wall of walls) {
              wall.destroy();
            }
          }
        }
      }
    }
    this.setMemoryCostMatrix(costMatrixBase);
  }
};

/**
 * destroyStructureSpawn
 *
 * @param {object} room
 * @param {object} structure
 * @return {boolean}
 */
function destroyStructureSpawn(room, structure) {
  const spawnsCount = room.findMySpawns().length;
  if (room.memory.misplacedSpawn) {
    if (spawnsCount < config.myRoom.leastSpawnsToRebuildStructureSpawn) {
      room.memory.misplacedSpawn = false;
      return false;
    }
    if (room.storage && room.storage.store.energy > 20000) {
      const builders = room.findMyCreepsOfRole('builder');
      if (builders.length > 3) {
        room.log('Destroying to rebuild spawn: ' + structure.structureType + ' ' + JSON.stringify(structure.pos));
        room.log('-----------------------------------------');
        room.log('ATTENTION: The last spawn is destroyed, a new one will be build automatically, DO NOT RESPAWN');
        room.log('-----------------------------------------');
        structure.destroy();
        delete room.memory.misplacedSpawn;
        room.memory.controllerLevel.checkWrongStructureInterval = 1;
        delete room.memory.walls;
        return true;
      }
    }
    return false;
  }
  room.log(`Spawn [${structure.pos.x}, ${structure.pos.y}] is misplaced, not in positions (prototype_room_basebuilder.destroyStructure), spawnsCount be ${spawnsCount}`); // eslint-disable-line max-len
  room.memory.misplacedSpawn = spawnsCount > config.myRoom.leastSpawnsToRebuildStructureSpawn;

  room.buildRampartsAroundSpawns();
  return false;
}

Room.prototype.destroyStructure = function(structure) {
  if (structure.structureType === STRUCTURE_WALL) {
    return this.destroyStructureWall(structure);
  }
  if (structure.structureType === STRUCTURE_ROAD) {
    return this.destroyStructureRoad(structure);
  }
  if (structure.structureType === STRUCTURE_RAMPART) {
    return false;
  }
  if (structure.structureType === STRUCTURE_CONTAINER) {
    return false;
  }
  if (posIsIn(structure.pos, this.data.positions.structure[structure.structureType])) {
    return false;
  }

  const structures = this.findStructuresOfStructureType(structure.structureType);
  let structuresMin = 0;
  if (structure.structureType === STRUCTURE_SPAWN) {
    structuresMin = 1;
  }

  if (structures.length > structuresMin && (structure.my || Room.structureIsEmpty(structure)) && (structure.structureType !== STRUCTURE_STORAGE)) {
    structure.destroy();
    return true;
  }
  if (structure.structureType === STRUCTURE_SPAWN) {
    return destroyStructureSpawn(this, structure);
  }
  return false;
};

Room.prototype.checkPath = function() {
  //  this.log('checkPath: ' + this.memory.controllerLevel.checkPathInterval);
  const path = this.getMemoryPath('pathStart-universal');
  if (!path) {
    this.log('Skipping checkPath, routing not initialized, try remove memory');
    this.clearMemory();
    return false;
  }
  for (const pos of path) {
    const roomPos = new RoomPosition(pos.x, pos.y, this.name);
    const structures = roomPos.lookFor('structure');

    for (const structure of structures) {
      if (structure.structureType === STRUCTURE_ROAD) {
        continue;
      }
      if (structure.structureType === STRUCTURE_RAMPART) {
        continue;
      }
      // console.log('checkPath: ' + pos);
      if (this.destroyStructure(structure)) {
        return true;
      }
    }
  }
  return false;
};

Room.prototype.checkWrongStructure = function() {
  this.debugLog('baseBuilding', 'checkWrongStructure: ' + this.memory.controllerLevel.checkWrongStructureInterval);
  if (this.memory.underSiege && this.controller.level >= 3) {
    this.log('checkWrongStructure: underSiege');
    return false;
  }

  // destroyStructure resets misplacedSpawn, so make sure we reach that point with the storage check
  if (this.memory.misplacedSpawn && (!this.storage || this.storage.store.energy < 20000)) {
    this.debugLog('baseBuilding', 'checkWrongStructures skipped - misplacedSpawn');
    return false;
  }

  // TODO Building up underSiege, maybe check for underSiege
  // if (this.controller.level < 6) {
  //  this.log('checkWrongStructure: controller.level < 6');
  //  return false;
  // }
  const structures = this.findStructuresToDestroy();
  for (const structure of structures) {
    if (this.destroyStructure(structure)) {
      return true;
    }
  }
  return false;
};

Room.prototype.clearPosition = function(pos, structure) {
  const posStructures = pos.lookFor('structure');
  let returnValue = false;
  for (const posStructureIndex of Object.keys(posStructures)) {
    const posStructure = posStructures[posStructureIndex];
    if (posStructure.structureType === STRUCTURE_ROAD) {
      continue;
    }
    if (posStructure.structureType === STRUCTURE_RAMPART) {
      continue;
    }
    if (posStructure.structureType === structure) {
      returnValue = {
        destroyed: false,
      };
      continue;
    }
    return this.destroyStructure(posStructure);
  }
  return returnValue;
};

/**
 * setupStructureFinishPriorityStructures
 *
 * @param {object} structure
 * @param {array} constructionSites
 * @return {boolean}
 */
function setupStructureFinishPriorityStructures(structure, constructionSites) {
  // Only build one spawn at a time, especially for reviving
  if (structure === STRUCTURE_SPAWN) {
    if (constructionSites.length > 0) {
      return true;
    }
  }

  // Complete storage before building something else
  if (structure === STRUCTURE_STORAGE) {
    if (constructionSites.length > 0) {
      return true;
    }
  }
}

Room.prototype.setupStructure = function(structure) {
  const constructionSites = this.find(FIND_CONSTRUCTION_SITES, {filter: (object) => object.structureType === structure});
  if (setupStructureFinishPriorityStructures(structure, constructionSites)) {
    return true;
  }

  const structures = this.find(FIND_MY_STRUCTURES, {filter: (object) => object.structureType === structure});
  const diff = CONTROLLER_STRUCTURES[structure][this.controller.level] -
    (structures.length + constructionSites.length);
  if (diff <= 0) {
    return false;
  }
  for (const pos of (this.data.positions.structure[structure] || [])) {
    // TODO special case e.g. when powerSpawn can't be set on CostMatrix.setup - need to be fixed there
    if (!pos) {
      continue;
    }
    const posObject = new RoomPosition(pos.x, pos.y, this.name);

    const clear = this.clearPosition(posObject, structure);
    if (clear) {
      if (clear.destroyed) {
        return true;
      } else {
        continue;
      }
    }

    const returnCode = posObject.createConstructionSite(structure);
    if (returnCode === OK) {
      return true;
    }
    if (returnCode === ERR_FULL) {
      this.debugLog('baseBuilding', 'setup createConstructionSite too many constructionSites');
      return true;
    }
    if (returnCode === ERR_INVALID_TARGET) {
      continue;
    }
    if (returnCode === ERR_RCL_NOT_ENOUGH) {
      this.debugLog('baseBuilding', structure + ' ' + this.controller.level + ' ' + CONTROLLER_STRUCTURES[structure][this.controller.level]);
      this.debugLog('baseBuilding', 'setup createConstructionSite ERR_RCL_NOT_ENOUGH structure: ' + structure + ' ' + CONTROLLER_STRUCTURES[structure][this.controller.level] + ' ' + structures.length + ' ' + constructionSites.length);
    }

    this.debugLog('baseBuilding', 'setup createConstructionSite returnCode: ' + returnCode + ' structure: ' + structure);
  }
  return false;
};

Room.prototype.checkBuildStructureValidity = function() {
  if (!this.data.positions) {
    this.log('No position buildStructures');
    this.setup();
    return false;
  }

  if (!this.data.positions.structure) {
    return false;
  }

  if (!this.controller || this.controller === null || !this.controller.my) {
    this.log('No controller');
    return false;
  }

  if (Object.keys(Game.constructionSites).length >= 100) {
    return false;
  }
  return true;
};

Room.prototype.buildStructures = function() {
  if (!this.checkBuildStructureValidity()) {
    return false;
  }

  const constructionSites = this.findBuildingConstructionSites();
  if (constructionSites.length > 0) {
    //    this.log('baseBuilder.setup: Too many construction sites');
    return true;
  }

  const beforeStorage = [STRUCTURE_SPAWN, STRUCTURE_TOWER, STRUCTURE_STORAGE, STRUCTURE_LINK, STRUCTURE_EXTENSION];
  for (const structure of beforeStorage) {
    if (this.setupStructure(structure)) {
      return true;
    }
  }

  if (!this.storage || this.findConstructionSiteLink().length > 0) {
    return false;
  }

  const afterStorage = [STRUCTURE_POWER_SPAWN, STRUCTURE_EXTRACTOR, STRUCTURE_OBSERVER, STRUCTURE_TERMINAL, STRUCTURE_LAB, STRUCTURE_NUKER];
  for (const structure of afterStorage) {
    if (this.setupStructure(structure)) {
      return true;
    }
  }

  return false;
};

const structureExist = function(pos, structureType) {
  const structures = pos.lookFor(LOOK_STRUCTURES);
  for (const structure of structures) {
    if (structure.structureType === structureType) {
      return true;
    }
  }
  return false;
};

Room.prototype.checkBlockers = function() {
  if (this.controller.level === 1) {
    return false;
  }
  //  this.log('checkBlockers: ' + this.memory.controllerLevel.checkBlockersInterval + ' ' + this.controller.level + ' ' + this.memory.walls);
  if (!this.memory.walls || !this.memory.walls.layer) {
    this.debugLog('baseBuilding', 'checkBlockers: reset walls');
    this.memory.walls = {
      exit_i: 0,
      ramparts: [],
      layer_i: 0,
      // TODO as array?
      layer: {
        0: [],
      },
    };
  }

  for (const layer of Object.keys(this.memory.walls.layer)) {
    for (const blocker of this.memory.walls.layer[layer]) {
      const pos = new RoomPosition(blocker.x, blocker.y, this.name);

      let structureType = STRUCTURE_WALL;
      if (pos.inRamparts()) {
        structureType = STRUCTURE_RAMPART;
      }

      if (structureExist(pos, structureType)) {
        continue;
      }
      const returnCode = pos.createConstructionSite(structureType);
      if (returnCode !== OK && returnCode !== ERR_FULL) {
        // this.log('Build ' + structureType + ' at ' + pos + ' with ' + returnCode);
        return true;
      }
    }
  }
  return false;
};