Mirroar/hivemind

View on GitHub
src/prototype/room.intel.ts

Summary

Maintainability
F
4 days
Test Coverage
/* global Room STRUCTURE_CONTAINER FIND_HOSTILE_CREEPS
STRUCTURE_LINK STRUCTURE_NUKER STRUCTURE_OBSERVER LOOK_CREEPS
STRUCTURE_POWER_SPAWN FIND_SOURCES FIND_MINERALS */

import container from 'utils/container';
import Bay from 'manager.bay';
import cache from 'utils/cache';
import FactoryManager from 'factory-manager';
import RoomDefense from 'room-defense';
import utilities from 'utilities';
import {getUsername} from 'utils/account';

declare global {
    interface Room {
        creeps: Record<string, Creep>;
        powerCreeps: Record<string, PowerCreep>;
        creepsByRole: Record<string, Record<string, Creep>>;
        enemyCreeps: Record<string, Creep[]>;
        defense: RoomDefense;
        sources: Source[];
        minerals: Mineral[];
        enhanceData;
        scan;
        updateControllerContainer;
        updateControllerLink;
        updateStorageLink;
        needsScout;
        isMine: (allowReserved?: boolean) => boolean;
        nuker?: StructureNuker;
        powerSpawn?: StructurePowerSpawn;
        observer?: StructureObserver;
        factory?: StructureFactory;
        factoryManager: FactoryManager;
        needsReclaiming: () => boolean;
        isSafeForReclaiming: () => boolean;
    }

    interface RoomMemory {
        controllerLink?: any;
        controllerContainer?: any;
        storageLink?: any;
    }
}

// Define quick access property room.enemyCreeps.
Object.defineProperty(Room.prototype, 'enemyCreeps', {

    /**
     * Gets all enemy creeps in a room, keyed by owner username.
     *
     * @return {Object}
     *   All enemy creeps in this room.
     */
    get(this: Room) {
        return cache.inObject(this, 'enemyCreeps', 1, () => _.groupBy(this.find(FIND_HOSTILE_CREEPS), 'owner.username'));
    },
    enumerable: false,
    configurable: true,
});

// Define quick access property room.enemyCreeps.
Object.defineProperty(Room.prototype, 'powerCreeps', {

    /**
     * Gets all power creeps in a room, keyed by name.
     *
     * @return {Object}
     *   All power creeps in this room.
     */
    get(this: Room) {
        return cache.inObject(this, 'powerCreeps', 1, () => {
            const powerCreeps = {};
            for (const powerCreep of this.find(FIND_MY_POWER_CREEPS)) {
                powerCreeps[powerCreep.name] = powerCreep;
            }

            return powerCreeps;
        });
    },
    enumerable: false,
    configurable: true,
});

// Define quick access property room.defense
Object.defineProperty(Room.prototype, 'defense', {

    /**
     * Gets a room's defense manager.
     *
     * @return {RoomDefense}
     *   The room's defense manager.
     */
    get(this: Room) {
        return cache.inObject(this, 'roomDefense', 1, () => new RoomDefense(this.name));
    },
    enumerable: false,
    configurable: true,
});

// Define quick access property room.sources
Object.defineProperty(Room.prototype, 'sources', {

    /**
     * Gets a room's sources.
     *
     * @return {Source[]}
     *   The room's sources.
     */
    get(this: Room) {
        return cache.inObject(this, 'sources', 1, () => {
            const sourceIds = cache.inHeap('sources:' + this.name, 10_000, () => _.map(this.find(FIND_SOURCES), 'id'));

            return _.map(sourceIds, Game.getObjectById);
        });
    },
    enumerable: false,
    configurable: true,
});

