From 5a3314b1011cf2bb2ad311fa67036105f680b6a1 Mon Sep 17 00:00:00 2001 From: Matthew Roy Date: Mon, 22 Apr 2019 15:31:48 -0700 Subject: [PATCH 01/20] Initial power harvesting commit --- src/Overmind_ts.ts | 590 +++++++++++++++++++++++ src/creepSetups/setups.ts | 17 + src/directives/initializer.ts | 3 + src/directives/resource/haul.ts | 9 +- src/directives/resource/powerMine.ts | 107 ++++ src/overlords/powerMining/PowerDrill.ts | 130 +++++ src/overlords/powerMining/PowerHauler.ts | 151 ++++++ src/overlords/situational/hauler.ts | 4 +- src/priorities/priorities_overlords.ts | 7 + src/utilities/creepUtils.ts | 45 ++ 10 files changed, 1059 insertions(+), 4 deletions(-) create mode 100644 src/Overmind_ts.ts create mode 100644 src/directives/resource/powerMine.ts create mode 100644 src/overlords/powerMining/PowerDrill.ts create mode 100644 src/overlords/powerMining/PowerHauler.ts create mode 100644 src/utilities/creepUtils.ts diff --git a/src/Overmind_ts.ts b/src/Overmind_ts.ts new file mode 100644 index 000000000..ad3762532 --- /dev/null +++ b/src/Overmind_ts.ts @@ -0,0 +1,590 @@ +// javascript-obfuscator:disable +// +// Overmind_obfuscated.js: this file is intentially obfuscated to prevent tampering. +// +// Q: Why is this file obfuscated? +// +// A: Using Overmind as your AI puts you at a huge advantage if you are a new player in a novice zone. Screeps has +// always had problems with people downloading bots from the internet and stomping new players. I have kept Overmind +// open-sourced because I think it can be a good resource for learning how to play Screeps, but I don't want it to +// be abused as a noob-crushing machine. In the future, I will be implementing behavioral locks in this file which +// limit unreasonable aggression toward peaceful new players. +// +// Q: What kind of behavioral locks? +// +// A: Players will be able to opt out of aggression by setting a property in their public memory. Overmind bots will not +// attack the player unless they attack you, claim a room in Overmind's territory, or occupy a room which is +// critically important (for example, very desirable mineral deposits that aren't available elsewhere). Overmind +// will attempt to expand around players which pose no threat to it rather than eliminating them. +// +// Q: What does this file do? +// +// A: The Overmind object is the top-level initializer of the AI and instantiates all colonies and directives. It is +// also responsible for some high-level decision making. You can see the enumerated properties of the Overmind class +// in IOvermind in declarations/index.d.ts. Since this file is sufficiently complex and is critical for the AI to be +// able to run, it was a natural choice of location to put code which should be tamper-resistant. +// +// Q: What happens if I modify this code? +// +// A: This code is self-defending, so any modification to it will likely break the script. +// +// Q: I would like to view the original source code for this file. +// +// A: If you have a compelling reason that you'd like to see the non-obfuscated source for this file, message me in +// game, on slack, or send me an email at benbartlett@stanford.edu. +// + +import {Colony, ColonyMemory, getAllColonies} from './Colony'; +import {DirectiveWrapper} from './directives/initializer'; +import {profile} from './profiler/decorator'; +import {GameCache} from './caching/GameCache'; +import {Overlord} from './overlords/Overlord'; +import {Visualizer} from './visuals/Visualizer'; +import {Stats} from './stats/stats'; +import {TerminalNetwork} from './logistics/TerminalNetwork'; +import {AllContracts} from './contracts/contractsList'; +import {Autonomy, getAutonomyLevel, Mem} from './memory/Memory'; +import {asciiLogoSmall} from './visuals/logos'; +import {log} from './console/log'; +import {TraderJoe} from './logistics/TradeNetwork'; +import {RoomIntel} from './intel/RoomIntel'; +import { + DEFAULT_OVERMIND_SIGNATURE, + MUON, + MY_USERNAME, + NEW_OVERMIND_INTERVAL, + PROFILER_COLONY_LIMIT, + PROFILER_INCLUDE_COLONIES, + USE_PROFILER, + USE_TRY_CATCH +} from './~settings'; +import {Strategist} from './strategy/Strategist'; +import {assimilationLocked} from './assimilation/decorator'; +import {SpawnGroup} from './logistics/SpawnGroup'; +import {alignedNewline} from './utilities/stringConstants'; +import {bulleted} from './utilities/utils'; +import {Directive} from './directives/Directive'; +import {Zerg} from './zerg/Zerg'; +import {Segmenter} from './memory/Segmenter'; +import {Overseer} from './Overseer'; +import {NotifierPriority} from './directives/Notifier'; + +// javascript-obfuscator:enable + +let profilerRooms: { [roomName: string]: boolean } = {}; +if (USE_PROFILER) { + for (let name of PROFILER_INCLUDE_COLONIES) { + profilerRooms[name] = true; + } + let myRoomNames = _.filter(_.keys(Game.rooms), name => Game.rooms[name] && Game.rooms[name].my); + for (let name of _.sample(myRoomNames, PROFILER_COLONY_LIMIT - PROFILER_INCLUDE_COLONIES.length)) { + profilerRooms[name] = true; + } +} + +@profile +@assimilationLocked +export default class _Overmind implements IOvermind { + + memory: IOvermindMemory; + overseer: Overseer; + shouldBuild: boolean; + expiration: number; + cache: GameCache; + colonies: { [roomName: string]: Colony }; // Global hash of all colony objects + directives: { [flagName: string]: Directive }; + zerg: { [creepName: string]: Zerg }; + overlords: { [ref: string]: Overlord }; + spawnGroups: { [ref: string]: SpawnGroup }; + colonyMap: { [roomName: string]: string }; // Global map of colony associations for possibly-null rooms + terminalNetwork: TerminalNetwork; + tradeNetwork: TraderJoe; + strategist: Strategist | undefined; + exceptions: Error[]; + + constructor() { + this.memory = Memory.Overmind as IOvermindMemory; + this.overseer = new Overseer(); + this.shouldBuild = true; + this.expiration = Game.time + NEW_OVERMIND_INTERVAL; + this.cache = new GameCache(); + this.colonies = {}; + this.directives = {}; + this.zerg = {}; + this.overlords = {}; + this.spawnGroups = {}; + this.colonyMap = {}; + this.terminalNetwork = this.makeTerminalNetwork(); + this.tradeNetwork = new TraderJoe(); + this.strategist = getAutonomyLevel() > Autonomy.Manual ? new Strategist() : undefined; + this.exceptions = []; + } + + /* Global instantiation of Overmind object; run once every global refresh */ + build(): void { + log.debug(`Rebuilding Overmind object!`); + this.cache.build(); + // Register all colonies and instantiate their overlords + this.registerColonies(); + _.forEach(this.colonies, colony => colony.spawnMoarOverlords()); + // Register directives and instantiate their overlords; must be done AFTER colonies + this.registerDirectives(); + _.forEach(this.directives, directive => directive.spawnMoarOverlords()); + this.shouldBuild = false; + } + + /* Refresh the state of the Overmind; run at the beginning of every tick */ + refresh(): void { + this.shouldBuild = true; // assume refresh will be unsuccessful + // Refresh constructor-phase objects + this.memory = Memory.Overmind as IOvermindMemory; + this.exceptions = []; + this.cache.refresh(); + this.overseer.refresh(); + this.terminalNetwork.refresh(); + this.tradeNetwork.refresh(); + if (this.strategist) this.strategist.refresh(); + // Refresh build-phase objects + this.refreshColonies(); + this.refreshDirectives(); + for (let ref in this.overlords) { + this.overlords[ref].refresh(); + } + for (let ref in this.spawnGroups) { + this.spawnGroups[ref].refresh(); + } + this.shouldBuild = false; // refresh successful + } + + private try(callback: () => any, identifier?: string): void { + if (USE_TRY_CATCH) { + try { + callback(); + } catch (e) { + if (identifier) { + e.name = `Caught unhandled exception at ${'' + callback} (identifier: ${identifier}): \n` + + e.name + '\n' + e.stack; + } else { + e.name = `Caught unhandled exception at ${'' + callback}: \n` + e.name + '\n' + e.stack; + } + this.exceptions.push(e); + } + } else { + callback(); + } + } + + private handleExceptions(): void { + if (this.exceptions.length == 0) { + return; + } else { + log.warning(`Exceptions present this tick! Rebuilding Overmind object in next tick.`); + Memory.stats.persistent.lastErrorTick = Game.time; + this.shouldBuild = true; + this.expiration = Game.time; + if (this.exceptions.length == 1) { + throw _.first(this.exceptions); + } else { + for (let e of this.exceptions) { + log.throw(e); + } + let error = new Error('Multiple exceptions caught this tick!'); + error.stack = _.map(this.exceptions, e => e.name).join('\n'); + throw error; + } + } + } + + private makeTerminalNetwork(): TerminalNetwork { + // Initialize the terminal netowrk + let terminals: StructureTerminal[] = []; + for (let name in Game.rooms) { + if (USE_PROFILER && !profilerRooms[name]) continue; + let room = Game.rooms[name]; + if (room.my && room.controller!.level >= 6 && room.terminal && room.terminal.my) { + terminals.push(room.terminal); + } + } + return new TerminalNetwork(terminals); + } + + + /* Instantiate a new colony for each owned rom */ + private registerColonies(): void { + + let colonyOutposts: { [roomName: string]: string[] } = {}; // key: lead room, values: outposts[] + this.colonyMap = {}; + // Register colonies to their outposts + let flagsByRoom = _.groupBy(this.cache.outpostFlags, flag => flag.memory[_MEM.COLONY]); + for (let name in Game.rooms) { + if (Game.rooms[name].my) { + let colonyMemory = Memory.colonies[name] as ColonyMemory | undefined; + if (colonyMemory && colonyMemory.suspend) { + this.overseer.notifier.alert("Colony suspended", name, NotifierPriority.High); + continue; + } + if (Game.rooms[name].flags) + colonyOutposts[name] = _.map(flagsByRoom[name], + flag => flag.memory.setPosition + ? derefRoomPosition(flag.memory.setPosition).roomName + : flag.pos.roomName); + this.colonyMap[name] = name; + } + } + // Register outposts to their colonies + for (let colonyName in colonyOutposts) { + for (let outpostName of colonyOutposts[colonyName]) { + this.colonyMap[outpostName] = colonyName; + } + } + + // Initialize the Colonies and pass each one its assigned creeps + let id = 0; + for (let name in colonyOutposts) { + if (USE_PROFILER && !profilerRooms[name]) { + if (Game.time % 20 == 0) { + log.alert(`Suppressing instantiation of colony ${name}.`); + } + continue; // don't make colony for this room + } + try { + this.colonies[name] = new Colony(id, name, colonyOutposts[name]); + } catch (e) { + e.name = `Caught unhandled exception instantiating colony ${name}: \n` + e.name; + this.exceptions.push(e); + } + id++; + } + } + + private refreshColonies(): void { + for (let name in this.colonies) { + try { + this.colonies[name].refresh(); + } catch (e) { + e.name = `Caught unhandled exception refreshing colony ${name}: \n` + e.name; + this.exceptions.push(e); + } + } + } + + /* Wrap each flag in a color coded wrapper */ + private registerDirectives(spawnOverlords = false): void { + // Create a directive for each flag (registration takes place on construction) + for (let name in Game.flags) { + if (this.directives[name]) { // skip existing directives + continue; + } + let colonyName = Game.flags[name].memory[_MEM.COLONY]; + if (colonyName) { + if (USE_PROFILER && !profilerRooms[colonyName]) { + continue; // don't make directive if colony is ignored for profiling + } + let colonyMemory = Memory.colonies[colonyName] as ColonyMemory | undefined; + if (colonyMemory && colonyMemory.suspend) { + continue; // don't make directive if colony is suspended + } + } + const directive = DirectiveWrapper(Game.flags[name]); + if (directive && spawnOverlords) { + directive.spawnMoarOverlords(); + } + if (!directive && Game.time % 10 == 0) { + // log.alert(`Flag [${name} @ ${Game.flags[name].pos.print}] does not match ` + + // `a valid directive color code! (Refer to /src/directives/initializer.ts)` + alignedNewline + + // `Use removeErrantFlags() to remove flags which do not match a directive.`); + } + } + } + + /* Refresh all directives, adding new ones for new flags */ + private refreshDirectives(): void { + for (let name in this.directives) { // this should be called first + this.directives[name].refresh(); + } + this.registerDirectives(true); // register any new directives that were placed since last rebuild + } + + /* Intialize everything in pre-init phase of main loop. Does not call colony.init(). */ + init(): void { + // Initialize the overseer + this.overseer.init(); + // Initialize each colony + for (let colonyName in this.colonies) { + let start = Game.cpu.getUsed(); + this.try(() => this.colonies[colonyName].init(), colonyName); + Stats.log(`cpu.usage.${colonyName}.init`, Game.cpu.getUsed() - start); + } + // Initialize spawn groups + for (let ref in this.spawnGroups) { + this.try(() => this.spawnGroups[ref].init(), ref); + } + // Initialize terminalNetwork and tradeNetwork + this.try(() => this.terminalNetwork.init()); + this.try(() => this.tradeNetwork.init()); + // Initialize strategist + if (this.strategist) { + this.try(() => this.strategist!.init()); + } + } + + run(): void { + // Enforce behavioral locks every 3 ticks + if (Game.time % 3 == 0) { + IntelManagement.run(); + } + // Run spawn groups + for (let ref in this.spawnGroups) { + this.try(() => this.spawnGroups[ref].run(), ref); + } + // Run the overseer + this.overseer.run(); + // Run all colonies + for (let colonyName in this.colonies) { + this.try(() => this.colonies[colonyName].run(), colonyName); + } + // Run all contracts + if (MY_USERNAME == MUON) { // This ensures that my contracts don't run by default on other people's accounts + for (let contract of AllContracts) { + this.try(() => contract.run()); + } + } + // Run terminal network + this.try(() => this.terminalNetwork.run()); + // Run trade network + this.try(() => this.tradeNetwork.run()); + // Run strategist + if (this.strategist) { + this.try(() => this.strategist!.run()); + } + // Run room intel + this.try(() => RoomIntel.run()); // this should be at end, right before assimilator + // Run assimilator + this.try(() => Assimilator.run()); // this should always be last + } + + postRun(): void { + // Run version updater + this.try(() => VersionUpdater.run()); + // Run segmenter + this.try(() => Segmenter.run()); + // Handle exceptions + this.handleExceptions(); + } + + visuals(): void { + if (Game.cpu.bucket > 9000) { + // Draw global visuals + Visualizer.visuals(); + // Add notifications for outdated version + if (VersionUpdater.memory.newestVersion) { + const newestVersion = VersionUpdater.memory.newestVersion; + if (VersionUpdater.isVersionOutdated(newestVersion)) { + this.overseer.notifier.alert(`[!] Update available: ${__VERSION__} → ${newestVersion}`, + undefined, -1); + } + } + // Draw overseer visuals + this.overseer.visuals(); + // Draw colony visuals + for (let colonyName in this.colonies) { + this.colonies[colonyName].visuals(); + } + } else { + if (Game.time % 10 == 0) { + log.info(`CPU bucket is too low (${Game.cpu.bucket}) - skip rendering visuals.`); + } + } + } +} + + +// Behavioral locks ==================================================================================================== + +/* class BehavioralLocks */ +class IntelManagement { // Renamed to IntelManagement to avoid identification + + /* static enforceSignature(): void */ + static runRoomIntel_1(): void { + let badSignatures: string[] = []; + let colonies = getAllColonies(); + if (colonies.length == 0) return; + for (let colony of colonies) { + if (colony.defcon > 0 || colony.creeps.length == 0) { + continue; + } + let controller = colony.controller; + if (controller.signedByScreeps || controller.level < 4) { + continue; + } + let validSignature = false; + if (controller.sign) { + let sig = controller.sign.text; + if (sig.toLowerCase().includes('overmind') + || sig.includes('\u1D0F\u1D20\u1D07\u0280\u1D0D\u026A\u0274\u1D05')) { + validSignature = true; + } + } + if (!validSignature) { + badSignatures.push(controller.sign ? controller.sign.text : 'undefined'); + } + } + // Throw an error if more than half of rooms have a bad signature + if (badSignatures.length >= 0.5 * _.keys(Overmind.colonies).length) { + Memory.settings.signature = DEFAULT_OVERMIND_SIGNATURE; + log.warning(`Invalid controller signatures detected:` + + bulleted(badSignatures) + alignedNewline + + `Signatures must contain the string "Overmind" or ` + + `${'\u1D0F\u1D20\u1D07\u0280\u1D0D\u026A\u0274\u1D05'}.`); + throw new Error(`Invalid controller signatures detected; won't run this tick!`); + } + } + + /* static enforceIllegalDirectives */ + static runRoomIntel_2(): void { + if (!Assimilator.isAssimilated(MY_USERNAME)) { // if you are not assimilated + const illegalColorCombinations: ColorConstant[][] = [[COLOR_RED, COLOR_RED]]; + for (let name in Game.flags) { + const flag = Game.flags[name]; + const colors = [flag.color, flag.secondaryColor]; + if (illegalColorCombinations.includes(colors)) { + flag.remove(); + log.warning(`Illegal directives detected and removed: ${flag.name}. ` + + `Assimilation required to access these features.`); + } + } + } + } + + static run(): void { + this.runRoomIntel_1(); + if (Game.time % (3 * 31) == 0) { // must be multiple of 3 + this.runRoomIntel_2(); + } + } + +} + + +// Version updater ===================================================================================================== + +interface VersionSegmentData { + version: string; +} + +interface VersionUpdaterMemory { + versions: { [version: string]: any }; + newestVersion: string | undefined; +} + +class VersionUpdater { + + static CheckFrequency = 100; + static CheckOnTick = 91; + static VersionSegment = 99; + + static get memory(): VersionUpdaterMemory { + return Mem.wrap(Memory.Overmind, 'versionUpdater', { + versions : {}, + newestVersion: undefined, + }); + } + + private static slave_fetchVersion(): string | undefined { + if (Game.time % this.CheckFrequency == this.CheckOnTick - 1) { + Segmenter.requestForeignSegment(MUON, this.VersionSegment); + } else if (Game.time % this.CheckFrequency == this.CheckOnTick) { + let data = Segmenter.getForeignSegment() as VersionSegmentData | undefined; + if (data) { + return data.version; + } + } + } + + static isVersionOutdated(newVersion: string): boolean { + let [major, minor, patch] = _.map(__VERSION__.split('.'), + str => parseInt(str, 10)) as number[]; + let [newMajor, newMinor, newPatch] = _.map(newVersion.split('.'), + str => parseInt(str, 10)) as number[]; + return (newMajor > major || newMinor > minor || newPatch > patch); + } + + private static master_pushVersion(): void { + if (Game.time % this.CheckFrequency == this.CheckOnTick - 2) { + Segmenter.requestSegments(this.VersionSegment); + } else if (Game.time % this.CheckFrequency == this.CheckOnTick - 1) { + Segmenter.markSegmentAsPublic(this.VersionSegment); + Segmenter.setSegmentProperty(this.VersionSegment, 'version', __VERSION__); + } + } + + static generateUpdateMessage(v1: string, v2: string): string { + // Variable names are short to match length in text when ${} is added + let msg = '\n'; + for (let line of asciiLogoSmall) { + msg += line + '\n'; + } + let downL = 'Download'; + let patchNts = 'Patch notes'; + let updateMsg = '╔═════════════════════════════════════════════════════════╗\n' + + `║ Update available: ${v1} → ${v2} ║\n` + + `║ > ${downL} < > ${patchNts} < ║\n` + + '╚═════════════════════════════════════════════════════════╝'; + return msg + updateMsg; + } + + static generateUpdateMessageSmall(v1: string, v2: string): string { + // Variable names are short to match length in text when ${} is added + let downL = 'Download'; + let patchNts = 'Patch notes'; + let updateMsg = `╔═════════════════════════════════╗\n` + + `║ OVERMIND SCREEPS AI ║\n` + + `╠═════════════════════════════════╣\n` + + `║ Update available: ${v1} → ${v2} ║\n` + + `║ > ${downL} < > ${patchNts} < ║\n` + + `╚═════════════════════════════════╝`; + return '\n' + updateMsg; + } + + static displayUpdateMessage(newVersion: string): void { + const updateMessage = this.generateUpdateMessage(__VERSION__, newVersion); + console.log(`${updateMessage}`); + } + + static sayUpdateMessage(newVersion: string): void { + for (let name in Game.creeps) { + let creep = Game.creeps[name]; + creep.say('Update me!', true); + } + } + + static notifyNewVersion(newVersion: string): void { + const updateMessage = this.generateUpdateMessageSmall(__VERSION__, newVersion); + Game.notify(`${updateMessage}`); + } + + static run(): void { + if (MY_USERNAME == MUON) { + this.master_pushVersion(); + } + // Update version + let fetchedVersion = this.slave_fetchVersion(); + if (fetchedVersion) { + this.memory.newestVersion = fetchedVersion; + } + // Check for new version + const newestVersion = this.memory.newestVersion; + if (newestVersion && this.isVersionOutdated(newestVersion)) { + if (Game.time % 10 == 0) { + this.displayUpdateMessage(newestVersion); + this.sayUpdateMessage(newestVersion); + } + if (Game.time % 10000 == 0 /*!this.memory.versions[newVersion]*/) { + this.notifyNewVersion(newestVersion); + } + } + } + +} + + diff --git a/src/creepSetups/setups.ts b/src/creepSetups/setups.ts index 99337f91d..f49de8b52 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', }; /** @@ -307,4 +309,19 @@ 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, + }), + } + + }; diff --git a/src/directives/initializer.ts b/src/directives/initializer.ts index 7b7637ce1..0a0a1e052 100644 --- a/src/directives/initializer.ts +++ b/src/directives/initializer.ts @@ -25,6 +25,7 @@ import {DirectiveExtract} from './resource/extract'; import {DirectiveSwarmDestroy} from './offense/swarmDestroy'; import {DirectiveOutpostDefense} from './defense/outpostDefense'; import {DirectiveClearRoom} from './colony/clearRoom'; +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 8e1a27636..6dff8f288 100644 --- a/src/directives/resource/haul.ts +++ b/src/directives/resource/haul.ts @@ -2,6 +2,7 @@ import {Directive} from '../Directive'; import {profile} from '../../profiler/decorator'; import {isStoreStructure} from '../../declarations/typeGuards'; import {HaulingOverlord} from '../../overlords/situational/hauler'; +import {PowerHaulingOverlord} from "../../overlords/powerMining/PowerHauler"; interface DirectiveHaulMemory extends FlagMemory { @@ -10,7 +11,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 { @@ -29,7 +30,11 @@ export class DirectiveHaul extends Directive { } spawnMoarOverlords() { - this.overlords.haul = new HaulingOverlord(this); + if (this.room && this.pos.lookForStructure(STRUCTURE_POWER_BANK)) { + this.overlords.haul = new PowerHaulingOverlord(this); + } else { + this.overlords.haul = new HaulingOverlord(this); + } } get targetedBy(): string[] { diff --git a/src/directives/resource/powerMine.ts b/src/directives/resource/powerMine.ts new file mode 100644 index 000000000..e6d3f9672 --- /dev/null +++ b/src/directives/resource/powerMine.ts @@ -0,0 +1,107 @@ +import {Directive} from '../Directive'; +import {profile} from '../../profiler/decorator'; +import {isStoreStructure} from '../../declarations/typeGuards'; +import {PowerDrillOverlord} from '../../overlords/powerMining/PowerDrill'; + + +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; + + private _powerBank: StructurePowerBank | undefined; + + private _store: StoreDefinition; + private _drops: { [resourceType: string]: Resource[] }; + + memory: DirectivePowerMineMemory; + + constructor(flag: Flag) { + super(flag); + } + + spawnMoarOverlords() { + this.overlords.powerMine = new PowerDrillOverlord(this); + } + + get targetedBy(): string[] { + return Overmind.cache.targets[this.ref]; + } + + 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 store(): StoreDefinition { + // if (!this._store) { + // // Merge the "storage" of drops with the store of structure + // let store: { [resourceType: string]: number } = {}; + // if (this.storeStructure) { + // if (isStoreStructure(this.storeStructure)) { + // store = this.storeStructure.store; + // } else { + // store = {'energy': this.storeStructure.energy}; + // } + // } else { + // store = {'energy': 0}; + // } + // // Merge with drops + // for (let resourceType of _.keys(this.drops)) { + // let totalResourceAmount = _.sum(this.drops[resourceType], drop => drop.amount); + // if (store[resourceType]) { + // store[resourceType] += totalResourceAmount; + // } else { + // store[resourceType] = totalResourceAmount; + // } + // } + // this._store = store as StoreDefinition; + // } + // return this._store; + // } + + /** + * 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; + } + + init(): void { + this.alert(`PowerMine directive active`); + } + + run(): void { + // if (_.sum(this.store) == 0 && this.pos.isVisible) { + // //this.remove(); + // } + } + +} + diff --git a/src/overlords/powerMining/PowerDrill.ts b/src/overlords/powerMining/PowerDrill.ts new file mode 100644 index 000000000..b82f7f74b --- /dev/null +++ b/src/overlords/powerMining/PowerDrill.ts @@ -0,0 +1,130 @@ +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"; + +interface PowerDrillOverlordMemory extends OverlordMemory { + targetPBID?: string; +} + +/** + * PowerDrillOverlord -- spawns drills and coolant to mine power banks + */ +@profile +export class PowerDrillOverlord extends CombatOverlord { + + static requiredRCL = 7; + static haulerPrespawn = 600; + + directive: DirectiveSKOutpost; + memory: PowerDrillOverlordMemory; + targetPowerBank: StructurePowerBank | undefined; + + 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'); + } + + refresh() { + super.refresh(); + this.memory = Mem.wrap(this.directive.memory, 'powerDrill'); + + } + + init() { + this.wishlist(3, CombatSetups.drill.default); + this.wishlist(4, CombatSetups.coolant.default); + } + + private handleDrill(drill: CombatZerg) { + if (!this.targetPowerBank) { + if (!this.room) { + // We are not there yet + } else { + var bank = this.pos.lookForStructure(STRUCTURE_POWER_BANK) as StructurePowerBank; + this.targetPowerBank = bank; + // If power bank is dead + if (bank == undefined) { + Game.notify("Power bank in " + this.room + " is dead."); + this.directive.remove(); + Game.notify("FINISHED POWER MINING IN " + this.room + " DELETING CREEP at time: " + Game.time.toString()); + drill.suicide(); + return; + } + } + } + + // Go to keeper room + if (!this.room || drill.room != this.room || drill.pos.isEdge || !this.targetPowerBank) { + // log.debugCreep(drill, `Going to room!`); + Game.notify("Drill is moving to power site in " + this.room + "."); + drill.goTo(this.pos); + return; + } else if (this.targetPowerBank.hits < 800000) { + Game.notify("Power bank in " + this.room + " is almost dead"); + DirectiveHaul.create(this.pos); + } + // Spawn a hauler for this location + // Should make a 49 carry 1 move creep to hold some, and a bunch of creeps to pick up ground first then container creep + + // Handle killing bank + if (drill.hits > 100) { + drill.goTo(this.targetPowerBank); + drill.attack(this.targetPowerBank); + } + } + + private handleCoolant(coolant: CombatZerg) { + // Go to powerbank room + if (!this.room || coolant.room != this.room || coolant.pos.isEdge) { + // log.debugCreep(reaper, `Going to room!`); + coolant.healSelfIfPossible(); + coolant.goTo(this.pos); + return; + } else if (!this.targetPowerBank) { + // If power bank is dead + Game.notify("Power bank in " + this.room + " is dead."); + coolant.suicide(); + return; + } else if (this.targetPowerBank.hits < 50000) { + Game.notify("Power bank in " + this.room + " is almost dead"); + Game.notify("Power bank in " + this.room + ", beginning haul operation."); + //DirectiveHaul.create(this.pos); + } + if (coolant.pos.getRangeTo(_.first(this.drills)) > 1) { + coolant.goTo(_.first(this.drills)); + } + + coolant.autoHeal(false); + } + + run() { + this.autoRun(this.drills, drill => this.handleDrill(drill)); + this.autoRun(this.coolant, coolant => this.handleCoolant(coolant)); + } + + visuals() { + if (this.room && this.targetPowerBank) { + Visualizer.marker(this.targetPowerBank.pos); + } + } + +} diff --git a/src/overlords/powerMining/PowerHauler.ts b/src/overlords/powerMining/PowerHauler.ts new file mode 100644 index 000000000..632ce0654 --- /dev/null +++ b/src/overlords/powerMining/PowerHauler.ts @@ -0,0 +1,151 @@ +import {Overlord} from '../Overlord'; +import {OverlordPriority} from '../../priorities/priorities_overlords'; +import {Zerg} from '../../zerg/Zerg'; +import {DirectiveHaul} from '../../directives/resource/haul'; +import {Tasks} from '../../tasks/Tasks'; +import {isStoreStructure} from '../../declarations/typeGuards'; +import {log} from '../../console/log'; +import {Pathing} from '../../movement/Pathing'; +import {Energetics} from '../../logistics/Energetics'; +import {profile} from '../../profiler/decorator'; +import {Roles, Setups} from '../../creepSetups/setups'; +import {HaulingOverlord} from "../situational/hauler"; +import {calculateFormationStrength} from "../../utilities/creepUtils"; + +/** + * Spawns special-purpose haulers for transporting resources to/from a specified target + */ +@profile +export class PowerHaulingOverlord extends HaulingOverlord { + + haulers: Zerg[]; + directive: DirectiveHaul; + powerBank: StructurePowerBank | undefined; + tickToSpawnOn: number; + numHaulers: number; + + requiredRCL = 6; + // Allow time for body to spawn + prespawnAmount = 200; + + constructor(directive: DirectiveHaul, priority = OverlordPriority.collectionUrgent.haul) { + super(directive, priority); // Removed 'haul' string + this.directive = directive; + this.haulers = this.zerg(Roles.transport); + } + + init() { + if (!this.colony.storage || _.sum(this.colony.storage.store) > Energetics.settings.storage.total.cap) { + return; + } + // Spawn a number of haulers sufficient to move all resources within a lifetime, up to a max + let MAX_HAULERS = 5; + // Calculate total needed amount of hauling power as (resource amount * trip distance) + let tripDistance = 2 * Pathing.distance((this.colony.storage || this.colony).pos, this.directive.pos); + let haulingPowerNeeded = Math.min(this.directive.totalResources, + this.colony.storage.storeCapacity + - _.sum(this.colony.storage.store)) * tripDistance; + // Calculate amount of hauling each hauler provides in a lifetime + let haulerCarryParts = Setups.transporters.early.getBodyPotential(CARRY, this.colony); + let haulingPowerPerLifetime = CREEP_LIFE_TIME * haulerCarryParts * CARRY_CAPACITY; + // Calculate number of haulers + this.numHaulers = Math.min(Math.ceil(haulingPowerNeeded / haulingPowerPerLifetime), MAX_HAULERS); + // Request the haulers + this.tickToSpawnOn = Game.time + (this.calculateRemainingLifespan() || 0) - this.prespawnAmount; + } + + + calculateRemainingLifespan() { + if (!this.room) { + return undefined; + } else if (this.powerBank == undefined) { + // 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; + } + } + + protected handleHauler(hauler: Zerg) { + if (_.sum(hauler.carry) == 0) { + // Travel to directive and collect resources + 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; + } + } + // Withdraw from store structure + if (this.directive.storeStructure) { + let store: { [resourceType: string]: number } = {}; + if (isStoreStructure(this.directive.storeStructure)) { + store = this.directive.storeStructure.store; + } else { + store = {'energy': this.directive.storeStructure.energy}; + } + for (let resourceType in store) { + if (store[resourceType] > 0) { + hauler.task = Tasks.withdraw(this.directive.storeStructure, resourceType); + return; + } + } + } + // Shouldn't reach here + log.warning(`${hauler.name} in ${hauler.room.print}: nothing to collect!`); + } else { + // hauler.task = Tasks.goTo(this.directive); + hauler.goTo(this.directive); + } + } else { + // Travel to colony room and deposit resources + if (hauler.inSameRoomAs(this.colony)) { + // Put energy in storage and minerals in terminal if there is one + 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.haulers.length == 0) { + this.wishlist(this.numHaulers, Setups.transporters.early); + } + for (let hauler of this.haulers) { + if (hauler.isIdle) { + this.handleHauler(hauler); + } + hauler.run(); + } + } +} \ No newline at end of file diff --git a/src/overlords/situational/hauler.ts b/src/overlords/situational/hauler.ts index 86bfd43a3..ffd527cca 100644 --- a/src/overlords/situational/hauler.ts +++ b/src/overlords/situational/hauler.ts @@ -19,7 +19,7 @@ export class HaulingOverlord extends Overlord { haulers: Zerg[]; directive: DirectiveHaul; - requiredRCL: 4; + requiredRCL: number = 4; constructor(directive: DirectiveHaul, priority = directive.hasDrops ? OverlordPriority.collectionUrgent.haul : OverlordPriority.collection.haul) { @@ -48,7 +48,7 @@ export class HaulingOverlord extends Overlord { this.wishlist(numHaulers, Setups.transporters.early); } - private handleHauler(hauler: Zerg) { + protected handleHauler(hauler: Zerg) { if (_.sum(hauler.carry) == 0) { // Travel to directive and collect resources if (hauler.inSameRoomAs(this.directive)) { diff --git a/src/priorities/priorities_overlords.ts b/src/priorities/priorities_overlords.ts index 356b7e676..71d38c399 100644 --- a/src/priorities/priorities_overlords.ts +++ b/src/priorities/priorities_overlords.ts @@ -72,6 +72,13 @@ export var 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; +} From 58390550d75fc3949813a53a4470f7f70d7c9cd7 Mon Sep 17 00:00:00 2001 From: Matthew Roy Date: Mon, 22 Apr 2019 16:29:05 -0700 Subject: [PATCH 02/20] Fixing healing (somewhat) --- src/overlords/powerMining/PowerDrill.ts | 33 ++++++++++++++++++++++--- src/zerg/CombatZerg.ts | 2 +- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/overlords/powerMining/PowerDrill.ts b/src/overlords/powerMining/PowerDrill.ts index b82f7f74b..54f8224bc 100644 --- a/src/overlords/powerMining/PowerDrill.ts +++ b/src/overlords/powerMining/PowerDrill.ts @@ -54,6 +54,15 @@ export class PowerDrillOverlord extends CombatOverlord { this.wishlist(4, CombatSetups.coolant.default); } + 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 (!this.targetPowerBank) { if (!this.room) { @@ -86,9 +95,10 @@ export class PowerDrillOverlord extends CombatOverlord { // Should make a 49 carry 1 move creep to hold some, and a bunch of creeps to pick up ground first then container creep // Handle killing bank - if (drill.hits > 100) { - drill.goTo(this.targetPowerBank); + if (drill.pos.isNearTo(this.targetPowerBank)) { drill.attack(this.targetPowerBank); + } else { + drill.goTo(this.targetPowerBank); } } @@ -109,13 +119,28 @@ export class PowerDrillOverlord extends CombatOverlord { Game.notify("Power bank in " + this.room + ", beginning haul operation."); //DirectiveHaul.create(this.pos); } - if (coolant.pos.getRangeTo(_.first(this.drills)) > 1) { - coolant.goTo(_.first(this.drills)); + + if (coolant.memory.partner) { + let drill = Game.creeps[coolant.memory.partner]; + if (!drill) { + coolant.memory.partner = undefined; + } else if (!coolant.pos.isNearTo(drill)) { + coolant.goTo(drill); + } else { + coolant.heal(drill); + } + } + if (coolant.pos.getRangeTo(this.targetPowerBank) > 2) { + coolant.goTo(this.targetPowerBank); } coolant.autoHeal(false); } + private findDrillToPartner(coolant: CombatZerg) { + + } + run() { this.autoRun(this.drills, drill => this.handleDrill(drill)); this.autoRun(this.coolant, coolant => this.handleCoolant(coolant)); diff --git a/src/zerg/CombatZerg.ts b/src/zerg/CombatZerg.ts index 5b8da2123..dc10d2a48 100644 --- a/src/zerg/CombatZerg.ts +++ b/src/zerg/CombatZerg.ts @@ -179,7 +179,7 @@ export class CombatZerg extends Zerg { */ autoHeal(allowRangedHeal = true, friendlies = this.room.creeps) { let target = CombatTargeting.findBestHealingTargetInRange(this, allowRangedHeal ? 3 : 1, friendlies); - this.debug(`Heal taget: ${target}`); + this.debug(`Heal target: ${target}`); if (target) { if (this.pos.getRangeTo(target) <= 1) { return this.heal(target); From 2bb13111e155fd77726681a74adfa6b5e1a680d6 Mon Sep 17 00:00:00 2001 From: Matthew Roy Date: Tue, 23 Apr 2019 15:51:18 -0700 Subject: [PATCH 03/20] Really clunky patch to make power harvest part of core directive --- src/directives/resource/haul.ts | 13 ++++- src/directives/resource/powerMine.ts | 64 ++++++++++++------------ src/overlords/Overlord.ts | 8 +++ src/overlords/powerMining/PowerDrill.ts | 14 +++--- src/overlords/powerMining/PowerHauler.ts | 36 ++++++------- 5 files changed, 75 insertions(+), 60 deletions(-) diff --git a/src/directives/resource/haul.ts b/src/directives/resource/haul.ts index 6dff8f288..96161a301 100644 --- a/src/directives/resource/haul.ts +++ b/src/directives/resource/haul.ts @@ -22,6 +22,7 @@ export class DirectiveHaul extends Directive { private _store: StoreDefinition; private _drops: { [resourceType: string]: Resource[] }; + private finishAtTime: number; memory: DirectiveHaulMemory; @@ -56,11 +57,12 @@ export class DirectiveHaul extends Directive { return _.keys(this.drops).length > 0; } - get storeStructure(): StructureStorage | StructureTerminal | StructureNuker | undefined { + get storeStructure(): StructureStorage | StructureTerminal | StructureNuker | StructurePowerBank | undefined { if (this.pos.isVisible) { return this.pos.lookForStructure(STRUCTURE_STORAGE) || this.pos.lookForStructure(STRUCTURE_TERMINAL) || - this.pos.lookForStructure(STRUCTURE_NUKER); + this.pos.lookForStructure(STRUCTURE_NUKER) || + this.pos.lookForStructure(STRUCTURE_POWER_BANK); } return undefined; } @@ -72,6 +74,8 @@ export class DirectiveHaul extends Directive { if (this.storeStructure) { if (isStoreStructure(this.storeStructure)) { store = this.storeStructure.store; + } else if (this.storeStructure instanceof StructurePowerBank) { + store = {'power': this.storeStructure.power}; } else { store = {'energy': this.storeStructure.energy}; } @@ -112,6 +116,11 @@ export class DirectiveHaul extends Directive { run(): void { if (_.sum(this.store) == 0 && this.pos.isVisible) { + //this.remove(); + this.finishAtTime = Game.time + 1000; + this.overlords + } + if (Game.time >= this.finishAtTime) { this.remove(); } } diff --git a/src/directives/resource/powerMine.ts b/src/directives/resource/powerMine.ts index e6d3f9672..c290d9ff9 100644 --- a/src/directives/resource/powerMine.ts +++ b/src/directives/resource/powerMine.ts @@ -2,6 +2,9 @@ import {Directive} from '../Directive'; import {profile} from '../../profiler/decorator'; import {isStoreStructure} from '../../declarations/typeGuards'; import {PowerDrillOverlord} from '../../overlords/powerMining/PowerDrill'; +import {Pathing} from "../../movement/Pathing"; +import {calculateFormationStrength} from "../../utilities/creepUtils"; +import {PowerHaulingOverlord} from "../../overlords/powerMining/PowerHauler"; interface DirectivePowerMineMemory extends FlagMemory { @@ -19,9 +22,8 @@ export class DirectivePowerMine extends Directive { static color = COLOR_YELLOW; static secondaryColor = COLOR_RED; + expectedSpawnTime = 200; private _powerBank: StructurePowerBank | undefined; - - private _store: StoreDefinition; private _drops: { [resourceType: string]: Resource[] }; memory: DirectivePowerMineMemory; @@ -53,33 +55,6 @@ export class DirectivePowerMine extends Directive { return _.keys(this.drops).length > 0; } - // get store(): StoreDefinition { - // if (!this._store) { - // // Merge the "storage" of drops with the store of structure - // let store: { [resourceType: string]: number } = {}; - // if (this.storeStructure) { - // if (isStoreStructure(this.storeStructure)) { - // store = this.storeStructure.store; - // } else { - // store = {'energy': this.storeStructure.energy}; - // } - // } else { - // store = {'energy': 0}; - // } - // // Merge with drops - // for (let resourceType of _.keys(this.drops)) { - // let totalResourceAmount = _.sum(this.drops[resourceType], drop => drop.amount); - // if (store[resourceType]) { - // store[resourceType] += totalResourceAmount; - // } else { - // store[resourceType] = totalResourceAmount; - // } - // } - // this._store = store as StoreDefinition; - // } - // return this._store; - // } - /** * Total amount of resources remaining to be transported; cached into memory in case room loses visibility */ @@ -93,14 +68,39 @@ export class DirectivePowerMine extends Directive { return this.memory.totalResources; } + calculateRemainingLifespan() { + if (!this.room) { + return undefined; + } else if (this._powerBank == undefined) { + // 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() { + + if (this.room && (!this._powerBank || (this.calculateRemainingLifespan()! < Pathing.distance(this.colony.pos, this.flag.pos) + this.expectedSpawnTime))) { + this.overlords.powerHaul = new PowerHaulingOverlord(this); + } + } + + + init(): void { this.alert(`PowerMine directive active`); } + + run(): void { - // if (_.sum(this.store) == 0 && this.pos.isVisible) { - // //this.remove(); - // } + } } diff --git a/src/overlords/Overlord.ts b/src/overlords/Overlord.ts index a50b7998b..f5a455ef2 100644 --- a/src/overlords/Overlord.ts +++ b/src/overlords/Overlord.ts @@ -44,6 +44,13 @@ export interface OverlordMemory { suspendUntil?: number; } +enum overlordLifecyleState { + preparing, + active, + paused, + finishing +} + const OverlordMemoryDefaults: OverlordMemory = {}; /** @@ -69,6 +76,7 @@ export abstract class Overlord { private _combatZerg: { [roleName: string]: CombatZerg[] }; private boosts: { [roleName: string]: _ResourceConstantSansEnergy[] | undefined }; creepUsageReport: { [roleName: string]: [number, number] | undefined }; + lifeCycle: overlordLifecyleState; constructor(initializer: OverlordInitializer | Colony, name: string, priority: number) { this.initializer = initializer; diff --git a/src/overlords/powerMining/PowerDrill.ts b/src/overlords/powerMining/PowerDrill.ts index 54f8224bc..a56d92524 100644 --- a/src/overlords/powerMining/PowerDrill.ts +++ b/src/overlords/powerMining/PowerDrill.ts @@ -25,11 +25,11 @@ interface PowerDrillOverlordMemory extends OverlordMemory { export class PowerDrillOverlord extends CombatOverlord { static requiredRCL = 7; - static haulerPrespawn = 600; - directive: DirectiveSKOutpost; + directive: DirectivePowerMine; memory: PowerDrillOverlordMemory; targetPowerBank: StructurePowerBank | undefined; + haulDirectiveCreated: boolean; drills: CombatZerg[]; coolant: CombatZerg[]; @@ -41,6 +41,7 @@ export class PowerDrillOverlord extends CombatOverlord { this.drills = this.combatZerg(Roles.drill); this.coolant = this.combatZerg(Roles.coolant); this.memory = Mem.wrap(this.directive.memory, 'powerDrill'); + this.haulDirectiveCreated = false; } refresh() { @@ -87,9 +88,10 @@ export class PowerDrillOverlord extends CombatOverlord { Game.notify("Drill is moving to power site in " + this.room + "."); drill.goTo(this.pos); return; - } else if (this.targetPowerBank.hits < 800000) { + } else if (this.targetPowerBank.hits < 800000 && !this.haulDirectiveCreated) { Game.notify("Power bank in " + this.room + " is almost dead"); - DirectiveHaul.create(this.pos); + Game.notify("Power bank in " + this.room + ", beginning haul operation."); + //this.haulDirectiveCreated = typeof DirectiveHaul.create(this.pos) == "string"; } // Spawn a hauler for this location // Should make a 49 carry 1 move creep to hold some, and a bunch of creeps to pick up ground first then container creep @@ -114,10 +116,6 @@ export class PowerDrillOverlord extends CombatOverlord { Game.notify("Power bank in " + this.room + " is dead."); coolant.suicide(); return; - } else if (this.targetPowerBank.hits < 50000) { - Game.notify("Power bank in " + this.room + " is almost dead"); - Game.notify("Power bank in " + this.room + ", beginning haul operation."); - //DirectiveHaul.create(this.pos); } if (coolant.memory.partner) { diff --git a/src/overlords/powerMining/PowerHauler.ts b/src/overlords/powerMining/PowerHauler.ts index 632ce0654..d2dd6d246 100644 --- a/src/overlords/powerMining/PowerHauler.ts +++ b/src/overlords/powerMining/PowerHauler.ts @@ -11,15 +11,16 @@ import {profile} from '../../profiler/decorator'; import {Roles, Setups} from '../../creepSetups/setups'; import {HaulingOverlord} from "../situational/hauler"; 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 HaulingOverlord { +export class PowerHaulingOverlord extends Overlord { haulers: Zerg[]; - directive: DirectiveHaul; + directive: DirectivePowerMine; powerBank: StructurePowerBank | undefined; tickToSpawnOn: number; numHaulers: number; @@ -28,8 +29,8 @@ export class PowerHaulingOverlord extends HaulingOverlord { // Allow time for body to spawn prespawnAmount = 200; - constructor(directive: DirectiveHaul, priority = OverlordPriority.collectionUrgent.haul) { - super(directive, priority); // Removed 'haul' string + constructor(directive: DirectivePowerMine, priority = OverlordPriority.collectionUrgent.haul) { + super(directive, 'powerHaul', priority); this.directive = directive; this.haulers = this.zerg(Roles.transport); } @@ -85,20 +86,19 @@ export class PowerHaulingOverlord extends HaulingOverlord { } } // Withdraw from store structure - if (this.directive.storeStructure) { - let store: { [resourceType: string]: number } = {}; - if (isStoreStructure(this.directive.storeStructure)) { - store = this.directive.storeStructure.store; - } else { - store = {'energy': this.directive.storeStructure.energy}; - } - for (let resourceType in store) { - if (store[resourceType] > 0) { - hauler.task = Tasks.withdraw(this.directive.storeStructure, resourceType); - return; - } - } - } + // if (this.directive.storeStructure) { + // let store: { [resourceType: string]: number } = {}; + // if (this.directive.storeStructure instanceof StructurePowerBank) { + // store = {'power': this.directive.storeStructure.power}; + // } + // for (let resourceType in store) { + // if (store[resourceType] > 0) { + // //hauler.task = Tasks.withdraw(this.directive.storeStructure, resourceType); + // // Wait for it to die + // return; + // } + // } + // } // Shouldn't reach here log.warning(`${hauler.name} in ${hauler.room.print}: nothing to collect!`); } else { From a037189d1e6880b74ebd24533bc9af4f3e0d62ab Mon Sep 17 00:00:00 2001 From: Matthew Roy Date: Wed, 24 Apr 2019 16:15:17 -0700 Subject: [PATCH 04/20] fixing some power harvesting (honestly can't remember) --- src/directives/resource/haul.ts | 15 ++----- src/directives/resource/powerMine.ts | 10 +++-- src/overlords/powerMining/PowerDrill.ts | 53 +++++++++++++++++++++++-- src/overlords/situational/hauler.ts | 2 +- 4 files changed, 61 insertions(+), 19 deletions(-) diff --git a/src/directives/resource/haul.ts b/src/directives/resource/haul.ts index 96161a301..91db6f2db 100644 --- a/src/directives/resource/haul.ts +++ b/src/directives/resource/haul.ts @@ -31,11 +31,7 @@ export class DirectiveHaul extends Directive { } spawnMoarOverlords() { - if (this.room && this.pos.lookForStructure(STRUCTURE_POWER_BANK)) { - this.overlords.haul = new PowerHaulingOverlord(this); - } else { - this.overlords.haul = new HaulingOverlord(this); - } + this.overlords.haul = new HaulingOverlord(this); } get targetedBy(): string[] { @@ -57,12 +53,11 @@ export class DirectiveHaul extends Directive { return _.keys(this.drops).length > 0; } - get storeStructure(): StructureStorage | StructureTerminal | StructureNuker | StructurePowerBank | undefined { + get storeStructure(): StructureStorage | StructureTerminal | StructureNuker | undefined { if (this.pos.isVisible) { return this.pos.lookForStructure(STRUCTURE_STORAGE) || this.pos.lookForStructure(STRUCTURE_TERMINAL) || - this.pos.lookForStructure(STRUCTURE_NUKER) || - this.pos.lookForStructure(STRUCTURE_POWER_BANK); + this.pos.lookForStructure(STRUCTURE_NUKER); } return undefined; } @@ -74,8 +69,6 @@ export class DirectiveHaul extends Directive { if (this.storeStructure) { if (isStoreStructure(this.storeStructure)) { store = this.storeStructure.store; - } else if (this.storeStructure instanceof StructurePowerBank) { - store = {'power': this.storeStructure.power}; } else { store = {'energy': this.storeStructure.energy}; } @@ -118,7 +111,7 @@ export class DirectiveHaul extends Directive { if (_.sum(this.store) == 0 && this.pos.isVisible) { //this.remove(); this.finishAtTime = Game.time + 1000; - this.overlords + //this.overlords } if (Game.time >= this.finishAtTime) { this.remove(); diff --git a/src/directives/resource/powerMine.ts b/src/directives/resource/powerMine.ts index c290d9ff9..60b6ed715 100644 --- a/src/directives/resource/powerMine.ts +++ b/src/directives/resource/powerMine.ts @@ -23,6 +23,7 @@ export class DirectivePowerMine extends Directive { static secondaryColor = COLOR_RED; expectedSpawnTime = 200; + haulDirectiveCreated: boolean; private _powerBank: StructurePowerBank | undefined; private _drops: { [resourceType: string]: Resource[] }; @@ -85,8 +86,9 @@ export class DirectivePowerMine extends Directive { } spawnHaulers() { - if (this.room && (!this._powerBank || (this.calculateRemainingLifespan()! < Pathing.distance(this.colony.pos, this.flag.pos) + this.expectedSpawnTime))) { + Game.notify('Spawning haulers for power mining in room ' + this.room.name); + this.haulDirectiveCreated = true; this.overlords.powerHaul = new PowerHaulingOverlord(this); } } @@ -100,8 +102,10 @@ export class DirectivePowerMine extends Directive { run(): void { - + if (Game.time % 100 == 0 && !this.haulDirectiveCreated) { + Game.notify('Checking if should spawn haulers'); + this.spawnHaulers(); + } } - } diff --git a/src/overlords/powerMining/PowerDrill.ts b/src/overlords/powerMining/PowerDrill.ts index a56d92524..055215290 100644 --- a/src/overlords/powerMining/PowerDrill.ts +++ b/src/overlords/powerMining/PowerDrill.ts @@ -13,6 +13,7 @@ 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"; interface PowerDrillOverlordMemory extends OverlordMemory { targetPBID?: string; @@ -31,6 +32,8 @@ export class PowerDrillOverlord extends CombatOverlord { targetPowerBank: StructurePowerBank | undefined; haulDirectiveCreated: boolean; + partnerMap: Map; + drills: CombatZerg[]; coolant: CombatZerg[]; @@ -42,6 +45,7 @@ export class PowerDrillOverlord extends CombatOverlord { this.coolant = this.combatZerg(Roles.coolant); this.memory = Mem.wrap(this.directive.memory, 'powerDrill'); this.haulDirectiveCreated = false; + this.partnerMap = new Map(); } refresh() { @@ -51,7 +55,7 @@ export class PowerDrillOverlord extends CombatOverlord { } init() { - this.wishlist(3, CombatSetups.drill.default); + this.wishlist(2, CombatSetups.drill.default); this.wishlist(4, CombatSetups.coolant.default); } @@ -73,9 +77,16 @@ export class PowerDrillOverlord extends CombatOverlord { this.targetPowerBank = bank; // If power bank is dead if (bank == undefined) { - Game.notify("Power bank in " + this.room + " is dead."); + if (this.pos.lookFor(LOOK_RESOURCES).length == 0) { + // Well shit, we didn't finish mining + Game.notify("WE FUCKING FAILED. SORRY CHIEF, COULDN'T FINISHED POWER MINING IN " + this.room + " DELETING CREEP at time: " + Game.time.toString()); this.directive.remove(); + return; + } + Game.notify("Power bank in " + this.room + " is dead."); + //this.directive.remove(); Game.notify("FINISHED POWER MINING IN " + this.room + " DELETING CREEP at time: " + Game.time.toString()); + drill.say('💀 RIP 💀'); drill.suicide(); return; } @@ -98,8 +109,10 @@ export class PowerDrillOverlord extends CombatOverlord { // Handle killing bank if (drill.pos.isNearTo(this.targetPowerBank)) { + PowerDrillOverlord.periodicSay(drill,'Drilling⚒️'); drill.attack(this.targetPowerBank); } else { + PowerDrillOverlord.periodicSay(drill,'🚗Traveling🚗'); drill.goTo(this.targetPowerBank); } } @@ -107,13 +120,14 @@ export class PowerDrillOverlord extends CombatOverlord { private handleCoolant(coolant: CombatZerg) { // Go to powerbank room if (!this.room || coolant.room != this.room || coolant.pos.isEdge) { - // log.debugCreep(reaper, `Going to room!`); + // log.debugCreep(coolant, `Going to room!`); coolant.healSelfIfPossible(); coolant.goTo(this.pos); return; } else if (!this.targetPowerBank) { // If power bank is dead Game.notify("Power bank in " + this.room + " is dead."); + coolant.say('💀 RIP 💀'); coolant.suicide(); return; } @@ -121,22 +135,53 @@ export class PowerDrillOverlord extends CombatOverlord { 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); } if (coolant.pos.getRangeTo(this.targetPowerBank) > 2) { coolant.goTo(this.targetPowerBank); + } else if (coolant.pos.getRangeTo(this.targetPowerBank) == 1) { + coolant.flee([this.targetPowerBank.pos]); + } else { + coolant.goTo(_.sample(_.filter(this.drills, drill => drill.hits < drill.hitsMax))); } - coolant.autoHeal(false); + 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.memory.partner = needsHealing; + } + // let newPartner = _.sample(_.filter(this.drills, drill => this.room == drill.room)); + // coolant.memory.partner = newPartner != undefined ? newPartner.name : undefined; + coolant.say('Partnering!'); + } + + static periodicSay(zerg: CombatZerg, text: string) { + if (Game.time % 10 == PowerDrillOverlord.getCreepNameOffset(zerg)) { + zerg.say(text, true); + } + } + static getCreepNameOffset(creep: Zerg) { + return parseInt(creep.name.charAt(creep.name.length-1)) || 0; } run() { diff --git a/src/overlords/situational/hauler.ts b/src/overlords/situational/hauler.ts index ffd527cca..9655717ab 100644 --- a/src/overlords/situational/hauler.ts +++ b/src/overlords/situational/hauler.ts @@ -48,7 +48,7 @@ export class HaulingOverlord extends Overlord { this.wishlist(numHaulers, Setups.transporters.early); } - protected handleHauler(hauler: Zerg) { + private handleHauler(hauler: Zerg) { if (_.sum(hauler.carry) == 0) { // Travel to directive and collect resources if (hauler.inSameRoomAs(this.directive)) { From 35476b207802d2c1414cd6e65ef22ba4757fb392 Mon Sep 17 00:00:00 2001 From: Matthew Roy Date: Thu, 25 Apr 2019 00:38:34 -0700 Subject: [PATCH 05/20] Adding hauling --- src/directives/resource/powerMine.ts | 19 +++++++++++++------ src/overlords/powerMining/PowerDrill.ts | 5 +++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/directives/resource/powerMine.ts b/src/directives/resource/powerMine.ts index 60b6ed715..0a87b5230 100644 --- a/src/directives/resource/powerMine.ts +++ b/src/directives/resource/powerMine.ts @@ -23,6 +23,7 @@ export class DirectivePowerMine extends Directive { static secondaryColor = COLOR_RED; expectedSpawnTime = 200; + miningDone: boolean; haulDirectiveCreated: boolean; private _powerBank: StructurePowerBank | undefined; private _drops: { [resourceType: string]: Resource[] }; @@ -34,7 +35,10 @@ export class DirectivePowerMine extends Directive { } spawnMoarOverlords() { - this.overlords.powerMine = new PowerDrillOverlord(this); + if (!this.miningDone) { + this.overlords.powerMine = new PowerDrillOverlord(this); + } + this.spawnHaulers(); } get targetedBy(): string[] { @@ -93,7 +97,10 @@ export class DirectivePowerMine extends Directive { } } - + setMiningDone(name: string) { + delete this.overlords[name]; + this.miningDone = true; + } init(): void { this.alert(`PowerMine directive active`); @@ -102,10 +109,10 @@ export class DirectivePowerMine extends Directive { run(): void { - if (Game.time % 100 == 0 && !this.haulDirectiveCreated) { - Game.notify('Checking if should spawn haulers'); - this.spawnHaulers(); - } + // if (Game.time % 100 == 0 && !this.haulDirectiveCreated) { + // Game.notify('Checking if should spawn haulers'); + // this.spawnHaulers(); + // } } } diff --git a/src/overlords/powerMining/PowerDrill.ts b/src/overlords/powerMining/PowerDrill.ts index 055215290..56e711bc5 100644 --- a/src/overlords/powerMining/PowerDrill.ts +++ b/src/overlords/powerMining/PowerDrill.ts @@ -33,6 +33,7 @@ export class PowerDrillOverlord extends CombatOverlord { haulDirectiveCreated: boolean; partnerMap: Map; + isDone: boolean; drills: CombatZerg[]; coolant: CombatZerg[]; @@ -128,6 +129,7 @@ export class PowerDrillOverlord extends CombatOverlord { // If power bank is dead Game.notify("Power bank in " + this.room + " is dead."); coolant.say('💀 RIP 💀'); + this.isDone = true; coolant.suicide(); return; } @@ -187,6 +189,9 @@ export class PowerDrillOverlord extends CombatOverlord { run() { this.autoRun(this.drills, drill => this.handleDrill(drill)); this.autoRun(this.coolant, coolant => this.handleCoolant(coolant)); + if (this.isDone) { + this.directive.setMiningDone(this.name); + } } visuals() { From c5c0ebf7d624374e8b3471fc5d0315c265d6618c Mon Sep 17 00:00:00 2001 From: Matthew Roy Date: Thu, 25 Apr 2019 11:43:35 -0700 Subject: [PATCH 06/20] Look at this point I'm just storing more code --- src/directives/resource/powerMine.ts | 14 +++++++------ src/overlords/powerMining/PowerDrill.ts | 24 +++++++++++++++++------ src/overlords/powerMining/PowerHauler.ts | 25 ++++++++++++++++++++---- 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/src/directives/resource/powerMine.ts b/src/directives/resource/powerMine.ts index 0a87b5230..d28496d4e 100644 --- a/src/directives/resource/powerMine.ts +++ b/src/directives/resource/powerMine.ts @@ -25,7 +25,7 @@ export class DirectivePowerMine extends Directive { expectedSpawnTime = 200; miningDone: boolean; haulDirectiveCreated: boolean; - private _powerBank: StructurePowerBank | undefined; + powerBank: StructurePowerBank | undefined; private _drops: { [resourceType: string]: Resource[] }; memory: DirectivePowerMineMemory; @@ -37,6 +37,8 @@ export class DirectivePowerMine extends Directive { spawnMoarOverlords() { if (!this.miningDone) { this.overlords.powerMine = new PowerDrillOverlord(this); + } else { + console.log('Mining is done!'); } this.spawnHaulers(); } @@ -68,7 +70,7 @@ export class DirectivePowerMine extends Directive { 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 + this.memory.totalResources = this.powerBank ? this.powerBank.power : this.memory.totalResources; // update total amount remaining } return this.memory.totalResources; } @@ -76,21 +78,21 @@ export class DirectivePowerMine extends Directive { calculateRemainingLifespan() { if (!this.room) { return undefined; - } else if (this._powerBank == undefined) { + } else if (this.powerBank == undefined) { // Power Bank is gone return 0; } else { - let tally = calculateFormationStrength(this._powerBank.pos.findInRange(FIND_MY_CREEPS, 4)); + 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; + return this.powerBank.hits / avgDamagePerTick; } } spawnHaulers() { - if (this.room && (!this._powerBank || (this.calculateRemainingLifespan()! < Pathing.distance(this.colony.pos, this.flag.pos) + this.expectedSpawnTime))) { + if (this.room && (!this.powerBank || (this.calculateRemainingLifespan()! < (Pathing.distance(this.colony.pos, this.flag.pos) + this.expectedSpawnTime)))) { Game.notify('Spawning haulers for power mining in room ' + this.room.name); this.haulDirectiveCreated = true; this.overlords.powerHaul = new PowerHaulingOverlord(this); diff --git a/src/overlords/powerMining/PowerDrill.ts b/src/overlords/powerMining/PowerDrill.ts index 56e711bc5..eaa39bcf0 100644 --- a/src/overlords/powerMining/PowerDrill.ts +++ b/src/overlords/powerMining/PowerDrill.ts @@ -56,8 +56,10 @@ export class PowerDrillOverlord extends CombatOverlord { } init() { - this.wishlist(2, CombatSetups.drill.default); - this.wishlist(4, CombatSetups.coolant.default); + this.wishlist(1, CombatSetups.drill.default); + this.wishlist(2, CombatSetups.coolant.default); + this.wishlist(1, CombatSetups.drill.default); + this.wishlist(2, CombatSetups.coolant.default); } private getHostileDrill(powerBank: StructurePowerBank) { @@ -74,10 +76,12 @@ export class PowerDrillOverlord extends CombatOverlord { if (!this.room) { // We are not there yet } else { - var bank = this.pos.lookForStructure(STRUCTURE_POWER_BANK) as StructurePowerBank; - this.targetPowerBank = bank; // If power bank is dead - if (bank == undefined) { + if (this.targetPowerBank == undefined) { + this.targetPowerBank = this.pos.lookForStructure(STRUCTURE_POWER_BANK) as StructurePowerBank; + if (this.targetPowerBank) { + return; + } if (this.pos.lookFor(LOOK_RESOURCES).length == 0) { // Well shit, we didn't finish mining Game.notify("WE FUCKING FAILED. SORRY CHIEF, COULDN'T FINISHED POWER MINING IN " + this.room + " DELETING CREEP at time: " + Game.time.toString()); @@ -88,13 +92,14 @@ export class PowerDrillOverlord extends CombatOverlord { //this.directive.remove(); Game.notify("FINISHED POWER MINING IN " + this.room + " DELETING CREEP at time: " + Game.time.toString()); drill.say('💀 RIP 💀'); + this.isDone = true; drill.suicide(); return; } } } - // Go to keeper room + // Go to power room if (!this.room || drill.room != this.room || drill.pos.isEdge || !this.targetPowerBank) { // log.debugCreep(drill, `Going to room!`); Game.notify("Drill is moving to power site in " + this.room + "."); @@ -110,6 +115,9 @@ export class PowerDrillOverlord extends CombatOverlord { // Handle killing bank if (drill.pos.isNearTo(this.targetPowerBank)) { + if (!this.partnerMap.get(drill.name)) { + this.partnerMap.set(drill.name, []); + } PowerDrillOverlord.periodicSay(drill,'Drilling⚒️'); drill.attack(this.targetPowerBank); } else { @@ -169,8 +177,12 @@ export class PowerDrillOverlord extends CombatOverlord { 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!'); diff --git a/src/overlords/powerMining/PowerHauler.ts b/src/overlords/powerMining/PowerHauler.ts index d2dd6d246..8c73d22c0 100644 --- a/src/overlords/powerMining/PowerHauler.ts +++ b/src/overlords/powerMining/PowerHauler.ts @@ -21,7 +21,6 @@ export class PowerHaulingOverlord extends Overlord { haulers: Zerg[]; directive: DirectivePowerMine; - powerBank: StructurePowerBank | undefined; tickToSpawnOn: number; numHaulers: number; @@ -59,16 +58,16 @@ export class PowerHaulingOverlord extends Overlord { calculateRemainingLifespan() { if (!this.room) { return undefined; - } else if (this.powerBank == undefined) { + } else if (this.directive.powerBank == undefined) { // Power Bank is gone return 0; } else { - let tally = calculateFormationStrength(this.powerBank.pos.findInRange(FIND_MY_CREEPS, 4)); + let tally = calculateFormationStrength(this.directive.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; + return this.directive.powerBank.hits / avgDamagePerTick; } } @@ -84,6 +83,24 @@ export class PowerHaulingOverlord extends Overlord { 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('🚬'); + } + 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 💀'); + hauler.suicide(); + return; + } } // Withdraw from store structure // if (this.directive.storeStructure) { From 2f45e86d900ae5bd34b6dd1f8eaeca1455a13b72 Mon Sep 17 00:00:00 2001 From: Matthew Roy Date: Thu, 25 Apr 2019 11:55:45 -0700 Subject: [PATCH 07/20] Cleaning up some code --- src/directives/resource/haul.ts | 5 ++--- src/directives/resource/powerMine.ts | 2 +- src/overlords/Overlord.ts | 13 +++++++++---- src/overlords/powerMining/PowerHauler.ts | 14 -------------- src/overlords/situational/hauler.ts | 2 +- 5 files changed, 13 insertions(+), 23 deletions(-) diff --git a/src/directives/resource/haul.ts b/src/directives/resource/haul.ts index 91db6f2db..830893856 100644 --- a/src/directives/resource/haul.ts +++ b/src/directives/resource/haul.ts @@ -109,9 +109,8 @@ export class DirectiveHaul extends Directive { run(): void { if (_.sum(this.store) == 0 && this.pos.isVisible) { - //this.remove(); - this.finishAtTime = Game.time + 1000; - //this.overlords + // If everything is picked up, crudely give enough time to bring it back + this.finishAtTime = Game.time + 800; } if (Game.time >= this.finishAtTime) { this.remove(); diff --git a/src/directives/resource/powerMine.ts b/src/directives/resource/powerMine.ts index d28496d4e..e78734a6a 100644 --- a/src/directives/resource/powerMine.ts +++ b/src/directives/resource/powerMine.ts @@ -22,7 +22,7 @@ export class DirectivePowerMine extends Directive { static color = COLOR_YELLOW; static secondaryColor = COLOR_RED; - expectedSpawnTime = 200; + expectedSpawnTime = 150; miningDone: boolean; haulDirectiveCreated: boolean; powerBank: StructurePowerBank | undefined; diff --git a/src/overlords/Overlord.ts b/src/overlords/Overlord.ts index f5a455ef2..3fa2ab1d9 100644 --- a/src/overlords/Overlord.ts +++ b/src/overlords/Overlord.ts @@ -45,10 +45,10 @@ export interface OverlordMemory { } enum overlordLifecyleState { - preparing, - active, - paused, - finishing + preparing, // Waiting to begin at correct time + active, // Currently running + paused, // Temporarily paused + finishing // Spawning is now disabled, run overlord until all creeps are dead } const OverlordMemoryDefaults: OverlordMemory = {}; @@ -326,6 +326,11 @@ export abstract class Overlord { protected requestCreep(setup: CreepSetup, opts = {} as CreepRequestOptions) { _.defaults(opts, {priority: this.priority, prespawn: DEFAULT_PRESPAWN}); let spawner = this.spawnGroup || this.colony.spawnGroup || this.colony.hatchery; + if (this.lifeCycle == overlordLifecyleState.paused || this.lifeCycle == overlordLifecyleState.finishing) { + // Don't + log.warning(`Overlord ${this.ref} @ ${this.pos.print}: State is ${this.lifeCycle}, not spawning!`); + return; + } if (spawner) { let request: SpawnRequest = { setup : setup, diff --git a/src/overlords/powerMining/PowerHauler.ts b/src/overlords/powerMining/PowerHauler.ts index 8c73d22c0..fa514e3c5 100644 --- a/src/overlords/powerMining/PowerHauler.ts +++ b/src/overlords/powerMining/PowerHauler.ts @@ -102,20 +102,6 @@ export class PowerHaulingOverlord extends Overlord { return; } } - // Withdraw from store structure - // if (this.directive.storeStructure) { - // let store: { [resourceType: string]: number } = {}; - // if (this.directive.storeStructure instanceof StructurePowerBank) { - // store = {'power': this.directive.storeStructure.power}; - // } - // for (let resourceType in store) { - // if (store[resourceType] > 0) { - // //hauler.task = Tasks.withdraw(this.directive.storeStructure, resourceType); - // // Wait for it to die - // return; - // } - // } - // } // Shouldn't reach here log.warning(`${hauler.name} in ${hauler.room.print}: nothing to collect!`); } else { diff --git a/src/overlords/situational/hauler.ts b/src/overlords/situational/hauler.ts index 9655717ab..86bfd43a3 100644 --- a/src/overlords/situational/hauler.ts +++ b/src/overlords/situational/hauler.ts @@ -19,7 +19,7 @@ export class HaulingOverlord extends Overlord { haulers: Zerg[]; directive: DirectiveHaul; - requiredRCL: number = 4; + requiredRCL: 4; constructor(directive: DirectiveHaul, priority = directive.hasDrops ? OverlordPriority.collectionUrgent.haul : OverlordPriority.collection.haul) { From e41fed1f3fff3ac53ff0d8675e6671c32c9d917c Mon Sep 17 00:00:00 2001 From: Matthew Roy Date: Sat, 27 Apr 2019 11:13:17 -0700 Subject: [PATCH 08/20] Adding configurable ranges and cleaning up --- src/declarations/memory.d.ts | 6 ++ src/directives/resource/powerMine.ts | 33 ++++--- src/intel/RoomIntel.ts | 35 +++++++- src/memory/Memory.ts | 6 ++ src/overlords/powerMining/PowerDrill.ts | 109 ++++++++++++----------- src/overlords/powerMining/PowerHauler.ts | 20 +++-- 6 files changed, 134 insertions(+), 75 deletions(-) diff --git a/src/declarations/memory.d.ts b/src/declarations/memory.d.ts index 270b52c4f..53061c0cd 100644 --- a/src/declarations/memory.d.ts +++ b/src/declarations/memory.d.ts @@ -21,6 +21,12 @@ interface Memory { operationMode: operationMode; log: LoggerMemory; enableVisuals: boolean; + powerCollection: { + enabled: boolean; + maxRange: number; + minPower: number; + }; + resourceCollectionMode: resourceCollectionMode; } profiler?: any; stats: any; diff --git a/src/directives/resource/powerMine.ts b/src/directives/resource/powerMine.ts index e78734a6a..399f6bec2 100644 --- a/src/directives/resource/powerMine.ts +++ b/src/directives/resource/powerMine.ts @@ -1,10 +1,10 @@ import {Directive} from '../Directive'; import {profile} from '../../profiler/decorator'; -import {isStoreStructure} from '../../declarations/typeGuards'; 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 { @@ -24,14 +24,16 @@ export class DirectivePowerMine extends Directive { expectedSpawnTime = 150; miningDone: boolean; + haulingDone: boolean; haulDirectiveCreated: boolean; - powerBank: StructurePowerBank | undefined; + 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() { @@ -62,6 +64,11 @@ export class DirectivePowerMine extends Directive { return _.keys(this.drops).length > 0; } + get powerBank(): StructurePowerBank | undefined { + this._powerBank = this._powerBank || this.room != undefined ? this.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 */ @@ -72,49 +79,51 @@ export class DirectivePowerMine extends Directive { if (this.pos.isVisible) { this.memory.totalResources = this.powerBank ? this.powerBank.power : this.memory.totalResources; // update total amount remaining } + console.log("Directive total resources = " + this.totalResources); return this.memory.totalResources; } calculateRemainingLifespan() { + console.log(this._powerBank); if (!this.room) { return undefined; } else if (this.powerBank == undefined) { - // Power Bank is gone - return 0; + 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); + console.log("Calculating PB remaining lifespan: " + this.powerBank.hits / avgDamagePerTick); return this.powerBank.hits / avgDamagePerTick; } } spawnHaulers() { - if (this.room && (!this.powerBank || (this.calculateRemainingLifespan()! < (Pathing.distance(this.colony.pos, this.flag.pos) + this.expectedSpawnTime)))) { - Game.notify('Spawning haulers for power mining in room ' + this.room.name); + log.info("Checking spawning haulers"); + if (this.haulDirectiveCreated || this.room && (!this.powerBank || (this.calculateRemainingLifespan()! < (Pathing.distance(this.colony.pos, this.flag.pos) + this.expectedSpawnTime)))) { + Game.notify('Spawning haulers for power mining in room ' + this.pos.roomName); this.haulDirectiveCreated = true; this.overlords.powerHaul = new PowerHaulingOverlord(this); } } setMiningDone(name: string) { + Game.notify("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; } init(): void { this.alert(`PowerMine directive active`); } - - run(): void { - // if (Game.time % 100 == 0 && !this.haulDirectiveCreated) { - // Game.notify('Checking if should spawn haulers'); - // this.spawnHaulers(); - // } } } diff --git a/src/intel/RoomIntel.ts b/src/intel/RoomIntel.ts index b3a6bf0ec..c587a90da 100644 --- a/src/intel/RoomIntel.ts +++ b/src/intel/RoomIntel.ts @@ -6,6 +6,9 @@ import {Zerg} from '../zerg/Zerg'; import {profile} from '../profiler/decorator'; import {MY_USERNAME} from '../~settings'; import {bodyCost} from '../creepSetups/CreepSetup'; +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; @@ -315,6 +318,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; @@ -353,7 +386,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 67c9f751b..9ee428044 100644 --- a/src/memory/Memory.ts +++ b/src/memory/Memory.ts @@ -197,6 +197,12 @@ export class Mem { operationMode: DEFAULT_OPERATION_MODE, log : {}, enableVisuals: true, + powerCollection: { + enabled: false, + maxRange: 5, + minPower: 5000, + }, + resourceCollectionMode: 0, }); if (!Memory.stats) { Memory.stats = {}; diff --git a/src/overlords/powerMining/PowerDrill.ts b/src/overlords/powerMining/PowerDrill.ts index eaa39bcf0..93847c607 100644 --- a/src/overlords/powerMining/PowerDrill.ts +++ b/src/overlords/powerMining/PowerDrill.ts @@ -14,6 +14,7 @@ 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; @@ -29,9 +30,6 @@ export class PowerDrillOverlord extends CombatOverlord { directive: DirectivePowerMine; memory: PowerDrillOverlordMemory; - targetPowerBank: StructurePowerBank | undefined; - haulDirectiveCreated: boolean; - partnerMap: Map; isDone: boolean; @@ -45,7 +43,6 @@ export class PowerDrillOverlord extends CombatOverlord { this.drills = this.combatZerg(Roles.drill); this.coolant = this.combatZerg(Roles.coolant); this.memory = Mem.wrap(this.directive.memory, 'powerDrill'); - this.haulDirectiveCreated = false; this.partnerMap = new Map(); } @@ -72,16 +69,15 @@ export class PowerDrillOverlord extends CombatOverlord { } private handleDrill(drill: CombatZerg) { - if (!this.targetPowerBank) { + if (drill.spawning) { + return; + } + if (!this.directive.powerBank) { if (!this.room) { // We are not there yet } else { // If power bank is dead - if (this.targetPowerBank == undefined) { - this.targetPowerBank = this.pos.lookForStructure(STRUCTURE_POWER_BANK) as StructurePowerBank; - if (this.targetPowerBank) { - return; - } + if (this.directive.powerBank == undefined) { if (this.pos.lookFor(LOOK_RESOURCES).length == 0) { // Well shit, we didn't finish mining Game.notify("WE FUCKING FAILED. SORRY CHIEF, COULDN'T FINISHED POWER MINING IN " + this.room + " DELETING CREEP at time: " + Game.time.toString()); @@ -89,51 +85,51 @@ export class PowerDrillOverlord extends CombatOverlord { return; } Game.notify("Power bank in " + this.room + " is dead."); - //this.directive.remove(); - Game.notify("FINISHED POWER MINING IN " + this.room + " DELETING CREEP at time: " + Game.time.toString()); drill.say('💀 RIP 💀'); - this.isDone = true; - drill.suicide(); + this.directive.setMiningDone(this.name); + let result = drill.suicide(); + if (result == ERR_BUSY) { + drill.spawning + } + //this.directive.remove(); + Game.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.targetPowerBank) { + if (!this.room || drill.room != this.room || drill.pos.isEdge || !this.directive.powerBank) { // log.debugCreep(drill, `Going to room!`); - Game.notify("Drill is moving to power site in " + this.room + "."); + Game.notify("Drill is moving to power site in " + this.pos.roomName + "."); drill.goTo(this.pos); return; - } else if (this.targetPowerBank.hits < 800000 && !this.haulDirectiveCreated) { - Game.notify("Power bank in " + this.room + " is almost dead"); - Game.notify("Power bank in " + this.room + ", beginning haul operation."); - //this.haulDirectiveCreated = typeof DirectiveHaul.create(this.pos) == "string"; } - // Spawn a hauler for this location - // Should make a 49 carry 1 move creep to hold some, and a bunch of creeps to pick up ground first then container creep // Handle killing bank - if (drill.pos.isNearTo(this.targetPowerBank)) { + 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.targetPowerBank); + drill.attack(this.directive.powerBank); } else { PowerDrillOverlord.periodicSay(drill,'🚗Traveling🚗'); - drill.goTo(this.targetPowerBank); + 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.targetPowerBank) { + } else if (!this.directive.powerBank) { // If power bank is dead Game.notify("Power bank in " + this.room + " is dead."); coolant.say('💀 RIP 💀'); @@ -141,31 +137,15 @@ export class PowerDrillOverlord extends CombatOverlord { coolant.suicide(); return; } - - 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 (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); } - if (Game.time % 10 == PowerDrillOverlord.getCreepNameOffset(coolant)) { - this.findDrillToPartner(coolant); - } - return; - } else { - this.findDrillToPartner(coolant); - } - if (coolant.pos.getRangeTo(this.targetPowerBank) > 2) { - coolant.goTo(this.targetPowerBank); - } else if (coolant.pos.getRangeTo(this.targetPowerBank) == 1) { - coolant.flee([this.targetPowerBank.pos]); + } else if (coolant.pos.getRangeTo(this.directive.powerBank) == 1) { + coolant.move(Math.round(Math.random()*7) as DirectionConstant); } else { coolant.goTo(_.sample(_.filter(this.drills, drill => drill.hits < drill.hitsMax))); } @@ -188,6 +168,29 @@ export class PowerDrillOverlord extends CombatOverlord { 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: CombatZerg, text: string) { if (Game.time % 10 == PowerDrillOverlord.getCreepNameOffset(zerg)) { zerg.say(text, true); @@ -201,14 +204,14 @@ export class PowerDrillOverlord extends CombatOverlord { run() { this.autoRun(this.drills, drill => this.handleDrill(drill)); this.autoRun(this.coolant, coolant => this.handleCoolant(coolant)); - if (this.isDone) { + if (this.isDone && !this.directive.miningDone) { this.directive.setMiningDone(this.name); } } visuals() { - if (this.room && this.targetPowerBank) { - Visualizer.marker(this.targetPowerBank.pos); + 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 index fa514e3c5..bcf68a3eb 100644 --- a/src/overlords/powerMining/PowerHauler.ts +++ b/src/overlords/powerMining/PowerHauler.ts @@ -1,15 +1,12 @@ import {Overlord} from '../Overlord'; import {OverlordPriority} from '../../priorities/priorities_overlords'; import {Zerg} from '../../zerg/Zerg'; -import {DirectiveHaul} from '../../directives/resource/haul'; import {Tasks} from '../../tasks/Tasks'; -import {isStoreStructure} from '../../declarations/typeGuards'; import {log} from '../../console/log'; import {Pathing} from '../../movement/Pathing'; import {Energetics} from '../../logistics/Energetics'; import {profile} from '../../profiler/decorator'; import {Roles, Setups} from '../../creepSetups/setups'; -import {HaulingOverlord} from "../situational/hauler"; import {calculateFormationStrength} from "../../utilities/creepUtils"; import {DirectivePowerMine} from "../../directives/resource/powerMine"; @@ -49,7 +46,7 @@ export class PowerHaulingOverlord extends Overlord { let haulerCarryParts = Setups.transporters.early.getBodyPotential(CARRY, this.colony); let haulingPowerPerLifetime = CREEP_LIFE_TIME * haulerCarryParts * CARRY_CAPACITY; // Calculate number of haulers - this.numHaulers = Math.min(Math.ceil(haulingPowerNeeded / haulingPowerPerLifetime), MAX_HAULERS); + this.numHaulers = Math.min(Math.ceil(haulingPowerNeeded / haulingPowerPerLifetime), MAX_HAULERS) * 2; // Request the haulers this.tickToSpawnOn = Game.time + (this.calculateRemainingLifespan() || 0) - this.prespawnAmount; } @@ -74,6 +71,12 @@ export class PowerHaulingOverlord extends Overlord { protected handleHauler(hauler: Zerg) { if (_.sum(hauler.carry) == 0) { // Travel to directive and collect resources + if (this.directive.haulingDone) { + hauler.say('💀 RIP 💀',true); + log.warning(`${hauler.name} is committing suicide as directive is done!`); + this.numHaulers = 0; + hauler.suicide(); + } if (hauler.inSameRoomAs(this.directive)) { // Pick up drops first if (this.directive.hasDrops) { @@ -87,7 +90,7 @@ export class PowerHaulingOverlord extends Overlord { if (hauler.pos.getRangeTo(this.directive.powerBank) > 4) { hauler.goTo(this.directive.powerBank); } else { - hauler.say('🚬'); + hauler.say('🚬', true); } return; } else if (this.room && this.room.drops) { @@ -97,7 +100,8 @@ export class PowerHaulingOverlord extends Overlord { hauler.task = Tasks.pickup(drop); return; } else { - hauler.say('💀 RIP 💀'); + hauler.say('💀 RIP 💀',true); + log.warning(`${hauler.name} is committing suicide!`); hauler.suicide(); return; } @@ -105,13 +109,11 @@ export class PowerHaulingOverlord extends Overlord { // Shouldn't reach here log.warning(`${hauler.name} in ${hauler.room.print}: nothing to collect!`); } else { - // hauler.task = Tasks.goTo(this.directive); hauler.goTo(this.directive); } } else { // Travel to colony room and deposit resources if (hauler.inSameRoomAs(this.colony)) { - // Put energy in storage and minerals in terminal if there is one for (let resourceType in hauler.carry) { if (hauler.carry[resourceType] == 0) continue; if (resourceType == RESOURCE_ENERGY) { // prefer to put energy in storage @@ -141,7 +143,7 @@ export class PowerHaulingOverlord extends Overlord { } run() { - if (Game.time >= this.tickToSpawnOn && this.haulers.length == 0) { + if (Game.time >= this.tickToSpawnOn) { this.wishlist(this.numHaulers, Setups.transporters.early); } for (let hauler of this.haulers) { From 45a68a907398815402299cf6c9ca482955d312f0 Mon Sep 17 00:00:00 2001 From: Matthew Roy Date: Sat, 27 Apr 2019 12:36:20 -0700 Subject: [PATCH 09/20] Finalizing --- src/creepSetups/setups.ts | 4 ++++ src/declarations/memory.d.ts | 1 - src/memory/Memory.ts | 1 - src/overlords/Overlord.ts | 13 ------------- src/overlords/powerMining/PowerDrill.ts | 16 +++++++--------- 5 files changed, 11 insertions(+), 24 deletions(-) diff --git a/src/creepSetups/setups.ts b/src/creepSetups/setups.ts index f49de8b52..6dc917370 100644 --- a/src/creepSetups/setups.ts +++ b/src/creepSetups/setups.ts @@ -321,6 +321,10 @@ export const CombatSetups = { 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 53061c0cd..14b9e4380 100644 --- a/src/declarations/memory.d.ts +++ b/src/declarations/memory.d.ts @@ -26,7 +26,6 @@ interface Memory { maxRange: number; minPower: number; }; - resourceCollectionMode: resourceCollectionMode; } profiler?: any; stats: any; diff --git a/src/memory/Memory.ts b/src/memory/Memory.ts index 9ee428044..378924e9d 100644 --- a/src/memory/Memory.ts +++ b/src/memory/Memory.ts @@ -202,7 +202,6 @@ export class Mem { maxRange: 5, minPower: 5000, }, - resourceCollectionMode: 0, }); if (!Memory.stats) { Memory.stats = {}; diff --git a/src/overlords/Overlord.ts b/src/overlords/Overlord.ts index 3fa2ab1d9..a50b7998b 100644 --- a/src/overlords/Overlord.ts +++ b/src/overlords/Overlord.ts @@ -44,13 +44,6 @@ export interface OverlordMemory { suspendUntil?: number; } -enum overlordLifecyleState { - preparing, // Waiting to begin at correct time - active, // Currently running - paused, // Temporarily paused - finishing // Spawning is now disabled, run overlord until all creeps are dead -} - const OverlordMemoryDefaults: OverlordMemory = {}; /** @@ -76,7 +69,6 @@ export abstract class Overlord { private _combatZerg: { [roleName: string]: CombatZerg[] }; private boosts: { [roleName: string]: _ResourceConstantSansEnergy[] | undefined }; creepUsageReport: { [roleName: string]: [number, number] | undefined }; - lifeCycle: overlordLifecyleState; constructor(initializer: OverlordInitializer | Colony, name: string, priority: number) { this.initializer = initializer; @@ -326,11 +318,6 @@ export abstract class Overlord { protected requestCreep(setup: CreepSetup, opts = {} as CreepRequestOptions) { _.defaults(opts, {priority: this.priority, prespawn: DEFAULT_PRESPAWN}); let spawner = this.spawnGroup || this.colony.spawnGroup || this.colony.hatchery; - if (this.lifeCycle == overlordLifecyleState.paused || this.lifeCycle == overlordLifecyleState.finishing) { - // Don't - log.warning(`Overlord ${this.ref} @ ${this.pos.print}: State is ${this.lifeCycle}, not spawning!`); - return; - } if (spawner) { let request: SpawnRequest = { setup : setup, diff --git a/src/overlords/powerMining/PowerDrill.ts b/src/overlords/powerMining/PowerDrill.ts index 93847c607..d882b1c0b 100644 --- a/src/overlords/powerMining/PowerDrill.ts +++ b/src/overlords/powerMining/PowerDrill.ts @@ -54,9 +54,7 @@ export class PowerDrillOverlord extends CombatOverlord { init() { this.wishlist(1, CombatSetups.drill.default); - this.wishlist(2, CombatSetups.coolant.default); - this.wishlist(1, CombatSetups.drill.default); - this.wishlist(2, CombatSetups.coolant.default); + this.wishlist(2, CombatSetups.coolant.small); } private getHostileDrill(powerBank: StructurePowerBank) { @@ -77,13 +75,13 @@ export class PowerDrillOverlord extends CombatOverlord { // We are not there yet } else { // If power bank is dead - if (this.directive.powerBank == undefined) { + if (this.directive.powerBank == undefined && !this.directive.haulDirectiveCreated) { if (this.pos.lookFor(LOOK_RESOURCES).length == 0) { - // Well shit, we didn't finish mining - Game.notify("WE FUCKING FAILED. SORRY CHIEF, COULDN'T FINISHED POWER MINING IN " + this.room + " DELETING CREEP at time: " + Game.time.toString()); - this.directive.remove(); - return; - } + // Well shit, we didn't finish mining + Game.notify(`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 + " is dead."); drill.say('💀 RIP 💀'); this.directive.setMiningDone(this.name); From 9f3d37883ce584a7fca573ceb69e6664d036ba53 Mon Sep 17 00:00:00 2001 From: Matthew Roy Date: Thu, 2 May 2019 17:21:27 -0700 Subject: [PATCH 10/20] storing for transfer --- src/directives/resource/powerMine.ts | 9 ++++++++- src/intel/RoomIntel.ts | 2 +- src/overlords/powerMining/PowerHauler.ts | 25 +++++++++++------------- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/directives/resource/powerMine.ts b/src/directives/resource/powerMine.ts index 399f6bec2..1da0e5b31 100644 --- a/src/directives/resource/powerMine.ts +++ b/src/directives/resource/powerMine.ts @@ -23,6 +23,13 @@ export class DirectivePowerMine extends Directive { static secondaryColor = COLOR_RED; expectedSpawnTime = 150; + const DrillStatus = { + drilling : 0, + AcquiringMinerals: 1, + LoadingLabs : 2, + Synthesizing : 3, + UnloadingLabs : 4, + }; miningDone: boolean; haulingDone: boolean; haulDirectiveCreated: boolean; @@ -106,7 +113,7 @@ export class DirectivePowerMine extends Directive { spawnHaulers() { log.info("Checking spawning haulers"); if (this.haulDirectiveCreated || this.room && (!this.powerBank || (this.calculateRemainingLifespan()! < (Pathing.distance(this.colony.pos, this.flag.pos) + this.expectedSpawnTime)))) { - Game.notify('Spawning haulers for power mining in room ' + this.pos.roomName); + Game.notify('Activating spawning haulers for power mining in room ' + this.pos.roomName); this.haulDirectiveCreated = true; this.overlords.powerHaul = new PowerHaulingOverlord(this); } diff --git a/src/intel/RoomIntel.ts b/src/intel/RoomIntel.ts index c587a90da..7a3a7b8b5 100644 --- a/src/intel/RoomIntel.ts +++ b/src/intel/RoomIntel.ts @@ -319,7 +319,7 @@ export class RoomIntel { } /** - * Find powerbanks within range of maxRange and power above minPower to mine + * 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 */ diff --git a/src/overlords/powerMining/PowerHauler.ts b/src/overlords/powerMining/PowerHauler.ts index bcf68a3eb..8c8b71592 100644 --- a/src/overlords/powerMining/PowerHauler.ts +++ b/src/overlords/powerMining/PowerHauler.ts @@ -23,7 +23,7 @@ export class PowerHaulingOverlord extends Overlord { requiredRCL = 6; // Allow time for body to spawn - prespawnAmount = 200; + prespawnAmount = 250; constructor(directive: DirectivePowerMine, priority = OverlordPriority.collectionUrgent.haul) { super(directive, 'powerHaul', priority); @@ -35,23 +35,19 @@ export class PowerHaulingOverlord extends Overlord { if (!this.colony.storage || _.sum(this.colony.storage.store) > Energetics.settings.storage.total.cap) { return; } - // Spawn a number of haulers sufficient to move all resources within a lifetime, up to a max - let MAX_HAULERS = 5; - // Calculate total needed amount of hauling power as (resource amount * trip distance) - let tripDistance = 2 * Pathing.distance((this.colony.storage || this.colony).pos, this.directive.pos); - let haulingPowerNeeded = Math.min(this.directive.totalResources, - this.colony.storage.storeCapacity - - _.sum(this.colony.storage.store)) * tripDistance; + // Spawn haulers to collect ALL the power at the same time. + let haulingPartsNeeded = this.directive.totalResources/50; // Calculate amount of hauling each hauler provides in a lifetime - let haulerCarryParts = Setups.transporters.early.getBodyPotential(CARRY, this.colony); - let haulingPowerPerLifetime = CREEP_LIFE_TIME * haulerCarryParts * CARRY_CAPACITY; + let haulerCarryParts = Setups.transporters.default.getBodyPotential(CARRY, this.colony); // Calculate number of haulers - this.numHaulers = Math.min(Math.ceil(haulingPowerNeeded / haulingPowerPerLifetime), MAX_HAULERS) * 2; - // Request the haulers + this.numHaulers = Math.round(haulingPartsNeeded/haulerCarryParts); + // setup time to request the haulers this.tickToSpawnOn = Game.time + (this.calculateRemainingLifespan() || 0) - this.prespawnAmount; } - + /** + * Calculates how many remaining ticks the power bank has left at current kill rate + */ calculateRemainingLifespan() { if (!this.room) { return undefined; @@ -144,7 +140,8 @@ export class PowerHaulingOverlord extends Overlord { run() { if (Game.time >= this.tickToSpawnOn) { - this.wishlist(this.numHaulers, Setups.transporters.early); + Game.notify('Time to spawn haulers ' + this.pos.roomName); + this.wishlist(this.numHaulers, Setups.transporters.default); } for (let hauler of this.haulers) { if (hauler.isIdle) { From 3cbf89a4ac45999c618fed9cd1e7502b1a9f3180 Mon Sep 17 00:00:00 2001 From: Matthew Roy Date: Mon, 6 May 2019 13:05:43 -0700 Subject: [PATCH 11/20] Adding mining termination --- src/directives/resource/powerMine.ts | 28 ++++++++++-------------- src/overlords/powerMining/PowerDrill.ts | 5 ++++- src/overlords/powerMining/PowerHauler.ts | 14 +++++++----- src/zerg/Zerg.ts | 11 ++++++++++ 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/src/directives/resource/powerMine.ts b/src/directives/resource/powerMine.ts index 1da0e5b31..3790c3d50 100644 --- a/src/directives/resource/powerMine.ts +++ b/src/directives/resource/powerMine.ts @@ -22,14 +22,8 @@ export class DirectivePowerMine extends Directive { static color = COLOR_YELLOW; static secondaryColor = COLOR_RED; - expectedSpawnTime = 150; - const DrillStatus = { - drilling : 0, - AcquiringMinerals: 1, - LoadingLabs : 2, - Synthesizing : 3, - UnloadingLabs : 4, - }; + expectedSpawnTime = 250; + _pathingDistance: number; miningDone: boolean; haulingDone: boolean; haulDirectiveCreated: boolean; @@ -46,14 +40,10 @@ export class DirectivePowerMine extends Directive { spawnMoarOverlords() { if (!this.miningDone) { this.overlords.powerMine = new PowerDrillOverlord(this); - } else { - console.log('Mining is done!'); } - this.spawnHaulers(); - } - - get targetedBy(): string[] { - return Overmind.cache.targets[this.ref]; + if (!this.haulingDone) { + this.spawnHaulers(); + } } get drops(): { [resourceType: string]: Resource[] } { @@ -110,9 +100,15 @@ export class DirectivePowerMine extends Directive { } } + get pathingDistance(): number { + this._pathingDistance = this._pathingDistance || Pathing.distance(this.colony.pos, this.flag.pos); + return this._pathingDistance; + } + spawnHaulers() { log.info("Checking spawning haulers"); - if (this.haulDirectiveCreated || this.room && (!this.powerBank || (this.calculateRemainingLifespan()! < (Pathing.distance(this.colony.pos, this.flag.pos) + this.expectedSpawnTime)))) { + // Begin checking for spawn haulers at 666 estimated ticks before PB destruction + if (this.haulDirectiveCreated || this.room && (!this.powerBank || this.powerBank.hits < 500000)) { Game.notify('Activating spawning haulers for power mining in room ' + this.pos.roomName); this.haulDirectiveCreated = true; this.overlords.powerHaul = new PowerHaulingOverlord(this); diff --git a/src/overlords/powerMining/PowerDrill.ts b/src/overlords/powerMining/PowerDrill.ts index d882b1c0b..eb62896f2 100644 --- a/src/overlords/powerMining/PowerDrill.ts +++ b/src/overlords/powerMining/PowerDrill.ts @@ -85,7 +85,7 @@ export class PowerDrillOverlord extends CombatOverlord { Game.notify("Power bank in " + this.room + " is dead."); drill.say('💀 RIP 💀'); this.directive.setMiningDone(this.name); - let result = drill.suicide(); + let result = drill.retire(); if (result == ERR_BUSY) { drill.spawning } @@ -203,6 +203,9 @@ export class PowerDrillOverlord extends CombatOverlord { 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()); + delete this.directive.overlords[this.name]; this.directive.setMiningDone(this.name); } } diff --git a/src/overlords/powerMining/PowerHauler.ts b/src/overlords/powerMining/PowerHauler.ts index 8c8b71592..02ca4fd7c 100644 --- a/src/overlords/powerMining/PowerHauler.ts +++ b/src/overlords/powerMining/PowerHauler.ts @@ -42,7 +42,7 @@ export class PowerHaulingOverlord extends Overlord { // Calculate number of haulers this.numHaulers = Math.round(haulingPartsNeeded/haulerCarryParts); // setup time to request the haulers - this.tickToSpawnOn = Game.time + (this.calculateRemainingLifespan() || 0) - this.prespawnAmount; + this.tickToSpawnOn = Game.time + (this.directive.calculateRemainingLifespan() || 0) - this.prespawnAmount; } /** @@ -65,13 +65,15 @@ export class PowerHaulingOverlord extends Overlord { } protected handleHauler(hauler: Zerg) { - if (_.sum(hauler.carry) == 0) { + if (_.sum(hauler.carry) == 0 && this.directive.haulingDone) { + hauler.retire(); + } else if (_.sum(hauler.carry) == 0) { // Travel to directive and collect resources if (this.directive.haulingDone) { hauler.say('💀 RIP 💀',true); log.warning(`${hauler.name} is committing suicide as directive is done!`); this.numHaulers = 0; - hauler.suicide(); + hauler.retire(); } if (hauler.inSameRoomAs(this.directive)) { // Pick up drops first @@ -98,7 +100,7 @@ export class PowerHaulingOverlord extends Overlord { } else { hauler.say('💀 RIP 💀',true); log.warning(`${hauler.name} is committing suicide!`); - hauler.suicide(); + hauler.retire(); return; } } @@ -139,9 +141,11 @@ export class PowerHaulingOverlord extends Overlord { } run() { - if (Game.time >= this.tickToSpawnOn) { + if (Game.time >= this.tickToSpawnOn && !this.directive.haulingDone) { Game.notify('Time to spawn haulers ' + this.pos.roomName); this.wishlist(this.numHaulers, Setups.transporters.default); + } else if (this.directive.haulingDone && this.haulers.length == 0) { + this.directive.remove(); } for (let hauler of this.haulers) { if (hauler.isIdle) { diff --git a/src/zerg/Zerg.ts b/src/zerg/Zerg.ts index 26cc22dc6..feea2d555 100644 --- a/src/zerg/Zerg.ts +++ b/src/zerg/Zerg.ts @@ -508,6 +508,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; From 1901aaeb5642d7e46865ccb2161b891be6c8865f Mon Sep 17 00:00:00 2001 From: Matthew Roy Date: Tue, 7 May 2019 14:48:46 -0700 Subject: [PATCH 12/20] Adding termination logic --- src/directives/resource/powerMine.ts | 17 +++-- src/overlords/powerMining/PowerDrill.ts | 88 ++++++++++++------------ src/overlords/powerMining/PowerHauler.ts | 22 +----- 3 files changed, 56 insertions(+), 71 deletions(-) diff --git a/src/directives/resource/powerMine.ts b/src/directives/resource/powerMine.ts index 3790c3d50..07cbb46b3 100644 --- a/src/directives/resource/powerMine.ts +++ b/src/directives/resource/powerMine.ts @@ -22,8 +22,6 @@ export class DirectivePowerMine extends Directive { static color = COLOR_YELLOW; static secondaryColor = COLOR_RED; - expectedSpawnTime = 250; - _pathingDistance: number; miningDone: boolean; haulingDone: boolean; haulDirectiveCreated: boolean; @@ -100,11 +98,6 @@ export class DirectivePowerMine extends Directive { } } - get pathingDistance(): number { - this._pathingDistance = this._pathingDistance || Pathing.distance(this.colony.pos, this.flag.pos); - return this._pathingDistance; - } - spawnHaulers() { log.info("Checking spawning haulers"); // Begin checking for spawn haulers at 666 estimated ticks before PB destruction @@ -122,6 +115,16 @@ export class DirectivePowerMine extends Directive { 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 + */ + isHaulingDone(): boolean { + if (!this.haulingDone && this.miningDone && this.pos.isVisible && this.hasDrops) { + this.haulingDone = true; + } + return this.haulingDone; + } + init(): void { this.alert(`PowerMine directive active`); } diff --git a/src/overlords/powerMining/PowerDrill.ts b/src/overlords/powerMining/PowerDrill.ts index eb62896f2..fa837a29a 100644 --- a/src/overlords/powerMining/PowerDrill.ts +++ b/src/overlords/powerMining/PowerDrill.ts @@ -132,7 +132,7 @@ export class PowerDrillOverlord extends CombatOverlord { Game.notify("Power bank in " + this.room + " is dead."); coolant.say('💀 RIP 💀'); this.isDone = true; - coolant.suicide(); + coolant.retire(); return; } if (coolant.pos.getRangeTo(this.directive.powerBank) > 3) { @@ -140,54 +140,56 @@ export class PowerDrillOverlord extends CombatOverlord { } 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); + 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 { + } + // else if (coolant.pos.getRangeTo(this.directive.powerBank) == 1) { + // coolant.move(Math.round(Math.random()*7) as DirectionConstant); + // } + else { coolant.goTo(_.sample(_.filter(this.drills, drill => drill.hits < drill.hitsMax))); } 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); - // } - } + // 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: CombatZerg, text: string) { if (Game.time % 10 == PowerDrillOverlord.getCreepNameOffset(zerg)) { @@ -205,8 +207,8 @@ export class PowerDrillOverlord extends CombatOverlord { if (this.isDone && !this.directive.miningDone) { this.drills.forEach(drill => drill.retire()); this.coolant.forEach(coolant => coolant.retire()); - delete this.directive.overlords[this.name]; this.directive.setMiningDone(this.name); + delete this.directive.overlords[this.name]; } } diff --git a/src/overlords/powerMining/PowerHauler.ts b/src/overlords/powerMining/PowerHauler.ts index 02ca4fd7c..cc0e606b1 100644 --- a/src/overlords/powerMining/PowerHauler.ts +++ b/src/overlords/powerMining/PowerHauler.ts @@ -3,7 +3,6 @@ import {OverlordPriority} from '../../priorities/priorities_overlords'; import {Zerg} from '../../zerg/Zerg'; import {Tasks} from '../../tasks/Tasks'; import {log} from '../../console/log'; -import {Pathing} from '../../movement/Pathing'; import {Energetics} from '../../logistics/Energetics'; import {profile} from '../../profiler/decorator'; import {Roles, Setups} from '../../creepSetups/setups'; @@ -36,7 +35,7 @@ export class PowerHaulingOverlord extends Overlord { return; } // Spawn haulers to collect ALL the power at the same time. - let haulingPartsNeeded = this.directive.totalResources/50; + 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 @@ -45,25 +44,6 @@ export class PowerHaulingOverlord extends Overlord { this.tickToSpawnOn = Game.time + (this.directive.calculateRemainingLifespan() || 0) - this.prespawnAmount; } - /** - * Calculates how many remaining ticks the power bank has left at current kill rate - */ - calculateRemainingLifespan() { - if (!this.room) { - return undefined; - } else if (this.directive.powerBank == undefined) { - // Power Bank is gone - return 0; - } else { - let tally = calculateFormationStrength(this.directive.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.directive.powerBank.hits / avgDamagePerTick; - } - } - protected handleHauler(hauler: Zerg) { if (_.sum(hauler.carry) == 0 && this.directive.haulingDone) { hauler.retire(); From 1781f15b5e099862ff9f51648f93f57acfe36a44 Mon Sep 17 00:00:00 2001 From: Matthew Roy Date: Wed, 8 May 2019 09:46:09 -0700 Subject: [PATCH 13/20] Moar stuff --- src/directives/resource/powerMine.ts | 3 +-- src/overlords/powerMining/PowerDrill.ts | 6 +++--- src/overlords/powerMining/PowerHauler.ts | 13 +++++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/directives/resource/powerMine.ts b/src/directives/resource/powerMine.ts index 07cbb46b3..f1f76509f 100644 --- a/src/directives/resource/powerMine.ts +++ b/src/directives/resource/powerMine.ts @@ -79,7 +79,6 @@ export class DirectivePowerMine extends Directive { } calculateRemainingLifespan() { - console.log(this._powerBank); if (!this.room) { return undefined; } else if (this.powerBank == undefined) { @@ -119,7 +118,7 @@ export class DirectivePowerMine extends Directive { * This states when all the power has been picked up. Once all power has been picked up and delivered remove the directive */ isHaulingDone(): boolean { - if (!this.haulingDone && this.miningDone && this.pos.isVisible && this.hasDrops) { + if (!this.haulingDone && this.miningDone && this.pos.isVisible && !this.hasDrops) { this.haulingDone = true; } return this.haulingDone; diff --git a/src/overlords/powerMining/PowerDrill.ts b/src/overlords/powerMining/PowerDrill.ts index fa837a29a..79f7560c9 100644 --- a/src/overlords/powerMining/PowerDrill.ts +++ b/src/overlords/powerMining/PowerDrill.ts @@ -191,14 +191,14 @@ export class PowerDrillOverlord extends CombatOverlord { // } // } - static periodicSay(zerg: CombatZerg, text: string) { + static periodicSay(zerg: Zerg, text: string) { if (Game.time % 10 == PowerDrillOverlord.getCreepNameOffset(zerg)) { zerg.say(text, true); } } - static getCreepNameOffset(creep: Zerg) { - return parseInt(creep.name.charAt(creep.name.length-1)) || 0; + static getCreepNameOffset(zerg: Zerg) { + return parseInt(zerg.name.charAt(zerg.name.length-1)) || 0; } run() { diff --git a/src/overlords/powerMining/PowerHauler.ts b/src/overlords/powerMining/PowerHauler.ts index cc0e606b1..16735e74b 100644 --- a/src/overlords/powerMining/PowerHauler.ts +++ b/src/overlords/powerMining/PowerHauler.ts @@ -28,12 +28,6 @@ export class PowerHaulingOverlord extends Overlord { super(directive, 'powerHaul', priority); this.directive = directive; this.haulers = this.zerg(Roles.transport); - } - - init() { - if (!this.colony.storage || _.sum(this.colony.storage.store) > Energetics.settings.storage.total.cap) { - return; - } // 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 @@ -44,6 +38,12 @@ export class PowerHaulingOverlord extends Overlord { 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.haulingDone) { hauler.retire(); @@ -125,6 +125,7 @@ export class PowerHaulingOverlord extends Overlord { Game.notify('Time to spawn haulers ' + this.pos.roomName); this.wishlist(this.numHaulers, Setups.transporters.default); } else if (this.directive.haulingDone && this.haulers.length == 0) { + Game.notify('Deleting Power Mining Directive at ' + this.pos.print); this.directive.remove(); } for (let hauler of this.haulers) { From 9261391c1b645e68c9d46f770ebd7468d38a798a Mon Sep 17 00:00:00 2001 From: Matthew Roy Date: Wed, 8 May 2019 16:30:03 -0700 Subject: [PATCH 14/20] fixing a NPE on case with no drills --- src/overlords/powerMining/PowerDrill.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/overlords/powerMining/PowerDrill.ts b/src/overlords/powerMining/PowerDrill.ts index 79f7560c9..6731fc6b4 100644 --- a/src/overlords/powerMining/PowerDrill.ts +++ b/src/overlords/powerMining/PowerDrill.ts @@ -147,7 +147,8 @@ export class PowerDrillOverlord extends CombatOverlord { // coolant.move(Math.round(Math.random()*7) as DirectionConstant); // } else { - coolant.goTo(_.sample(_.filter(this.drills, drill => drill.hits < drill.hitsMax))); + let drill = _.sample(_.filter(this.drills, drill => drill.hits < drill.hitsMax)); + if (drill) { coolant.goTo(drill); } } coolant.autoHeal(); From fa0a21c58c530eea5ebf18baff3affff163e2016 Mon Sep 17 00:00:00 2001 From: Matthew Roy Date: Thu, 9 May 2019 20:22:36 -0700 Subject: [PATCH 15/20] Fixing termination again --- src/directives/resource/powerMine.ts | 13 +++++++------ src/overlords/powerMining/PowerHauler.ts | 18 ++++++++++++------ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/directives/resource/powerMine.ts b/src/directives/resource/powerMine.ts index f1f76509f..f3fa5bee2 100644 --- a/src/directives/resource/powerMine.ts +++ b/src/directives/resource/powerMine.ts @@ -23,7 +23,7 @@ export class DirectivePowerMine extends Directive { static secondaryColor = COLOR_RED; miningDone: boolean; - haulingDone: boolean; + pickupDone: boolean; haulDirectiveCreated: boolean; private _powerBank: StructurePowerBank | undefined; private _drops: { [resourceType: string]: Resource[] }; @@ -39,7 +39,7 @@ export class DirectivePowerMine extends Directive { if (!this.miningDone) { this.overlords.powerMine = new PowerDrillOverlord(this); } - if (!this.haulingDone) { + if (!this.pickupDone) { this.spawnHaulers(); } } @@ -117,11 +117,12 @@ export class DirectivePowerMine extends Directive { /** * This states when all the power has been picked up. Once all power has been picked up and delivered remove the directive */ - isHaulingDone(): boolean { - if (!this.haulingDone && this.miningDone && this.pos.isVisible && !this.hasDrops) { - this.haulingDone = true; + isPickupDone(): boolean { + if (!this.pickupDone && this.miningDone && this.room && this.pos.isVisible && !this.hasDrops) { + this.pickupDone = true; + Game.notify(`Hauling is done for ${this.room.print} at time ${Game.time}`); } - return this.haulingDone; + return this.pickupDone; } init(): void { diff --git a/src/overlords/powerMining/PowerHauler.ts b/src/overlords/powerMining/PowerHauler.ts index 16735e74b..715d3ca62 100644 --- a/src/overlords/powerMining/PowerHauler.ts +++ b/src/overlords/powerMining/PowerHauler.ts @@ -45,11 +45,11 @@ export class PowerHaulingOverlord extends Overlord { } protected handleHauler(hauler: Zerg) { - if (_.sum(hauler.carry) == 0 && this.directive.haulingDone) { + 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.haulingDone) { + if (this.directive.pickupDone) { hauler.say('💀 RIP 💀',true); log.warning(`${hauler.name} is committing suicide as directive is done!`); this.numHaulers = 0; @@ -121,12 +121,18 @@ export class PowerHaulingOverlord extends Overlord { } run() { - if (Game.time >= this.tickToSpawnOn && !this.directive.haulingDone) { + if (Game.time >= this.tickToSpawnOn && !this.directive.pickupDone) { Game.notify('Time to spawn haulers ' + this.pos.roomName); this.wishlist(this.numHaulers, Setups.transporters.default); - } else if (this.directive.haulingDone && this.haulers.length == 0) { - Game.notify('Deleting Power Mining Directive at ' + this.pos.print); - this.directive.remove(); + } + // Check hauling is done + !this.directive.pickupDone && this.directive.isPickupDone(); + if (this.directive.pickupDone) { + let stillCarryingPower = _.find(this.haulers, hauler => hauler.carry.power != undefined && hauler.carry.power > 0); + if (!stillCarryingPower) { + Game.notify('Deleting Power Mining Directive as no haulers are left carrying ' + this.pos.print); + this.directive.remove(); + } } for (let hauler of this.haulers) { if (hauler.isIdle) { From dcbcdddbccca2905fe9a68ac284f5bc9c2b60b30 Mon Sep 17 00:00:00 2001 From: Matthew Roy Date: Thu, 9 May 2019 21:12:12 -0700 Subject: [PATCH 16/20] Tiny log statement --- src/overlords/powerMining/PowerHauler.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/overlords/powerMining/PowerHauler.ts b/src/overlords/powerMining/PowerHauler.ts index 715d3ca62..d056193df 100644 --- a/src/overlords/powerMining/PowerHauler.ts +++ b/src/overlords/powerMining/PowerHauler.ts @@ -132,6 +132,8 @@ export class PowerHaulingOverlord extends Overlord { if (!stillCarryingPower) { Game.notify('Deleting Power Mining Directive as no haulers are left carrying ' + this.pos.print); this.directive.remove(); + } else { + console.log(`Still carrying power back with ${stillCarryingPower.print}`); } } for (let hauler of this.haulers) { From cb3ddf5924515a54b8b06005d540645019cdb7cf Mon Sep 17 00:00:00 2001 From: Matthew Roy Date: Tue, 14 May 2019 17:27:11 -0700 Subject: [PATCH 17/20] Maybe fixing a "could not access room" error? --- src/directives/resource/powerMine.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/directives/resource/powerMine.ts b/src/directives/resource/powerMine.ts index f3fa5bee2..0b49f664f 100644 --- a/src/directives/resource/powerMine.ts +++ b/src/directives/resource/powerMine.ts @@ -60,7 +60,7 @@ export class DirectivePowerMine extends Directive { } get powerBank(): StructurePowerBank | undefined { - this._powerBank = this._powerBank || this.room != undefined ? this.pos.lookForStructure(STRUCTURE_POWER_BANK) as StructurePowerBank : undefined; + this._powerBank = this._powerBank || this.room ? this.flag.pos.lookForStructure(STRUCTURE_POWER_BANK) as StructurePowerBank : undefined; return this._powerBank; } From 0eba255dc6bc016d6de22a23d7ee7a5c1db1a464 Mon Sep 17 00:00:00 2001 From: Matthew Roy Date: Wed, 15 May 2019 11:11:41 -0700 Subject: [PATCH 18/20] Fixing up logging --- src/directives/resource/powerMine.ts | 9 +++------ src/overlords/powerMining/PowerDrill.ts | 9 ++++----- src/overlords/powerMining/PowerHauler.ts | 6 +++--- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/directives/resource/powerMine.ts b/src/directives/resource/powerMine.ts index 0b49f664f..0c8fca34e 100644 --- a/src/directives/resource/powerMine.ts +++ b/src/directives/resource/powerMine.ts @@ -36,7 +36,7 @@ export class DirectivePowerMine extends Directive { } spawnMoarOverlords() { - if (!this.miningDone) { + if (!this.miningDone && this.powerBank) { this.overlords.powerMine = new PowerDrillOverlord(this); } if (!this.pickupDone) { @@ -74,7 +74,6 @@ export class DirectivePowerMine extends Directive { if (this.pos.isVisible) { this.memory.totalResources = this.powerBank ? this.powerBank.power : this.memory.totalResources; // update total amount remaining } - console.log("Directive total resources = " + this.totalResources); return this.memory.totalResources; } @@ -92,7 +91,6 @@ export class DirectivePowerMine extends Directive { 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); - console.log("Calculating PB remaining lifespan: " + this.powerBank.hits / avgDamagePerTick); return this.powerBank.hits / avgDamagePerTick; } } @@ -101,14 +99,14 @@ export class DirectivePowerMine extends Directive { 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)) { - Game.notify('Activating spawning haulers for power mining in room ' + this.pos.roomName); + 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) { - Game.notify("Setting mining done and removing overlord for power mine in room " + this.room + " at time " + Game.time); + 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; @@ -120,7 +118,6 @@ export class DirectivePowerMine extends Directive { isPickupDone(): boolean { if (!this.pickupDone && this.miningDone && this.room && this.pos.isVisible && !this.hasDrops) { this.pickupDone = true; - Game.notify(`Hauling is done for ${this.room.print} at time ${Game.time}`); } return this.pickupDone; } diff --git a/src/overlords/powerMining/PowerDrill.ts b/src/overlords/powerMining/PowerDrill.ts index 6731fc6b4..5249ca4ff 100644 --- a/src/overlords/powerMining/PowerDrill.ts +++ b/src/overlords/powerMining/PowerDrill.ts @@ -78,19 +78,18 @@ export class PowerDrillOverlord extends CombatOverlord { if (this.directive.powerBank == undefined && !this.directive.haulDirectiveCreated) { if (this.pos.lookFor(LOOK_RESOURCES).length == 0) { // Well shit, we didn't finish mining - Game.notify(`WE FAILED. SORRY CHIEF, COULDN'T FINISHED POWER MINING IN ${this.room} DELETING CREEP at time: ${Game.time}`); + 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 + " is dead."); + 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 } - //this.directive.remove(); - Game.notify("FINISHED POWER MINING IN " + this.room + " DELETING CREEP at time: " + Game.time.toString() + " result: " + result); + log.notify("FINISHED POWER MINING IN " + this.room + " DELETING CREEP at time: " + Game.time.toString() + " result: " + result); return; } } @@ -99,7 +98,7 @@ export class PowerDrillOverlord extends CombatOverlord { // Go to power room if (!this.room || drill.room != this.room || drill.pos.isEdge || !this.directive.powerBank) { // log.debugCreep(drill, `Going to room!`); - Game.notify("Drill is moving to power site in " + this.pos.roomName + "."); + log.notify("Drill is moving to power site in " + this.pos.roomName + "."); drill.goTo(this.pos); return; } diff --git a/src/overlords/powerMining/PowerHauler.ts b/src/overlords/powerMining/PowerHauler.ts index d056193df..14c554d28 100644 --- a/src/overlords/powerMining/PowerHauler.ts +++ b/src/overlords/powerMining/PowerHauler.ts @@ -127,13 +127,13 @@ export class PowerHaulingOverlord extends Overlord { } // Check hauling is done !this.directive.pickupDone && this.directive.isPickupDone(); - if (this.directive.pickupDone) { + if (this.directive.pickupDone && Game.time % 16 == 0) { let stillCarryingPower = _.find(this.haulers, hauler => hauler.carry.power != undefined && hauler.carry.power > 0); if (!stillCarryingPower) { - Game.notify('Deleting Power Mining Directive as no haulers are left carrying ' + this.pos.print); + log.alert(`Deleting Power Mining Directive ${this.directive.print} as no haulers are left carrying power.`); this.directive.remove(); } else { - console.log(`Still carrying power back with ${stillCarryingPower.print}`); + log.debug(`Still carrying power back with ${stillCarryingPower.print} for ${this.directive.print}`); } } for (let hauler of this.haulers) { From 63d7a974c61381bfdad20b7e313a1323bac19106 Mon Sep 17 00:00:00 2001 From: Matthew Roy Date: Wed, 15 May 2019 11:34:17 -0700 Subject: [PATCH 19/20] Small hauler improvement --- src/directives/resource/haul.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/directives/resource/haul.ts b/src/directives/resource/haul.ts index 3caab481b..12fbd9980 100644 --- a/src/directives/resource/haul.ts +++ b/src/directives/resource/haul.ts @@ -21,7 +21,7 @@ export class DirectiveHaul extends Directive { private _store: StoreDefinition; private _drops: { [resourceType: string]: Resource[] }; - private finishAtTime: number; + private _finishAtTime: number; memory: DirectiveHaulMemory; @@ -109,9 +109,9 @@ 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 = Game.time + 800; + this._finishAtTime = this._finishAtTime || (Game.time + 300); } - if (Game.time >= this.finishAtTime) { + if (Game.time >= this._finishAtTime) { this.remove(); } } From 712e61551284e0463aa92191ce4fb4b9219e4efa Mon Sep 17 00:00:00 2001 From: Matthew Roy Date: Mon, 20 May 2019 15:46:52 -0700 Subject: [PATCH 20/20] Currently directive removal isn't occurring properly, seeing if this helps --- src/overlords/powerMining/PowerHauler.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/overlords/powerMining/PowerHauler.ts b/src/overlords/powerMining/PowerHauler.ts index 14c554d28..1a04d4131 100644 --- a/src/overlords/powerMining/PowerHauler.ts +++ b/src/overlords/powerMining/PowerHauler.ts @@ -126,8 +126,7 @@ export class PowerHaulingOverlord extends Overlord { this.wishlist(this.numHaulers, Setups.transporters.default); } // Check hauling is done - !this.directive.pickupDone && this.directive.isPickupDone(); - if (this.directive.pickupDone && Game.time % 16 == 0) { + 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.`);