diff --git a/src/creepSetups/setups.ts b/src/creepSetups/setups.ts index 1c57dcbee..f71f998b7 100644 --- a/src/creepSetups/setups.ts +++ b/src/creepSetups/setups.ts @@ -22,6 +22,8 @@ export const Roles = { ranged : 'hydralisk', healer : 'transfuser', dismantler: 'lurker', + drill : 'drill', + coolant : 'coolant', }; /** @@ -319,4 +321,23 @@ export const CombatSetups = { }, + drill: { + default: new CreepSetup(Roles.drill, { + pattern : [MOVE, ATTACK, ATTACK, MOVE], + sizeLimit: Infinity, + }), + }, + + coolant: { + default: new CreepSetup(Roles.coolant, { + pattern : [HEAL, MOVE], + sizeLimit: Infinity, + }), + small: new CreepSetup(Roles.coolant, { + pattern : [HEAL, MOVE], + sizeLimit: 16, + }), + } + + }; diff --git a/src/declarations/memory.d.ts b/src/declarations/memory.d.ts index 5e32b7282..f9d1d820b 100644 --- a/src/declarations/memory.d.ts +++ b/src/declarations/memory.d.ts @@ -21,7 +21,12 @@ interface Memory { operationMode: operationMode; log: LoggerMemory; enableVisuals: boolean; - }; + powerCollection: { + enabled: boolean; + maxRange: number; + minPower: number; + }; + } profiler?: any; stats: any; constructionSites: { [id: string]: number }; diff --git a/src/directives/initializer.ts b/src/directives/initializer.ts index 081a081bd..cf21dba63 100644 --- a/src/directives/initializer.ts +++ b/src/directives/initializer.ts @@ -25,6 +25,7 @@ import {DirectiveTargetSiege} from './targeting/siegeTarget'; import {DirectiveTerminalEmergencyState} from './terminalState/terminalState_emergency'; import {DirectiveTerminalEvacuateState} from './terminalState/terminalState_evacuate'; import {DirectiveTerminalRebuildState} from './terminalState/terminalState_rebuild'; +import {DirectivePowerMine} from "./resource/powerMine"; /** * This is the initializer for directives, which maps flags by their color code to the corresponding directive @@ -92,6 +93,8 @@ export function DirectiveWrapper(flag: Flag): Directive | undefined { return new DirectiveExtract(flag); case COLOR_BLUE: return new DirectiveHaul(flag); + case COLOR_RED: + return new DirectivePowerMine(flag); } break; diff --git a/src/directives/resource/haul.ts b/src/directives/resource/haul.ts index 9a2c452dd..12fbd9980 100644 --- a/src/directives/resource/haul.ts +++ b/src/directives/resource/haul.ts @@ -10,7 +10,7 @@ interface DirectiveHaulMemory extends FlagMemory { /** - * Hauling directive: spawns hauler creeps to move large amounts of resourecs from a location (e.g. draining a storage) + * Hauling directive: spawns hauler creeps to move large amounts of resources from a location (e.g. draining a storage) */ @profile export class DirectiveHaul extends Directive { @@ -21,6 +21,7 @@ export class DirectiveHaul extends Directive { private _store: StoreDefinition; private _drops: { [resourceType: string]: Resource[] }; + private _finishAtTime: number; memory: DirectiveHaulMemory; @@ -107,6 +108,10 @@ export class DirectiveHaul extends Directive { run(): void { if (_.sum(this.store) == 0 && this.pos.isVisible) { + // If everything is picked up, crudely give enough time to bring it back + this._finishAtTime = this._finishAtTime || (Game.time + 300); + } + if (Game.time >= this._finishAtTime) { this.remove(); } } diff --git a/src/directives/resource/powerMine.ts b/src/directives/resource/powerMine.ts new file mode 100644 index 000000000..0c8fca34e --- /dev/null +++ b/src/directives/resource/powerMine.ts @@ -0,0 +1,132 @@ +import {Directive} from '../Directive'; +import {profile} from '../../profiler/decorator'; +import {PowerDrillOverlord} from '../../overlords/powerMining/PowerDrill'; +import {Pathing} from "../../movement/Pathing"; +import {calculateFormationStrength} from "../../utilities/creepUtils"; +import {PowerHaulingOverlord} from "../../overlords/powerMining/PowerHauler"; +import {log} from "../../console/log"; + + +interface DirectivePowerMineMemory extends FlagMemory { + totalResources?: number; +} + + +/** + * PowerMining directive: kills power banks and collects the resources. + */ +@profile +export class DirectivePowerMine extends Directive { + + static directiveName = 'powerMine'; + static color = COLOR_YELLOW; + static secondaryColor = COLOR_RED; + + miningDone: boolean; + pickupDone: boolean; + haulDirectiveCreated: boolean; + private _powerBank: StructurePowerBank | undefined; + private _drops: { [resourceType: string]: Resource[] }; + + memory: DirectivePowerMineMemory; + + constructor(flag: Flag) { + super(flag); + this._powerBank = this.room != undefined ? this.pos.lookForStructure(STRUCTURE_POWER_BANK) as StructurePowerBank : undefined; + } + + spawnMoarOverlords() { + if (!this.miningDone && this.powerBank) { + this.overlords.powerMine = new PowerDrillOverlord(this); + } + if (!this.pickupDone) { + this.spawnHaulers(); + } + } + + get drops(): { [resourceType: string]: Resource[] } { + if (!this.pos.isVisible) { + return {}; + } + if (!this._drops) { + let drops = (this.pos.lookFor(LOOK_RESOURCES) || []) as Resource[]; + this._drops = _.groupBy(drops, drop => drop.resourceType); + } + return this._drops; + } + + get hasDrops(): boolean { + return _.keys(this.drops).length > 0; + } + + get powerBank(): StructurePowerBank | undefined { + this._powerBank = this._powerBank || this.room ? this.flag.pos.lookForStructure(STRUCTURE_POWER_BANK) as StructurePowerBank : undefined; + return this._powerBank; + } + + /** + * Total amount of resources remaining to be transported; cached into memory in case room loses visibility + */ + get totalResources(): number { + if (this.memory.totalResources == undefined) { + return 5000; // pick some non-zero number so that powerMiners will spawn + } + if (this.pos.isVisible) { + this.memory.totalResources = this.powerBank ? this.powerBank.power : this.memory.totalResources; // update total amount remaining + } + return this.memory.totalResources; + } + + calculateRemainingLifespan() { + if (!this.room) { + return undefined; + } else if (this.powerBank == undefined) { + if (this.miningDone) { + // Power Bank is gone + return 0; + } + } else { + let tally = calculateFormationStrength(this.powerBank.pos.findInRange(FIND_MY_CREEPS, 4)); + let healStrength: number = tally.heal * HEAL_POWER || 0; + let attackStrength: number = tally.attack * ATTACK_POWER || 0; + // PB have 50% hitback, avg damage is attack strength if its enough healing, otherwise healing + let avgDamagePerTick = Math.min(attackStrength, healStrength*2); + return this.powerBank.hits / avgDamagePerTick; + } + } + + spawnHaulers() { + log.info("Checking spawning haulers"); + // Begin checking for spawn haulers at 666 estimated ticks before PB destruction + if (this.haulDirectiveCreated || this.room && (!this.powerBank || this.powerBank.hits < 500000)) { + log.debug('Activating spawning haulers for power mining in room ' + this.pos.roomName); + this.haulDirectiveCreated = true; + this.overlords.powerHaul = new PowerHaulingOverlord(this); + } + } + + setMiningDone(name: string) { + log.debug("Setting mining done and removing overlord for power mine in room " + this.room + " at time " + Game.time); + delete this.overlords[name]; + this.miningDone = true; + this._powerBank = undefined; + } + + /** + * This states when all the power has been picked up. Once all power has been picked up and delivered remove the directive + */ + isPickupDone(): boolean { + if (!this.pickupDone && this.miningDone && this.room && this.pos.isVisible && !this.hasDrops) { + this.pickupDone = true; + } + return this.pickupDone; + } + + init(): void { + this.alert(`PowerMine directive active`); + } + + run(): void { + } +} + diff --git a/src/intel/RoomIntel.ts b/src/intel/RoomIntel.ts index 565d681a4..5fcbf8d5a 100644 --- a/src/intel/RoomIntel.ts +++ b/src/intel/RoomIntel.ts @@ -7,6 +7,9 @@ import {ExpansionEvaluator} from '../strategy/ExpansionEvaluator'; import {getCacheExpiration, irregularExponentialMovingAverage} from '../utilities/utils'; import {Zerg} from '../zerg/Zerg'; import {MY_USERNAME} from '../~settings'; +import {Cartographer, ROOMTYPE_ALLEY} from "../utilities/Cartographer"; +import {getAllColonies} from "../Colony"; +import {DirectivePowerMine} from "../directives/resource/powerMine"; const RECACHE_TIME = 2500; const OWNED_RECACHE_TIME = 1000; @@ -327,6 +330,36 @@ export class RoomIntel { return 0; } + /** + * Find PowerBanks within range of maxRange and power above minPower to mine + * Creates directive to mine it + * TODO refactor when factory resources come out to be more generic + */ + private static minePowerBanks(room: Room) { + let powerSetting = Memory.settings.powerCollection; + if (powerSetting.enabled && Game.time % 300 == 0 && Cartographer.roomType(room.name) == ROOMTYPE_ALLEY) { + let powerBank = _.first(room.find(FIND_STRUCTURES).filter(struct => struct.structureType == STRUCTURE_POWER_BANK)) as StructurePowerBank; + if (powerBank != undefined && powerBank.ticksToDecay > 4000 && powerBank.power >= powerSetting.minPower) { + Game.notify(`Looking for power banks in ${room} found ${powerBank} with power ${powerBank.power} and ${powerBank.ticksToDecay} TTL.`); + if (DirectivePowerMine.isPresent(powerBank.pos, 'pos')) { + Game.notify(`Already mining room ${powerBank.room}!`); + return; + } + + let colonies = getAllColonies().filter(colony => colony.level > 6); + + for (let colony of colonies) { + let route = Game.map.findRoute(colony.room, powerBank.room); + if (route != -2 && route.length <= powerSetting.maxRange) { + Game.notify(`FOUND POWER BANK IN RANGE ${route.length}, STARTING MINING ${powerBank.room}`); + DirectivePowerMine.create(powerBank.pos); + return; + } + } + + } + } + } static run(): void { let alreadyComputedScore = false; @@ -365,7 +398,7 @@ export class RoomIntel { if (room.controller && Game.time % 5 == 0) { this.recordControllerInfo(room.controller); } - + this.minePowerBanks(room); } } diff --git a/src/memory/Memory.ts b/src/memory/Memory.ts index 35db27790..529159842 100644 --- a/src/memory/Memory.ts +++ b/src/memory/Memory.ts @@ -199,6 +199,11 @@ export class Mem { operationMode: DEFAULT_OPERATION_MODE, log : {}, enableVisuals: true, + powerCollection: { + enabled: false, + maxRange: 5, + minPower: 5000, + }, }); if (!Memory.stats) { Memory.stats = {}; diff --git a/src/overlords/powerMining/PowerDrill.ts b/src/overlords/powerMining/PowerDrill.ts new file mode 100644 index 000000000..5249ca4ff --- /dev/null +++ b/src/overlords/powerMining/PowerDrill.ts @@ -0,0 +1,221 @@ +import {CombatZerg} from '../../zerg/CombatZerg'; +import {DirectiveSKOutpost} from '../../directives/colony/outpostSK'; +import {RoomIntel} from '../../intel/RoomIntel'; +import {minBy} from '../../utilities/utils'; +import {Mem} from '../../memory/Memory'; +import {debug, log} from '../../console/log'; +import {OverlordPriority} from '../../priorities/priorities_overlords'; +import {Visualizer} from '../../visuals/Visualizer'; +import {profile} from '../../profiler/decorator'; +import {CombatOverlord} from '../CombatOverlord'; +import {CombatSetups, Roles} from '../../creepSetups/setups'; +import {OverlordMemory} from '../Overlord'; +import {DirectivePowerMine} from "../../directives/resource/powerMine"; +import {DirectiveHaul} from "../../directives/resource/haul"; +import {calculateFormationStrength} from "../../utilities/creepUtils"; +import {Zerg} from "../../zerg/Zerg"; +import {MoveOptions} from "../../movement/Movement"; + +interface PowerDrillOverlordMemory extends OverlordMemory { + targetPBID?: string; +} + +/** + * PowerDrillOverlord -- spawns drills and coolant to mine power banks + */ +@profile +export class PowerDrillOverlord extends CombatOverlord { + + static requiredRCL = 7; + + directive: DirectivePowerMine; + memory: PowerDrillOverlordMemory; + partnerMap: Map; + isDone: boolean; + + drills: CombatZerg[]; + coolant: CombatZerg[]; + + constructor(directive: DirectivePowerMine, priority = OverlordPriority.powerMine.drill) { + super(directive, 'powerDrill', priority, PowerDrillOverlord.requiredRCL); + this.directive = directive; + this.priority += this.outpostIndex * OverlordPriority.powerMine.roomIncrement; + this.drills = this.combatZerg(Roles.drill); + this.coolant = this.combatZerg(Roles.coolant); + this.memory = Mem.wrap(this.directive.memory, 'powerDrill'); + this.partnerMap = new Map(); + } + + refresh() { + super.refresh(); + this.memory = Mem.wrap(this.directive.memory, 'powerDrill'); + + } + + init() { + this.wishlist(1, CombatSetups.drill.default); + this.wishlist(2, CombatSetups.coolant.small); + } + + private getHostileDrill(powerBank: StructurePowerBank) { + return powerBank.hits < powerBank.hitsMax && powerBank.pos.findInRange(FIND_HOSTILE_CREEPS, 2)[0]; + } + + private handleHostileDrill(hostileDrill: Creep, powerBank: StructurePowerBank) { + Game.notify(`${hostileDrill.owner.username} power harvesting ${powerBank.room.name}, competing for same power bank.`); + // this.directive.remove(); + } + + private handleDrill(drill: CombatZerg) { + if (drill.spawning) { + return; + } + if (!this.directive.powerBank) { + if (!this.room) { + // We are not there yet + } else { + // If power bank is dead + if (this.directive.powerBank == undefined && !this.directive.haulDirectiveCreated) { + if (this.pos.lookFor(LOOK_RESOURCES).length == 0) { + // Well shit, we didn't finish mining + log.error(`WE FAILED. SORRY CHIEF, COULDN'T FINISHED POWER MINING IN ${this.room} DELETING CREEP at time: ${Game.time}`); + this.directive.remove(); + return; + } + Game.notify(`Power bank in ${this.room.print} is dead.`); + drill.say('๐Ÿ’€ RIP ๐Ÿ’€'); + this.directive.setMiningDone(this.name); + let result = drill.retire(); + if (result == ERR_BUSY) { + drill.spawning + } + log.notify("FINISHED POWER MINING IN " + this.room + " DELETING CREEP at time: " + Game.time.toString() + " result: " + result); + return; + } + } + } + + // Go to power room + if (!this.room || drill.room != this.room || drill.pos.isEdge || !this.directive.powerBank) { + // log.debugCreep(drill, `Going to room!`); + log.notify("Drill is moving to power site in " + this.pos.roomName + "."); + drill.goTo(this.pos); + return; + } + + // Handle killing bank + if (drill.pos.isNearTo(this.directive.powerBank)) { + if (!this.partnerMap.get(drill.name)) { + this.partnerMap.set(drill.name, []); + } + PowerDrillOverlord.periodicSay(drill,'Drillingโš’๏ธ'); + drill.attack(this.directive.powerBank); + } else { + PowerDrillOverlord.periodicSay(drill,'๐Ÿš—Traveling๐Ÿš—'); + drill.goTo(this.directive.powerBank); + } + } + + private handleCoolant(coolant: CombatZerg) { + if (coolant.spawning) { + return; + } + // Go to powerbank room + if (!this.room || coolant.room != this.room || coolant.pos.isEdge) { + // log.debugCreep(coolant, `Going to room!`); + coolant.healSelfIfPossible(); + coolant.goTo(this.pos); + return; + } else if (!this.directive.powerBank) { + // If power bank is dead + Game.notify("Power bank in " + this.room + " is dead."); + coolant.say('๐Ÿ’€ RIP ๐Ÿ’€'); + this.isDone = true; + coolant.retire(); + return; + } + if (coolant.pos.getRangeTo(this.directive.powerBank) > 3) { + coolant.goTo(this.directive.powerBank); + } else if (coolant.pos.findInRange(FIND_MY_CREEPS, 1).filter(creep => _.contains(creep.name, "drill")).length == 0) { + let target = _.sample(_.filter(this.drills, drill => drill.hits < drill.hitsMax)); + if (target) { + coolant.goTo(target, {range: 1, noPush: true}); + } + } + // else if (coolant.pos.getRangeTo(this.directive.powerBank) == 1) { + // coolant.move(Math.round(Math.random()*7) as DirectionConstant); + // } + else { + let drill = _.sample(_.filter(this.drills, drill => drill.hits < drill.hitsMax)); + if (drill) { coolant.goTo(drill); } + } + + coolant.autoHeal(); + } + + // private findDrillToPartner(coolant: CombatZerg) { + // let needsHealing = _.min(Array.from(this.partnerMap.keys()), key => this.partnerMap.get(key)!.length); + // if (this.partnerMap.get(needsHealing)) { + // this.partnerMap.get(needsHealing)!.concat(coolant.name); + // coolant.say(needsHealing.toString()); + // coolant.memory.partner = needsHealing; + // } else { + // + // } + // //console.log(JSON.stringify(this.partnerMap)); + // // let newPartner = _.sample(_.filter(this.drills, drill => this.room == drill.room)); + // // coolant.memory.partner = newPartner != undefined ? newPartner.name : undefined; + // coolant.say('Partnering!'); + // } + // + // private runPartnerHealing(coolant: CombatZerg) { + // if (coolant.memory.partner) { + // let drill = Game.creeps[coolant.memory.partner]; + // if (!drill) { + // // Partner is dead + // coolant.memory.partner = undefined; + // this.findDrillToPartner(coolant) + // } else if (!coolant.pos.isNearTo(drill)) { + // PowerDrillOverlord.periodicSay(coolant,'๐Ÿš—Traveling๏ธ'); + // coolant.goTo(drill); + // } else { + // PowerDrillOverlord.periodicSay(coolant,'โ„๏ธCoolingโ„๏ธ'); + // coolant.heal(drill); + // } + // if (Game.time % 10 == PowerDrillOverlord.getCreepNameOffset(coolant)) { + // this.findDrillToPartner(coolant); + // } + // return; + // } else { + // this.findDrillToPartner(coolant); + // } + // } + + static periodicSay(zerg: Zerg, text: string) { + if (Game.time % 10 == PowerDrillOverlord.getCreepNameOffset(zerg)) { + zerg.say(text, true); + } + } + + static getCreepNameOffset(zerg: Zerg) { + return parseInt(zerg.name.charAt(zerg.name.length-1)) || 0; + } + + run() { + this.autoRun(this.drills, drill => this.handleDrill(drill)); + this.autoRun(this.coolant, coolant => this.handleCoolant(coolant)); + if (this.isDone && !this.directive.miningDone) { + this.drills.forEach(drill => drill.retire()); + this.coolant.forEach(coolant => coolant.retire()); + this.directive.setMiningDone(this.name); + delete this.directive.overlords[this.name]; + } + } + + visuals() { + if (this.room && this.directive.powerBank) { + Visualizer.marker(this.directive.powerBank.pos); + } + } + +} diff --git a/src/overlords/powerMining/PowerHauler.ts b/src/overlords/powerMining/PowerHauler.ts new file mode 100644 index 000000000..1a04d4131 --- /dev/null +++ b/src/overlords/powerMining/PowerHauler.ts @@ -0,0 +1,145 @@ +import {Overlord} from '../Overlord'; +import {OverlordPriority} from '../../priorities/priorities_overlords'; +import {Zerg} from '../../zerg/Zerg'; +import {Tasks} from '../../tasks/Tasks'; +import {log} from '../../console/log'; +import {Energetics} from '../../logistics/Energetics'; +import {profile} from '../../profiler/decorator'; +import {Roles, Setups} from '../../creepSetups/setups'; +import {calculateFormationStrength} from "../../utilities/creepUtils"; +import {DirectivePowerMine} from "../../directives/resource/powerMine"; + +/** + * Spawns special-purpose haulers for transporting resources to/from a specified target + */ +@profile +export class PowerHaulingOverlord extends Overlord { + + haulers: Zerg[]; + directive: DirectivePowerMine; + tickToSpawnOn: number; + numHaulers: number; + + requiredRCL = 6; + // Allow time for body to spawn + prespawnAmount = 250; + + constructor(directive: DirectivePowerMine, priority = OverlordPriority.collectionUrgent.haul) { + super(directive, 'powerHaul', priority); + this.directive = directive; + this.haulers = this.zerg(Roles.transport); + // Spawn haulers to collect ALL the power at the same time. + let haulingPartsNeeded = this.directive.totalResources/CARRY_CAPACITY; + // Calculate amount of hauling each hauler provides in a lifetime + let haulerCarryParts = Setups.transporters.default.getBodyPotential(CARRY, this.colony); + // Calculate number of haulers + this.numHaulers = Math.round(haulingPartsNeeded/haulerCarryParts); + // setup time to request the haulers + this.tickToSpawnOn = Game.time + (this.directive.calculateRemainingLifespan() || 0) - this.prespawnAmount; + } + + init() { + if (!this.colony.storage || _.sum(this.colony.storage.store) > Energetics.settings.storage.total.cap) { + return; + } + } + + protected handleHauler(hauler: Zerg) { + if (_.sum(hauler.carry) == 0 && this.directive.pickupDone) { + hauler.retire(); + } else if (_.sum(hauler.carry) == 0) { + // Travel to directive and collect resources + if (this.directive.pickupDone) { + hauler.say('๐Ÿ’€ RIP ๐Ÿ’€',true); + log.warning(`${hauler.name} is committing suicide as directive is done!`); + this.numHaulers = 0; + hauler.retire(); + } + if (hauler.inSameRoomAs(this.directive)) { + // Pick up drops first + if (this.directive.hasDrops) { + let allDrops: Resource[] = _.flatten(_.values(this.directive.drops)); + let drop = allDrops[0]; + if (drop) { + hauler.task = Tasks.pickup(drop); + return; + } + } else if (this.directive.powerBank) { + if (hauler.pos.getRangeTo(this.directive.powerBank) > 4) { + hauler.goTo(this.directive.powerBank); + } else { + hauler.say('๐Ÿšฌ', true); + } + return; + } else if (this.room && this.room.drops) { + let allDrops: Resource[] = _.flatten(_.values(this.room.drops)); + let drop = allDrops[0]; + if (drop) { + hauler.task = Tasks.pickup(drop); + return; + } else { + hauler.say('๐Ÿ’€ RIP ๐Ÿ’€',true); + log.warning(`${hauler.name} is committing suicide!`); + hauler.retire(); + return; + } + } + // Shouldn't reach here + log.warning(`${hauler.name} in ${hauler.room.print}: nothing to collect!`); + } else { + hauler.goTo(this.directive); + } + } else { + // Travel to colony room and deposit resources + if (hauler.inSameRoomAs(this.colony)) { + for (let resourceType in hauler.carry) { + if (hauler.carry[resourceType] == 0) continue; + if (resourceType == RESOURCE_ENERGY) { // prefer to put energy in storage + if (this.colony.storage && _.sum(this.colony.storage.store) < STORAGE_CAPACITY) { + hauler.task = Tasks.transfer(this.colony.storage, resourceType); + return; + } else if (this.colony.terminal && _.sum(this.colony.terminal.store) < TERMINAL_CAPACITY) { + hauler.task = Tasks.transfer(this.colony.terminal, resourceType); + return; + } + } else { // prefer to put minerals in terminal + if (this.colony.terminal && _.sum(this.colony.terminal.store) < TERMINAL_CAPACITY) { + hauler.task = Tasks.transfer(this.colony.terminal, resourceType); + return; + } else if (this.colony.storage && _.sum(this.colony.storage.store) < STORAGE_CAPACITY) { + hauler.task = Tasks.transfer(this.colony.storage, resourceType); + return; + } + } + } + // Shouldn't reach here + log.warning(`${hauler.name} in ${hauler.room.print}: nowhere to put resources!`); + } else { + hauler.task = Tasks.goToRoom(this.colony.room.name); + } + } + } + + run() { + if (Game.time >= this.tickToSpawnOn && !this.directive.pickupDone) { + Game.notify('Time to spawn haulers ' + this.pos.roomName); + this.wishlist(this.numHaulers, Setups.transporters.default); + } + // Check hauling is done + if (this.directive.isPickupDone() && Game.time % 16 == 0) { + let stillCarryingPower = _.find(this.haulers, hauler => hauler.carry.power != undefined && hauler.carry.power > 0); + if (!stillCarryingPower) { + log.alert(`Deleting Power Mining Directive ${this.directive.print} as no haulers are left carrying power.`); + this.directive.remove(); + } else { + log.debug(`Still carrying power back with ${stillCarryingPower.print} for ${this.directive.print}`); + } + } + for (let hauler of this.haulers) { + if (hauler.isIdle) { + this.handleHauler(hauler); + } + hauler.run(); + } + } +} \ No newline at end of file diff --git a/src/priorities/priorities_overlords.ts b/src/priorities/priorities_overlords.ts index bef3ab436..cffdc6814 100644 --- a/src/priorities/priorities_overlords.ts +++ b/src/priorities/priorities_overlords.ts @@ -72,6 +72,13 @@ export let OverlordPriority = { roomIncrement: 5, }, + powerMine: { + cool : 1050, + drill : 1051, + gather : 604, + roomIncrement: 5, + }, + collection: { // Non-urgent collection of resources, like from a deserted storage haul: 1100 }, diff --git a/src/utilities/creepUtils.ts b/src/utilities/creepUtils.ts new file mode 100644 index 000000000..7e5b1a6b7 --- /dev/null +++ b/src/utilities/creepUtils.ts @@ -0,0 +1,45 @@ +// Creep utilities that don't belong anywhere else + + +// Does not account for range, just total of body parts +export function calculateFormationStrength(creeps : Creep[]): Record { + let tally: Record = { + move : 0, + work : 0, + carry : 0, + attack : 0, + ranged_attack : 0, + tough : 0, + heal : 0, + claim : 0, + }; + + _.forEach(creeps, + function (unit) { + let individualTally = calculateBodyPotential(unit.body); + for (let bodyType in individualTally) { + let type = bodyType as BodyPartConstant; + tally[type] += individualTally[type]; + } + }); + return tally; +} + +export function calculateBodyPotential(body : BodyPartDefinition[]): Record { + let tally: Record = { + move : 0, + work : 0, + carry : 0, + attack : 0, + ranged_attack : 0, + tough : 0, + heal : 0, + claim : 0, + }; + _.forEach(body, function (bodyPart) { + // Needs boost logic + tally[bodyPart.type] += 1; + } + ); + return tally; +} diff --git a/src/zerg/Zerg.ts b/src/zerg/Zerg.ts index dd1a85d45..f5586c0ae 100644 --- a/src/zerg/Zerg.ts +++ b/src/zerg/Zerg.ts @@ -511,6 +511,17 @@ export class Zerg { setOverlord(this, newOverlord); } + // TODO add retire/reassignment logic + // Eg. creep get repurposed, it gets recycled, etc + /** + * When a zerg has no more use for it's current overlord, it will be retired. + * For now, that means RIP + */ + retire() { + this.say('๐Ÿ’€ RIP ๐Ÿ’€', true); + return this.suicide(); + } + /* Reassigns the creep to work under a new overlord and as a new role. */ reassign(newOverlord: Overlord | null, newRole: string, invalidateTask = true) { this.overlord = newOverlord;