// Define quick access property room.minerals
Object.defineProperty(Room.prototype, 'minerals', {

    /**
     * Gets a room's minerals.
     *
     * @return {Source[]}
     *   The room's minerals.
     */
    get(this: Room): Mineral[] {
        return cache.inObject(this, 'minerals', 1, () => {
            const mineralIds = cache.inHeap('mineral:' + this.name, 10_000, () => _.map<Mineral, string>(this.find(FIND_MINERALS), 'id'));

            const minerals = [];
            for (const mineralId of mineralIds) {
                const mineral = Game.getObjectById(mineralId);
                if (!mineral) continue;

                minerals.push(mineral);
            }

            return minerals;
        });
    },
    enumerable: false,
    configurable: true,
});

const structuresToReference = [
    STRUCTURE_NUKER,
    STRUCTURE_OBSERVER,
    STRUCTURE_POWER_SPAWN,
    STRUCTURE_FACTORY,
];

for (const structureType of structuresToReference) {
    Object.defineProperty(Room.prototype, structureType, {
        get(this: Room) {
            return getCachedStructureReference(this, structureType);
        },
        enumerable: false,
        configurable: true,
    });
}

function getCachedStructureReference<T extends typeof structuresToReference[number]>(room: Room, structureType: T): Structure<T> {
    if (!room.controller) return null;

    const cacheKey = room.name + ':' + structureType + ':id';
    const structureId = cache.inHeap(cacheKey, 250, () => {
        if (CONTROLLER_STRUCTURES[structureType][room.controller.level] === 0) return null;

        const structures = room.structuresByType[structureType] || [];
        if (structures.length > 0) {
            return structures[0].id;
        }

        return null;
    });

    if (!structureId) return null;

    const structure = Game.getObjectById<Structure<T>>(structureId);
    if (!structure) {
        cache.removeEntry(null, cacheKey);
    }

    return structure;
};

Object.defineProperty(Room.prototype, 'factoryManager', {
    get(this: Room) {
        return cache.inObject(this, 'factoryManager', 1, () => this.factory.isOperational() ? new FactoryManager(this.name) : null);
    },
    enumerable: false,
    configurable: true,
});

Object.defineProperty(Room.prototype, 'bays', {
    get(this: Room) {
        return cache.inObject(this, 'bays', 1, () => {
            if (!this.isMine()) return [];
            if (!this.roomPlanner) return [];

            const bays = [];
            for (const pos of this.roomPlanner.getLocations('bay_center')) {
                let hasHarvester = false;
                if (this.roomPlanner.isPlannedLocation(pos, 'harvester')) {
                    // @todo Don't use pos.lookFor, instead filter this.creepsByRole.harvester.
                    const creeps = pos.lookFor(LOOK_CREEPS);
                    hasHarvester = creeps.length > 0 && creeps[0].my && creeps[0].memory.role === 'harvester';
                }

                const bay = new Bay(pos, hasHarvester);
                bays.push(bay);
            }

            return bays;
        });
    },
    enumerable: false,
    configurable: true,
});

/**
 * Adds some additional data to room objects.
 */
Room.prototype.enhanceData = function (this: Room) {
    if (this.factory && !this.factory.isOperational()) delete this.factory;
    if (this.terminal && !this.terminal.isOperational()) delete this.terminal;
    if (this.storage && !this.storage.isOperational()) delete this.storage;

    // Prepare memory for creep cache (filled globally later).
    if (!this.creeps) {
        this.creeps = {};
        this.creepsByRole = {};
    }
};

/**
* Gathers information about a room and saves it to memory for faster access.
*/
Room.prototype.scan = function (this: Room) {
    this.updateControllerContainer();
    this.updateControllerLink();
    this.updateStorageLink();
};

/**
 * Updates location of the room's controller container.
 */
