Skip to content

Commit

Permalink
feat: Add flag to room manager to prevent checking all structures ove…
Browse files Browse the repository at this point in the history
…r and over again in completely built rooms.
  • Loading branch information
Mirroar committed Aug 14, 2024
1 parent de4ffe9 commit 6f57ac9
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 19 deletions.
87 changes: 68 additions & 19 deletions src/room/room-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ FIND_MY_CONSTRUCTION_SITES */

import cache from 'utils/cache';
import hivemind from 'hivemind';
import PersistentFeatureFlag from 'utils/persistent-feature-flag';
import RemoteMiningOperation from 'operation/remote-mining';
import RoomPlanner from 'room/planner/room-planner';
import {ENEMY_STRENGTH_NONE} from 'room-defense';
import {serializeCoords} from 'utils/serialization';
import RemoteMiningOperation from 'operation/remote-mining';

declare global {
interface Structure {
Expand Down Expand Up @@ -42,10 +43,13 @@ interface ScoredExtractorPosition {
mineralType?: MineralConstant;
}

type RoomManagerFeatureFlag = 'finishedRecovering' | 'cleanedRoom' | 'builtAllStructures' | 'ranAtRcl';

export default class RoomManager {
room: Room;
roomPlanner: RoomPlanner;
memory: RoomManagerMemory;
featureFlags: PersistentFeatureFlag<RoomManagerFeatureFlag>;
roomConstructionSites: ConstructionSite[];
constructionSitesByType: Record<string, ConstructionSite[]>;

Expand Down Expand Up @@ -80,6 +84,8 @@ export default class RoomManager {
}

this.memory = Memory.rooms[this.room.name].manager;

this.featureFlags = new PersistentFeatureFlag<RoomManagerFeatureFlag>('room:managerFlags:' + this.room.name);
}

/**
Expand All @@ -96,25 +102,63 @@ export default class RoomManager {
* Manages the assigned room.
*/
runLogic() {
if (!this.roomPlanner || !this.roomPlanner.isPlanningFinished()) return;
if (this.room.defense.getEnemyStrength() > ENEMY_STRENGTH_NONE && !this.room.controller?.safeMode) return;
if (!this.roomPlanner) return;
if (!this.roomPlanner.isPlanningFinished()) {
// Make sure to reset feature flags when room planner is running.
this.featureFlags.reset();

return;
}

if (this.room.defense.getEnemyStrength() > ENEMY_STRENGTH_NONE && !this.room.controller?.safeMode) {
// Don't build anything while under attack.
// Reset feature flags to make sure we rebuild everything once the threat is gone.
this.featureFlags.reset();

return;
}

// Figure out if rcl has changed and we need to build some new structures.
if (this.room.controller.level !== this.featureFlags.getNumeric('ranAtRcl')) {
this.featureFlags.reset();
this.featureFlags.setNumeric('ranAtRcl', this.room.controller.level);
}

// @todo Figure out if a road, container or rampart has decayed and needs rebuilding.

delete this.memory.runNextTick;
if (this.featureFlags.isSet('builtAllStructures')) return;

this.roomConstructionSites = this.room.find(FIND_MY_CONSTRUCTION_SITES);
this.constructionSitesByType = _.groupBy(this.roomConstructionSites, 'structureType');
this.roomStructures = this.room.structures;
this.structuresByType = this.room.structuresByType;
this.newStructures = 0;

if (this.isRoomRecovering()) {
this.buildRoomDefenseFirst();
if (this.recoverRoom()) return;
this.cleanRoom();
this.manageStructures();

if (!this.structuresByType[STRUCTURE_SPAWN] || this.structuresByType[STRUCTURE_SPAWN].length === 0) return;
if (CONTROLLER_STRUCTURES[STRUCTURE_STORAGE][this.room.controller.level] > 0 && (!this.structuresByType[STRUCTURE_STORAGE] || this.structuresByType[STRUCTURE_STORAGE].length === 0)) return;
// If there's nothing more to build, we're done.
if (this.checkWallIntegrity() && this.roomConstructionSites.length + this.newStructures === 0) {
this.featureFlags.set('builtAllStructures');
}
}

this.cleanRoom();
this.manageStructures();
recoverRoom(): boolean {
if (this.featureFlags.isSet('finishedRecovering')) return false;
if (!this.isRoomRecovering()) {
this.featureFlags.set('finishedRecovering');
return false;
}

this.buildRoomDefenseFirst();

if (!this.structuresByType[STRUCTURE_SPAWN] || this.structuresByType[STRUCTURE_SPAWN].length === 0) return true;
if (CONTROLLER_STRUCTURES[STRUCTURE_STORAGE][this.room.controller.level] > 0 && (!this.structuresByType[STRUCTURE_STORAGE] || this.structuresByType[STRUCTURE_STORAGE].length === 0)) return true;

this.featureFlags.set('finishedRecovering');
return false;
}

isRoomRecovering(): boolean {
Expand Down Expand Up @@ -163,6 +207,8 @@ export default class RoomManager {
* Removes structures that might prevent the room's construction.
*/
cleanRoom() {
if (this.featureFlags.isSet('cleanedRoom')) return;

this.removeHostileStructures();
this.removeHostileConstructionSites();

Expand All @@ -172,6 +218,7 @@ export default class RoomManager {
this.cleanLabs();
this.cleanLinks();
this.cleanWalls();
this.featureFlags.set('cleanedRoom');
}

cleanExtensions() {
Expand Down Expand Up @@ -771,21 +818,23 @@ export default class RoomManager {
checkWallIntegrity(minHits?: number) {
// @todo make this consistent with defense manager.
if (!minHits) minHits = hivemind.settings.get('minWallIntegrity');
const maxHealth = hivemind.settings.get('maxWallHealth');

minHits *= maxHealth[this.room.controller.level] / maxHealth[8];
const maxHealth = hivemind.settings.get('maxWallHealth');
const targetHealth = minHits * maxHealth[this.room.controller.level] / maxHealth[8];

for (const pos of this.roomPlanner.getLocations('rampart')) {
if (this.roomPlanner.isPlannedLocation(pos, 'rampart.ramp')) continue;
return cache.inHeap(`wallIntegrity:${this.room.name}:${minHits}`, 100, () => {
for (const pos of this.roomPlanner.getLocations('rampart')) {
if (this.roomPlanner.isPlannedLocation(pos, 'rampart.ramp')) continue;

// Check if there's a rampart here already.
const structures = pos.lookFor(LOOK_STRUCTURES);
if (_.filter(structures, structure => structure.structureType === STRUCTURE_RAMPART && structure.hits >= minHits).length === 0) {
return false;
// Check if there's a rampart here already.
const structures = pos.lookFor(LOOK_STRUCTURES);
if (_.filter(structures, structure => structure.structureType === STRUCTURE_RAMPART && structure.hits >= targetHealth).length === 0) {
return false;
}
}
}

return true;
return true;
});
}

/**
Expand Down
44 changes: 44 additions & 0 deletions src/utils/persistent-feature-flag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import hivemind from "hivemind";

class PersistentFeatureFlag<FlagOption extends string> {
memory: Partial<Record<FlagOption, boolean | number>>;

constructor(private memoryKey: string) {
if (!hivemind.segmentMemory.isReady()) {
throw new Error("Segment memory is not ready yet");
}

if (!hivemind.segmentMemory.has(this.memoryKey)) {
hivemind.segmentMemory.set(this.memoryKey, {});
}

this.memory = hivemind.segmentMemory.get(this.memoryKey);
}

set(flag: FlagOption) {
this.memory[flag] = true;
}

isSet(flag: FlagOption) {
return this.memory[flag] === true;
}

setNumeric(flag: FlagOption, value: number) {
this.memory[flag] = value;
}

getNumeric(flag: FlagOption) {
return (this.memory[flag] ?? 0) as number;
}

unset(flag: FlagOption) {
delete this.memory[flag];
}

reset() {
this.memory = {};
hivemind.segmentMemory.set(this.memoryKey, this.memory);
}
}

export default PersistentFeatureFlag;

0 comments on commit 6f57ac9

Please sign in to comment.