src/role/harvester.remote.ts
/* global STRUCTURE_ROAD OK RESOURCE_ENERGY LOOK_CREEPS
STRUCTURE_CONTAINER FIND_SOURCES LOOK_CONSTRUCTION_SITES
FIND_MY_CONSTRUCTION_SITES */
import RemoteMiningOperation from 'operation/remote-mining';
import Role from 'role/role';
import {decodePosition, serializePositionPath} from 'utils/serialization';
declare global {
interface RemoteHarvesterCreep extends Creep {
memory: RemoteHarvesterCreepMemory;
heapMemory: RemoteHarvesterCreepHeapMemory;
operation: RemoteMiningOperation;
}
interface RemoteHarvesterCreepMemory extends CreepMemory {
role: 'harvester.remote';
source: string;
}
interface RemoteHarvesterCreepHeapMemory extends CreepHeapMemory {
}
}
export default class RemoteHarvesterRole extends Role {
constructor() {
super();
// Remote harvesters have slighly higher priority, since they don't use much
// cpu once they are harvesting.
this.throttleAt = 5000;
this.stopAt = 2000;
}
/**
* Makes a creep behave like a remote harvester.
*
* @param {Creep} creep
* The creep to run logic for.
*/
run(creep: RemoteHarvesterCreep) {
if (!creep.operation) {
// @todo Operation has probably ended. Return home and suicide?
return;
}
if (this.travelToSource(creep)) return;
this.performRemoteHarvest(creep);
}
/**
* Makes the creep move toward its targeted source.
*
* @param {Creep} creep
* The creep to run logic for.
*
* @returns {boolean}
* Whether the creep is in the process of moving.
*/
travelToSource(creep: RemoteHarvesterCreep) {
const sourcePosition = decodePosition(creep.memory.source);
if (creep.pos.roomName !== creep.operation.getRoom() && !creep.hasCachedPath()) {
const paths = creep.operation.getPaths();
if (!paths[creep.memory.source] || !paths[creep.memory.source].accessible) return false;
creep.setCachedPath(serializePositionPath(paths[creep.memory.source].path), true, 1);
}
if (creep.hasCachedPath()) {
if (
creep.hasArrived()
|| creep.pos.getRangeTo(sourcePosition) < 3
|| (creep.pos.roomName === creep.operation.getRoom() && this.getSource(creep)?.isDangerous() && creep.pos.getRangeTo(sourcePosition) < 10)
) {
creep.clearCachedPath();
}
else {
creep.followCachedPath();
return true;
}
}
if (sourcePosition.roomName !== creep.pos.roomName) {
creep.moveToRange(sourcePosition, 1);
return true;
}
return false;
}
/**
* Makes the creep harvest resources outside of owned rooms.
*
* @param {Creep} creep
* The creep to run logic for.
*/
performRemoteHarvest(creep: RemoteHarvesterCreep) {
if (creep.pos.roomName !== creep.operation.getRoom()) return;
// Check if a container nearby is in need of repairs, since we can handle
// it with less intents than haulers do.
const workParts = creep.getActiveBodyparts(CARRY) ? creep.getActiveBodyparts(WORK) : 0;
const needsBuild = creep.pos.findClosestByRange(FIND_MY_CONSTRUCTION_SITES, {
// It's important we build nearby roads as their sites may prevent the
// container construction site from being placed.
filter: site => (site.structureType === STRUCTURE_CONTAINER) || (site.structureType === STRUCTURE_ROAD),
});
if (needsBuild && creep.pos.getRangeTo(needsBuild) <= 3) {
if (creep.store.energy >= Math.min(workParts * 5, creep.store.getCapacity()) && workParts > 0) {
const result = creep.build(needsBuild);
if (result === OK) {
return;
}
}
else {
const energy = creep.pos.findInRange(FIND_DROPPED_RESOURCES, 1, {
filter: resource => resource.resourceType === RESOURCE_ENERGY,
});
if (energy.length > 0) creep.pickup(energy[0]);
}
}
if (this.repairNearbyContainer(creep)) return;
const source = this.getSource(creep);
// Keep away from source keepers.
if (source.isDangerous()) {
if (creep.pos.getRangeTo(source) < 5) {
// @todo To save cpu, just move back along remote path.
creep.whenInRange(5, new RoomPosition(25, 25, creep.pos.roomName), () => {});
return;
}
creep.whenInRange(6, source, () => {});
// @todo We might consider repairing nearby infrastructure.
return;
}
let moveTarget: RoomObject = source;
let moveRange = 1;
if ((creep.operation instanceof RemoteMiningOperation)) {
const container = creep.operation.getContainer(creep.memory.source);
const creepsOnContainer = container && container.pos.lookFor(LOOK_CREEPS).length > 0;
// Move onto container when possible.
if (container && !creepsOnContainer) {
moveTarget = container;
moveRange = 0;
}
// Transfer energy to container if we can't drop directly onto it.
if (
container
&& creep.pos.getRangeTo(container.pos) === 1
&& creep.store.getFreeCapacity() < creep.getActiveBodyparts(WORK) * HARVEST_POWER
&& creepsOnContainer
) {
creep.transfer(container, RESOURCE_ENERGY);
}
}
creep.whenInRange(moveRange, moveTarget, () => {
// Wait if source is depleted.
if (source.energy <= 0) return;
if (this.mayHarvest(creep, source)) creep.harvest(source);
// Immediately deposit energy if a container is nearby.
if (!(creep.operation instanceof RemoteMiningOperation)) return;
if (!creep.operation.hasContainer(creep.memory.source)) {
// Check if there is a construction site nearby.
const containerPosition = creep.operation.getContainerPosition(creep.memory.source);
if (!containerPosition) return;
const sites = _.filter(containerPosition.lookFor(LOOK_CONSTRUCTION_SITES), (site: ConstructionSite) => site.structureType === STRUCTURE_CONTAINER);
if (sites.length === 0) {
// Place a container construction site for this source.
containerPosition.createConstructionSite(STRUCTURE_CONTAINER);
}
}
});
}
getSource(creep: RemoteHarvesterCreep): Source {
const sourcePosition = decodePosition(creep.memory.source);
return creep.room.find(FIND_SOURCES, {
filter: source => source.pos.x === sourcePosition.x && source.pos.y === sourcePosition.y,
})[0];
}
mayHarvest(creep: RemoteHarvesterCreep, source: Source): boolean {
// Hit fully regenerated sources to start regeneration timer ASAP.
if (source.energy === source.energyCapacity) return true;
const harvestPower = creep.getActiveBodyparts(WORK) * HARVEST_POWER;
// Always harvest if we can carry the resource.
if (creep.store.getFreeCapacity() >= harvestPower) return true;
// If we don't have a container, always harvest as soon as possible.
if (!(creep.operation instanceof RemoteMiningOperation)) return true;
if (!creep.operation.hasContainer(creep.memory.source)) return true;
const container = creep.operation.getContainer(creep.memory.source);
if (!container) return true;
// If creep storage is full, only harvest when on container so we don't
// unnecessarily drop resources on the ground.
if (creep.pos.getRangeTo(container.pos) > 0) return false;
// Only harvest if container still has capacity.
if (container.store.getFreeCapacity() >= harvestPower) return true;
// Any additional resources will drop to the ground, so only harvest
// if we would otherwise lose energy to the regeneration timer.
const ticksToHarvestFully = Math.ceil(source.energy / harvestPower);
if (source.ticksToRegeneration <= ticksToHarvestFully) return true;
return false;
}
repairNearbyContainer(creep: RemoteHarvesterCreep): boolean {
const workParts = creep.getActiveBodyparts(CARRY) ? creep.getActiveBodyparts(WORK) : 0;
if (workParts === 0) return false;
if (creep.store.energy < workParts) return false;
const needsRepair = _.filter(
creep.room.structuresByType[STRUCTURE_CONTAINER],
// Repair if possible so we can save on dedicated builders.
structure =>
structure.hits <= structure.hitsMax - (workParts * REPAIR_POWER)
&& creep.pos.getRangeTo(structure.pos) <= 3
&& creep.operation.getContainerPosition(creep.memory.source).isEqualTo(structure.pos),
);
if (needsRepair.length > 0) {
const result = creep.repair(needsRepair[0]);
if (result === OK) {
return true;
}
}
return false;
}
}