Room.prototype.updateControllerContainer = function (this: Room) {
    // @todo Split into a get function and set / delete value according to result.
    // Check if the controller has a container nearby.
    // Use room planner locations if available.
    if (this.roomPlanner) {
        const containerPositions: RoomPosition[] = this.roomPlanner.getLocations('container.controller');
        if (containerPositions.length > 0) {
            const structures = _.filter(
                this.structuresByType[STRUCTURE_CONTAINER], 
                structure => _.some(containerPositions, pos => pos.x === structure.pos.x && pos.y === structure.pos.y),
            );
            this.memory.controllerContainer = structures.length > 0 && structures[0].id;
            if (!this.memory.controllerContainer) delete this.memory.controllerContainer;
            return;
        }
    }

    const structures = _.filter(
        this.structuresByType[STRUCTURE_CONTAINER],
        structure => structure.pos.getRangeTo(this.controller) <= 3,
    );
    this.memory.controllerContainer = structures.length > 0 && structures[0].id;
    if (!this.memory.controllerContainer) delete this.memory.controllerContainer;
};

/**
 * Updates location of the room's controller link.
 */
Room.prototype.updateControllerLink = function (this: Room) {
    // @todo Split into a get function and set / delete value according to result.
    // Check if the controller has a link nearby.
    // Use room planner locations if available.
    if (this.roomPlanner) {
        const linkPositions: RoomPosition[] = this.roomPlanner.getLocations('link.controller');
        if (linkPositions.length > 0) {
            const structures = _.filter(
                this.myStructuresByType[STRUCTURE_LINK],
                structure => _.some(linkPositions, pos => pos.x === structure.pos.x && pos.y === structure.pos.y),
            );
            this.memory.controllerLink = structures.length > 0 && structures[0].id;
            if (!this.memory.controllerLink) delete this.memory.controllerLink;
            return;
        }
    }

    const structures = _.filter(
        this.myStructuresByType[STRUCTURE_LINK], 
        structure => structure.pos.getRangeTo(this.controller) <= 3,
    );
    this.memory.controllerLink = structures.length > 0 && structures[0].id;
    if (!this.memory.controllerLink) delete this.memory.controllerLink;
};

/**
 * Updates location of the room's storage link.
 */
Room.prototype.updateStorageLink = function (this: Room) {
    // @todo Split into a get function and set / delete value according to result.
    if (!this.storage) return;

    // Check if storage has a link nearby.
    // Use room planner locations if available.
    if (this.roomPlanner) {
        const linkPositions: RoomPosition[] = this.roomPlanner.getLocations('link.storage');
        if (linkPositions.length > 0) {
            const structures = _.filter(
                this.myStructuresByType[STRUCTURE_LINK], 
                structure => _.some(linkPositions, pos => pos.x === structure.pos.x && pos.y === structure.pos.y),
            );
            this.memory.storageLink = structures.length > 0 && structures[0].id;
            return;
        }
    }

    const structures = _.filter(
        this.myStructuresByType[STRUCTURE_LINK],
        structure => structure.pos.getRangeTo(this.storage) <= 3,
    );
    this.memory.storageLink = structures.length > 0 && structures[0].id;
};

/**
 * Decides if this room needs to send out a scout.
 *
 * @return {boolean}
 *   True if a scout is needed.
 */
Room.prototype.needsScout = function (this: Room) {
    if (!Memory.strategy) return false;

    const room = this;
    return _.any(Memory.strategy.roomList, (info: RoomListEntry) => info.origin === room.name && info.scoutPriority >= 1);
};

/**
 * Decides if the room belongs to the player.
 *
 * @param {boolean} allowReserved
 *   If specified, rooms reserved by the player are acceptable as well.
 *
 * @return {boolean}
 *   True if the room is owned / reserved by the player.
 */
Room.prototype.isMine = function (this: Room, allowReserved?: boolean) {
    if (!this.controller) return false;
    if (this.controller.my) return true;

    if (!allowReserved) return false;
    if (!this.controller.reservation) return false;
    if (this.controller.reservation.username === getUsername()) return true;

    return false;
};

Room.prototype.needsReclaiming = function (this: Room) {
    const reclaimManager = container.get('ReclaimManager');
    return reclaimManager.roomNeedsReclaiming(this);
};

Room.prototype.isSafeForReclaiming = function (this: Room) {
    const reclaimManager = container.get('ReclaimManager');
    return reclaimManager.roomIsSafeForReclaiming(this);
};