Mirroar/hivemind

View on GitHub
src/role/guardian.ts

Summary

Maintainability
B
4 hrs
Test Coverage
/* global FIND_HOSTILE_CREEPS STRUCTURE_RAMPART */

import container from 'utils/container';
import hivemind from 'hivemind';
import Role from 'role/role';

declare global {
    interface GuardianCreep extends Creep {
        memory: GuardianCreepMemory;
        heapMemory: GuardianCreepHeapMemory;
    }

    interface GuardianCreepMemory extends CreepMemory {
        role: 'guardian';
    }

    interface GuardianCreepHeapMemory extends CreepHeapMemory {
    }
}

const filterEnemyCreeps = (c: Creep) => !hivemind.relations.isAlly(c.owner.username) && c.isDangerous();

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

        // Guardians have high priority because of their importance to room defense.
        this.stopAt = 0;
        this.throttleAt = 0;
    }

    /**
     * Makes a creep behave like a guardian.
     *
     * @param {Creep} creep
     *   The creep to run logic for.
     */
    run(creep: GuardianCreep) {
        const rampart = this.getBestRampartToCover(creep);
        if (!rampart) return;

        creep.whenInRange(0, rampart, () => {});
        this.setAlternatePositions(creep);
        this.attackTargetsInRange(creep);
    }

    setAlternatePositions(creep: GuardianCreep) {
        container.get('TrafficManager').setAlternatePositions(creep, this.getAdjacentRampartPositions(creep));
    }

    getAdjacentRampartPositions(creep: GuardianCreep): RoomPosition[] {
        const closestRamparts = _.filter(
            creep.room.myStructuresByType[STRUCTURE_RAMPART],
            s => {
                if (s.pos.getRangeTo(creep.pos) !== 1) return false;
                if (!creep.room.roomPlanner.isPlannedLocation(s.pos, 'rampart')) return false;
                if (creep.room.roomPlanner.isPlannedLocation(s.pos, 'rampart.ramp')) return false;

                return true;
            },
        );

        return _.map(closestRamparts, s => s.pos);
    }

    getBestRampartToCover(creep: GuardianCreep): StructureRampart {
        // @todo Make sure we can find a safe path to the rampart in question.
        const targets = creep.room.find(FIND_HOSTILE_CREEPS, {
            filter: filterEnemyCreeps,
        });

        const ramparts: StructureRampart[] = [];
        for (const target of targets) {
            const closestRampart = _.min(
                _.filter(
                    creep.room.myStructuresByType[STRUCTURE_RAMPART],
                    s => {
                        if (!creep.room.roomPlanner.isPlannedLocation(s.pos, 'rampart')) return false;
                        if (creep.room.roomPlanner.isPlannedLocation(s.pos, 'rampart.ramp')) return false;

                        // Only target ramparts not occupied by another creep.
                        const occupyingCreeps = s.pos.lookFor(LOOK_CREEPS);
                        if (occupyingCreeps.length > 0 && occupyingCreeps[0].id !== creep.id) return false;

                        return true;
                    },
                ),
                s => s.pos.getRangeTo(target.pos),
            );
            if (!ramparts.includes(closestRampart)) ramparts.push(closestRampart);
        }

        return _.min(ramparts, (s: StructureRampart) => s.pos.getRangeTo(creep.pos) / 2 + s.pos.getRangeTo(s.pos.findClosestByRange(FIND_HOSTILE_CREEPS, {
            filter: filterEnemyCreeps,
        })));
    }

    attackTargetsInRange(creep: GuardianCreep) {
        // Ask military manager for best target for joint attacks.
        creep.room.assertMilitarySituation();

        if (creep.getActiveBodyparts(RANGED_ATTACK) > 0) {
            const targets = creep.pos.findInRange(FIND_HOSTILE_CREEPS, 3, {
                filter: filterEnemyCreeps,
            });
            if (targets.length === 0) return;

            let target = _.max(targets, 'militaryPriority');
            if (!target || (typeof target === 'number')) {
                if (targets.length > 2 || _.find(targets, c => c.pos.getRangeTo(creep) === 1)) {
                    creep.rangedMassAttack();
                }
                else {
                    target = _.sample(targets);
                }
            }

            creep.rangedAttack(target);
        }

        if (creep.getActiveBodyparts(ATTACK) > 0) {
            const targets = creep.pos.findInRange(FIND_HOSTILE_CREEPS, 1, {
                filter: filterEnemyCreeps,
            });
            if (targets.length === 0) return;

            let target = _.max(targets, 'militaryPriority');
            if (!target || (typeof target === 'number')) target = _.sample(targets);
            creep.attack(target);
        }
    }
}