src/room/planner/variation-builder-base.ts
import hivemind from 'hivemind';
import PlacementManager from 'room/planner/placement-manager';
import RoomPlan from 'room/planner/room-plan';
import {handleMapArea} from 'utils/map';
declare global {
type StepResult = 'ok' | 'failed' | 'done';
}
export default class RoomVariationBuilderBase {
protected currentStep: number;
protected roomPlan: RoomPlan;
protected terrain: RoomTerrain;
protected finished: boolean;
protected minCutBounds: MinCutRect[];
protected placementManager: PlacementManager;
protected constructor(
protected roomName: string,
protected variation: string,
wallMatrix: CostMatrix,
exitMatrix: CostMatrix,
) {
this.currentStep = 0;
this.finished = false;
this.roomPlan = new RoomPlan(this.roomName);
this.terrain = new Room.Terrain(this.roomName);
this.minCutBounds = [];
this.placementManager = new PlacementManager(this.roomPlan, new PathFinder.CostMatrix(), wallMatrix, exitMatrix);
}
getRoomPlan(): RoomPlan {
return this.roomPlan;
}
buildNextStep() {
const stepResult = this.buildStep(this.currentStep++);
// @todo Provide a mechanism by which any step may abort the calculation.
// @todo Handle 'failed'.
if (stepResult === 'done') {
this.finished = true;
hivemind.log('rooms', this.roomName).info('Finished building room plan variation', this.variation);
}
}
buildStep(step: number): StepResult {
return 'done';
}
placeBayStructures(bayPosition: RoomPosition, options: {spawn?: boolean; source?: boolean; id?: number} = {}) {
const allowNearExit = options.source && !options.spawn;
if (this.roomPlan.canPlaceMore('spawn') && options.spawn) {
handleMapArea(bayPosition.x, bayPosition.y, (x, y) => {
if (this.terrain.get(x, y) === TERRAIN_MASK_WALL) return true;
if (!this.placementManager.isBuildableTile(x, y, false, allowNearExit)) return true;
if (x === bayPosition.x && y === bayPosition.y) return true;
// Only place spawn where a road tile is adjacent, so creeps can
// actually exit when a harvester is on its spot.
let spawnPlaced = false;
handleMapArea(x, y, (x2, y2) => {
if (x2 == bayPosition.x && y2 == bayPosition.y) return true;
if (!this.roomPlan.hasPosition('road', new RoomPosition(x2, y2, this.roomName))) return true;
this.placementManager.planLocation(new RoomPosition(x, y, this.roomName), 'spawn.' + this.roomPlan.getPositions('spawn').length);
this.placementManager.planLocation(new RoomPosition(x, y, this.roomName), 'spawn');
spawnPlaced = true;
return false;
});
if (spawnPlaced) return false;
return true;
});
}
let linkPlaced = !this.roomPlan.canPlaceMore('link') || !options.source;
handleMapArea(bayPosition.x, bayPosition.y, (x, y) => {
if (this.terrain.get(x, y) === TERRAIN_MASK_WALL) return;
if (!this.placementManager.isBuildableTile(x, y, false, allowNearExit)) return;
if (x === bayPosition.x && y === bayPosition.y) return;
if (linkPlaced) {
this.placementManager.planLocation(new RoomPosition(x, y, this.roomName), 'extension');
if (options.source) {
this.placementManager.planLocation(new RoomPosition(x, y, this.roomName), 'extension.harvester');
}
else {
if (typeof options.id !== 'undefined') {
this.placementManager.planLocation(new RoomPosition(x, y, this.roomName), 'extension.bay.' + options.id);
}
this.placementManager.planLocation(new RoomPosition(x, y, this.roomName), 'extension.bay');
}
}
else {
this.placementManager.planLocation(new RoomPosition(x, y, this.roomName), 'link');
if (options.source) {
this.placementManager.planLocation(new RoomPosition(x, y, this.roomName), 'link.source');
}
linkPlaced = true;
}
});
}
/**
* Places a link near a given road.
*
* @param {RoomPosition[]} sourceRoads
* Positions that make up the road.
* @param {string} linkType
* Type identifier for this link, like `source` or `controller`.
*/
placeLink(sourceRoads: RoomPosition[], linkType: string) {
const targetPos = this.findLinkPosition(sourceRoads);
if (!targetPos) return;
if (linkType) {
this.placementManager.planLocation(targetPos, 'link.' + linkType, null);
}
this.placementManager.planLocation(targetPos, 'link');
this.protectPosition(targetPos, 0);
}
/**
* Finds a spot for a link near a given road.
*
* @param {RoomPosition[]} sourceRoads
* Positions that make up the road.
*
* @return {RoomPosition}
* A Position at which a container can be placed.
*/
findLinkPosition(sourceRoads: RoomPosition[]): RoomPosition {
let targetPosition: RoomPosition;
for (const pos of _.slice(sourceRoads, 0, 3)) {
handleMapArea(pos.x, pos.y, (x, y) => {
if (this.placementManager.isBuildableTile(x, y, false, true)) {
targetPosition = new RoomPosition(x, y, pos.roomName);
return false;
}
return true;
});
if (targetPosition) break;
}
return targetPosition;
}
/**
* Places a container near a given road.
*
* @param {RoomPosition[]} sourceRoads
* Positions that make up the road.
* @param {string} containerType
* Type identifier for this container, like `source` or `controller`.
*/
placeContainer(sourceRoads: RoomPosition[], containerType: string) {
const targetPos = this.findContainerPosition(sourceRoads);
if (!targetPos) return null;
if (containerType) {
this.placementManager.planLocation(targetPos, 'container.' + containerType, null);
}
this.placementManager.planLocation(targetPos, 'container', 1);
return targetPos;
}
/**
* Finds a spot for a container near or on a given road.
*
* @param {RoomPosition[]} sourceRoads
* Positions that make up the road.
*
* @return {RoomPosition}
* A Position at which a container can be placed.
*/
findContainerPosition(sourceRoads: RoomPosition[]): RoomPosition {
if (sourceRoads[0] && this.placementManager.isBuildableTile(sourceRoads[0].x, sourceRoads[0].y, true, true)) {
return sourceRoads[0];
}
if (sourceRoads[1] && this.placementManager.isBuildableTile(sourceRoads[1].x, sourceRoads[1].y, true, true)) {
return sourceRoads[1];
}
return this.findLinkPosition(sourceRoads);
}
/**
* Adds a position to be protected by minCut.
*/
protectPosition(pos: RoomPosition, distance?: number) {
if (typeof distance === 'undefined') distance = hivemind.settings.get('minCutRampartDistance');
const x1 = Math.max(2, pos.x - distance);
const x2 = Math.min(47, pos.x + distance);
const y1 = Math.max(2, pos.y - distance);
const y2 = Math.min(47, pos.y + distance);
this.minCutBounds.push({x1, x2, y1, y2});
}
}