Mirroar/hivemind

View on GitHub
src/role/harvester.deposit.ts

Summary

Maintainability
A
2 hrs
Test Coverage
/* global RoomPosition STRUCTURE_POWER_BANK OK
POWER_BANK_DECAY FIND_MY_CREEPS HEAL_POWER RANGED_HEAL_POWER HEAL
FIND_DROPPED_RESOURCES RESOURCE_POWER FIND_HOSTILE_CREEPS RANGED_ATTACK
POWER_BANK_HIT_BACK */

import cache from 'utils/cache';
import hivemind from 'hivemind';
import NavMesh from 'utils/nav-mesh';
import Role from 'role/role';
import {deserializePosition} from 'utils/serialization';
import {getCostMatrix} from 'utils/cost-matrix';
import {getResourcesIn} from 'utils/store';

declare global {
    interface DepositHarvesterCreep extends Creep {
        memory: DepositHarvesterCreepMemory;
        heapMemory: DepositHarvesterCreepHeapMemory;
    }

    interface DepositHarvesterCreepMemory extends CreepMemory {
        role: 'harvester.deposit';
        targetPos: string;
        origin: string;
        delivering?: boolean;
    }

    interface DepositHarvesterCreepHeapMemory extends CreepHeapMemory {
        returnTravelTime?: number;
    }
}

export default class DepositHarvesterRole extends Role {
    constructor() {
        super();

        // Deposit harvesters have high priority because they need to harvest in the same tick.
        this.stopAt = 1000;
        this.throttleAt = 3000;
    }

    /**
     * Makes a creep act like a power harvester.
     *
     * @param {Creep} creep
     *   The creep to run logic for.
     */
    run(creep: DepositHarvesterCreep) {
        // @todo Return / suicide when TTL gets low.
        // @todo transfer to adjacent creeps to send only one home?

        const targetPosition = deserializePosition(creep.memory.targetPos);
        if (creep.memory.delivering && creep.store.getUsedCapacity() === 0) {
            if (creep.ticksToLive <= 2.5 * this.getReturnTravelTime(creep)) {
                // Round trip plus harvesting is not realistic with this little time.
                // @todo Go to a spawn to recycle if possible.
                creep.suicide();
                return;
            }

            this.setDelivering(creep, false);
        }
        else if (!creep.memory.delivering && creep.store.getUsedCapacity() > creep.store.getCapacity() * 0.95) {
            this.setDelivering(creep, true);
        }
        else if (!creep.memory.delivering && creep.pos.roomName == targetPosition.roomName && creep.pos.getRangeTo(targetPosition) < 3 && creep.ticksToLive <= 1.1 * this.getReturnTravelTime(creep)) {
            this.setDelivering(creep, true);
        }

        if (creep.memory.delivering) {
            this.performDeliver(creep);
            return;
        }

        this.performDepositHarvesting(creep);
    }

    setDelivering(creep: DepositHarvesterCreep, delivering: boolean) {
        creep.memory.delivering = delivering;
    }

    getReturnTravelTime(creep: DepositHarvesterCreep): number {
        if (creep.heapMemory.returnTravelTime) return creep.heapMemory.returnTravelTime;

        creep.heapMemory.returnTravelTime = cache.inHeap('returnTravel:' + creep.memory.targetPos + ':' + creep.memory.origin, 10_000, () => {
            const mesh = new NavMesh();
            const targetPosition = deserializePosition(creep.memory.targetPos);
            const path = mesh.findPath(targetPosition, new RoomPosition(25, 25, creep.memory.origin));
            if (path.incomplete) {
                creep.heapMemory.returnTravelTime = Game.map.getRoomLinearDistance(creep.pos.roomName, creep.memory.origin) * 75;
                return creep.heapMemory.returnTravelTime || 0;
            }

            const previousWaypoint = targetPosition;
            let total = 0;
            for (const waypoint of path.path) {
                const subPath = PathFinder.search(previousWaypoint, waypoint, {
                    maxRooms: 3,
                    roomCallback: roomName => getCostMatrix(roomName),
                });

                total += subPath.incomplete ? 75 : subPath.path.length;
            }

            return total;
        });

        return creep.heapMemory.returnTravelTime || 0;
    }

    performDeliver(creep: DepositHarvesterCreep) {
        const origin = creep.memory.origin;
        if (!Game.rooms[origin] || !Game.rooms[origin].isMine()) {
            // @todo Choose a new room close by and deliver.
            return;
        }

        const targetPosition = Game.rooms[origin].getStorageLocation();
        if (creep.interRoomTravel(targetPosition)) return;
        if (creep.pos.roomName != targetPosition.roomName) return;

        let resourceType: ResourceConstant;
        for (const contentType of getResourcesIn(creep.store)) {
            if (creep.store.getUsedCapacity(contentType) > 0) {
                resourceType = contentType;
                break;
            }
        }

        const target = creep.room.getBestStorageTarget(creep.store.getUsedCapacity(), resourceType);
        creep.whenInRange(1, target, () => {
            creep.transferAny(target);
        });
    }

    performDepositHarvesting(creep: DepositHarvesterCreep) {
        const targetPosition = deserializePosition(creep.memory.targetPos);
        if (creep.interRoomTravel(targetPosition)) return;
        if (creep.pos.roomName != targetPosition.roomName) return;

        const deposits = targetPosition.lookFor(LOOK_DEPOSITS);

        if (deposits.length === 0) {
            this.setDelivering(creep, true);
            return;
        }

        creep.whenInRange(1, deposits[0], () => {
            creep.harvest(deposits[0]);
        });
    }
}