Mirroar/hivemind

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

Summary

Maintainability
D
2 days
Test Coverage
/* global MOVE ATTACK RANGED_ATTACK HEAL TOUGH CLAIM CARRY WORK */

import BodyBuilder, {MOVEMENT_MODE_SWAMP} from 'creep/body-builder';
import SpawnRole from 'spawn-role/spawn-role';
import Squad from 'manager.squad';

const availableUnitTypes = [
    'ranger',
    'healer',
    'claimer',
    'singleClaim',
    'builder',
    'attacker',
    'brawler',
    'blinky',
    'test',
] as const;

declare global {
    type SquadUnitType = typeof availableUnitTypes[number];
}

interface SquadSpawnOption extends SpawnOption {
    unitType: SquadUnitType;
    squad: string;
}

export default class SquadSpawnRole extends SpawnRole {
    /**
     * Adds squad spawn options for the given room.
     *
     * @param {Room} room
     *   The room to add spawn options for.
     */
    getSpawnOptions(room: Room) {
        return this.cacheEmptySpawnOptionsFor(room, 10, () => {
            const options: SquadSpawnOption[] = [];

            _.each(Game.squads, squad => {
                if (squad.getSpawn() !== room.name) return;

                const availableEnergy = room.getEffectiveAvailableEnergy();
                if (availableEnergy < 5000) return;

                const spawnUnitType = this.needsSpawning(room, squad);
                if (!spawnUnitType) return;

                const roomHasReserves = availableEnergy > 10_000;
                options.push({
                    priority: roomHasReserves ? 4 : 2,
                    weight: 1.1,
                    unitType: spawnUnitType,
                    squad: squad.name,
                });
            });

            return options;
        });
    }

    /**
     * Decides whether a squad needs additional units spawned.
     *
     * @param {Squad} squad
     *   The squad to check.
     *
     * @return {string|null}
     *   Type of the unit that needs spawning.
     */
    needsSpawning(room: Room, squad: Squad): SquadUnitType | null {
        const neededUnits: SquadUnitType[] = [];
        for (const unitType in squad.memory.composition) {
            if (!availableUnitTypes.includes(unitType as SquadUnitType)) continue;

            if (squad.getUnitCount(unitType as SquadUnitType) > _.size(squad.units[unitType])) {
                neededUnits.push(unitType as SquadUnitType);
            }
        }

        if (_.size(neededUnits) === 0) squad.memory.fullySpawned = true;

        // @todo Some squad units might need to be spawned at higher priorities
        // than others.
        return _.sample(neededUnits);
    }

    /**
     * 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: SquadSpawnOption): BodyPartConstant[] {
        // Automatically call spawning function for selected unit type.
        const methodName = 'get' + _.capitalize(option.unitType) + 'CreepBody';
        const bodyCallback: (room: Room, option: SquadSpawnOption) => BodyPartConstant[] = this[methodName];
        if (bodyCallback) return bodyCallback.call(this, room, option);

        // If the unit type is not supported, spawn a general brawler.
        return this.getBrawlerCreepBody(room);
    }

    getRangerCreepBody(room: Room) {
        return (new BodyBuilder())
            .setWeights({[RANGED_ATTACK]: 3})
            .setMoveBufferRatio(0.4)
            .setEnergyLimit(Math.min(room.energyCapacityAvailable, Math.max(room.energyCapacityAvailable * 0.9, room.energyAvailable)))
            .build();
    }

    getHealerCreepBody(room: Room) {
        return (new BodyBuilder())
            .setWeights({[HEAL]: 1})
            .setMoveBufferRatio(0.4)
            .setEnergyLimit(Math.min(room.energyCapacityAvailable, Math.max(room.energyCapacityAvailable * 0.9, room.energyAvailable)))
            .build();
    }

    getClaimerCreepBody(room: Room) {
        return (new BodyBuilder())
            .setWeights({[CLAIM]: 3, [TOUGH]: 2})
            .setEnergyLimit(Math.min(room.energyCapacityAvailable, Math.max(room.energyCapacityAvailable * 0.9, room.energyAvailable)))
            .build();
    }

    getSingleClaimCreepBody(room: Room) {
        return (new BodyBuilder())
            .setWeights({[CLAIM]: 1})
            .setPartLimit(CLAIM, 1)
            .setMovementMode(MOVEMENT_MODE_SWAMP)
            .setEnergyLimit(room.energyCapacityAvailable)
            .build();
    }

    getBuilderCreepBody(room: Room) {
        return (new BodyBuilder())
            .setWeights({[CARRY]: 3, [WORK]: 2})
            .setEnergyLimit(Math.min(room.energyCapacityAvailable, Math.max(room.energyCapacityAvailable * 0.9, room.energyAvailable)))
            .build();
    }

    getAttackerCreepBody(room: Room) {
        return (new BodyBuilder())
            .setWeights({[ATTACK]: 1})
            .setMoveBufferRatio(0.4)
            .setEnergyLimit(Math.min(room.energyCapacityAvailable, Math.max(room.energyCapacityAvailable * 0.9, room.energyAvailable)))
            .build();
    }

    getTestCreepBody() {
        return [MOVE];
    }

    getBlinkyCreepBody(room: Room) {
        return (new BodyBuilder())
            .setWeights({[RANGED_ATTACK]: 3, [HEAL]: 2})
            .setMoveBufferRatio(0.4)
            .setEnergyLimit(Math.min(room.energyCapacityAvailable, Math.max(room.energyCapacityAvailable * 0.9, room.energyAvailable)))
            .build();
    }

    getBrawlerCreepBody(room: Room) {
        return (new BodyBuilder())
            .setWeights({[ATTACK]: 3, [HEAL]: 2})
            .setMoveBufferRatio(0.4)
            .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, option: SquadSpawnOption): CreepMemory {
        return {
            role: 'brawler',
            squadName: option.squad,
            squadUnitType: option.unitType,
        };
    }

    /**
     * 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: SquadSpawnOption, body: BodyPartConstant[]): Record<string, ResourceConstant> {
        if (option.unitType === 'healer') {
            return this.generateCreepBoosts(room, body, HEAL, 'heal');
        }

        if (option.unitType === 'attacker') {
            return this.generateCreepBoosts(room, body, ATTACK, 'attack');
        }

        return null;
    }
}