Mirroar/hivemind

View on GitHub
src/spawn-role/upgrader.ts

Summary

Maintainability
C
1 day
Test Coverage
/* global CONTROLLER_DOWNGRADE MOVE WORK CARRY
CONTROLLER_MAX_UPGRADE_PER_TICK */

import balancer from 'excess-energy-balancer';
import BodyBuilder, {MOVEMENT_MODE_ROAD, MOVEMENT_MODE_SLOW} from 'creep/body-builder';
import container from 'utils/container';
import hivemind from 'hivemind';
import SpawnRole from 'spawn-role/spawn-role';

interface UpgraderSpawnOption extends SpawnOption {
    mini?: boolean;
}

export default class UpgraderSpawnRole extends SpawnRole {
    /**
     * Adds upgrader spawn options for the given room.
     *
     * @param {Room} room
     *   The room to add spawn options for.
     */
    getSpawnOptions(room: Room) {
        return this.cacheEmptySpawnOptionsFor(room, 100, () => {
            const options: UpgraderSpawnOption[] = [];
            const maxUpgraders = this.getUpgraderAmount(room);
            const upgraderCount = _.size(_.filter(room.creepsByRole.upgrader, creep => !creep.ticksToLive || creep.ticksToLive > creep.body.length * 3));
            if (upgraderCount < maxUpgraders) {
                options.push({
                    priority: 3,
                    weight: 1,
                });
            }

            if (maxUpgraders === 0 && upgraderCount === 0 && room.controller.progress > room.controller.progressTotal) {
                // Spawn a mini upgrader to get ticksToDowngrade up so level gets raised.
                options.push({
                    priority: 3,
                    weight: 1,
                    mini: true,
                });
            }

            return options;
        });
    }

    /**
     * Gets number of needed upgraders depending on room needs.
     *
     * @param {Room} room
     *   The room to add spawn options for.
     *
     * @return {number}
     *   The requested number of upgraders.
     */
    getUpgraderAmount(room: Room): number {
        const maxUpgraders = this.getBaseUpgraderAmount(room);

        if (maxUpgraders === 0) {
            // Even if no upgraders are needed, at least create one when the controller is getting close to being downgraded.
            if (room.controller.ticksToDowngrade < CONTROLLER_DOWNGRADE[room.controller.level] * 0.1) {
                hivemind.log('creeps', room.name).info('trying to spawn upgrader because controller is close to downgrading', room.controller.ticksToDowngrade, '/', CONTROLLER_DOWNGRADE[room.controller.level]);
                return 1;
            }

            if (room.controller.ticksToDowngrade < CONTROLLER_DOWNGRADE[room.controller.level] * 0.5 && room.getEffectiveAvailableEnergy() > 5000) {
                return 1;
            }
        }

        return maxUpgraders;
    }

    /**
     * Gets number of needed upgraders depending on room needs.
     *
     * @param {Room} room
     *   The room to add spawn options for.
     *
     * @return {number}
     *   The requested number of upgraders.
     */
    getBaseUpgraderAmount(room: Room): number {
        // Early on, builders will take care of upgrading once necessary
        // structures have been built.
        if (!room.storage && !room.terminal) return 0;

        // Do not spawn upgraders in evacuating rooms.
        if (room.isEvacuating()) return 0;

        if (room.roomManager?.hasMisplacedSpawn()) return 0;

        if (room.controller.level >= 6 && room.isStripmine()) return 0;

        const availableEnergy = room.getEffectiveAvailableEnergy();
        const funnelManager = container.get('FunnelManager');
        const isFunneling = room.terminal && funnelManager.isFunneling() && !funnelManager.isFunnelingTo(room.name);
        if (isFunneling && availableEnergy < 100_000) return 0;

        if (room.controller.level === 8 && !balancer.maySpendEnergyOnGpl()) return 0;

        // RCL 8 rooms can't make use of more than 1 upgrader.
        if (room.controller.level === 8) {
            if (availableEnergy < hivemind.settings.get('minEnergyToUpgradeAtRCL8')) return 0;
            return 1;
        }

        // Spawn upgraders depending on stored energy.
        // RCL 7 rooms need to keep a bit more energy in reserve for doing other
        // things like power or deposit harvesting, sending squads, ...
        if (availableEnergy < (room.controller.level === 7 ? 25_000 : 10_000)) return 0;
        if (availableEnergy < (room.controller.level === 7 ? 75_000 : 50_000)) return 1;
        if (availableEnergy < 100_000) return 2;
        if (availableEnergy < 125_000 && isFunneling) return 3;
        if (availableEnergy < 150_000) return isFunneling ? 4 : 3;
        // @todo Have maximum depend on number of work parts.
        return isFunneling ? 5 : 4;
    }

    /**
     * Gets the body of a creep to be spawned.
     *
     * @param {Room} room
     *   The room to add spawn options for.
     * @param {Object} option
     *   The spawn option for which to generate the body.
     *
     * @return {string[]}
     *   A list of body parts the new creep should consist of.
     */
    getCreepBody(room: Room, option: UpgraderSpawnOption): BodyPartConstant[] {
        const hasEasyEnergyAccess = room.memory.controllerContainer || room.memory.controllerLink;

        return (new BodyBuilder())
            .setWeights({[CARRY]: 1, [WORK]: hasEasyEnergyAccess ? 10 : 1})
            .setPartLimit(WORK, option.mini ? 2 : (room.controller.level === 8 ? CONTROLLER_MAX_UPGRADE_PER_TICK : MAX_CREEP_SIZE))
            .setMovementMode(hasEasyEnergyAccess ? MOVEMENT_MODE_SLOW : MOVEMENT_MODE_ROAD)
            .setCarryContentLevel(0)
            .setEnergyLimit(Math.min(room.energyCapacityAvailable, Math.max(room.energyCapacityAvailable * 0.9, room.energyAvailable)))
            .build();
    }

    /**
     * Gets memory for a new creep.
     *
     * @param {Room} room
     *   The room to add spawn options for.
     * @param {Object} option
     *   The spawn option for which to generate the body.
     *
     * @return {Object}
     *   The boost compound to use keyed by body part type.
     */
    getCreepMemory(room: Room): CreepMemory {
        return {
            singleRoom: room.name,
            operation: 'room:' + room.name,
        };
    }

    /**
     * Gets which boosts to use on a new creep.
     *
     * @param {Room} room
     *   The room to add spawn options for.
     * @param {Object} option
     *   The spawn option for which to generate the body.
     * @param {string[]} body
     *   The body generated for this creep.
     *
     * @return {Object}
     *   The boost compound to use keyed by body part type.
     */
    getCreepBoosts(room: Room, option: UpgraderSpawnOption, body: BodyPartConstant[]) {
        if (option.mini) return {};
        if (room.getEffectiveAvailableEnergy() < 50_000) return {};
        if (room.controller.level < 8) return {};

        return this.generateCreepBoosts(room, body, WORK, 'upgradeController');
    }
}