diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ec6d7496..b2a14e9d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,18 +11,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Added Grafana dashboard support for new stats (in /assets) - Lots of automation improvements to the room planner! Now once you create a room plan at RCL 1, you can dynamically add and remove outposts without needing to open/close the planner. - Road planning is now done with the RoadPlanner, which is instantiated from a room planner, and provides a much higher degree of automation: - - Road planning is continuously recalculated every 1000 ticks with a heuristic that encourages road merging at minimal expense to path length + - Road network is continuously recalculated every 1000 ticks with a heuristic that encourages road merging at minimal expense to path length - Roads which become deprecated (no longer in the optimal road plan) are allowed to decay - Road routing hints (white/white flags) no longer have any effect. (The new routing algorithm uses a variant of this idea to merge roads more intelligently.) - - UpgradeSites now automatically calculate their optimal location instead of needing to be placed manually - - MiningSites at outposts now automatically build container outputs without you needing to open and close the room planner for their colony + - UpgradeSites now automatically calculate input location and no longer need to be placed in room planner + - MiningSites at outposts now automatically build container outputs without you needing to reboot room planner + - MiningSites in rooms now automatically upgrade their container to a link if necessary - Hauling directives and overlords for hauling large amounts of resources long distances (e.g. scavenging from abandoned storage) -- Finished integrating LogisticsRequestDirectives: these act as requestor or provider objects at a position - - Requestor: requests energy to be dropped at a positiion - - Provider: resources dropped at position or in a tombstone to be collected +- Finished coding LogisticsRequestDirectives: these flags act as requestor or provider targets and have a `store` property + - `provider == false`: requests energy to be dropped at a positiion + - `provider == true`: resources dropped at position or in a tombstone to be collected - Preliminary contract module for making deals between players -- Added `Energetics` module, which will make high-level decisions based on energy distributions -- Colonies now have a `lowPowerMode` operational state, which scales back production of miners and transporters at RCL8 with full storage/terminal +- `Energetics` module, which will make high-level decisions based on energy distributions + - Colonies now have a `lowPowerMode` operational state, which scales back production of miners and transporters at RCL8 with full storage/terminal ### Changed - Transporters now use a single-sided greedy selection at RCL<4, since stable matching only works well when the transporter carry is a significant fraction of the logisticsRequest target's capacity @@ -41,6 +42,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Bugfixes with `TaskDrop`, `TaskGoTo`, `TaskGoToRoom` - Even more bugfixes with `TaskDrop` - Fixed bug (hopefully) with creeps not approaching to range 1 in `TaskAttack` and `TaskHeal` +- Fixed bugs where claimers and reservers would get stuck trying to sign a room that was perma-signed by Screeps as a future newbie zone +- Fixed a bug where upgradeSites could misidentify their input in some pathological room layouts ## Overmind [0.2.1] - 2018.3.22 ### Added diff --git a/README.md b/README.md index 637c98a20..72b2d62d7 100644 --- a/README.md +++ b/README.md @@ -6,28 +6,24 @@ - See the [changelog](https://github.com/bencbartlett/Overmind/blob/master/CHANGELOG.md) for patch notes. - Documentation is available in the [wiki](https://github.com/bencbartlett/Overmind/wiki). +- Join the discussion in the [#overmind](https://screeps.slack.com/messages/overmind) Slack channel! - Development roadmap can be seen [here](https://github.com/bencbartlett/Overmind/projects/1). +- Find me in game [here](https://screeps.com/a/#!/profile/Muon). --- ## What is Screeps? Screeps is an [MMO strategy game for programmers](https://screeps.com/). The core objective is to expand your colony to gather more resources. The game is played on a massive shared server which runs 24/7, even when you aren't actively playing. To control your units, you program their behavior in JavaScript or any other transpiled language. This is the AI I have been developing for Screeps, themed loosely around the [Zerg's swarm intelligence](http://starcraft.wikia.com/wiki/Overlord) from Starcraft. Creeps belong to [Colonies](https://github.com/bencbartlett/Overmind/blob/master/src/Colony.ts), which have several [Hive Clusters](https://github.com/bencbartlett/Overmind/blob/master/src/hiveClusters/HiveCluster.ts). Creep actions for a given process are orchestrated by [Overlords](https://github.com/bencbartlett/Overmind/blob/master/src/overlords/Overlord.ts). The colony [Overseer](https://github.com/bencbartlett/Overmind/blob/master/src/Overseer.ts) places [Directives](https://github.com/bencbartlett/Overmind/blob/master/src/directives/Directive.ts) to adapt to stimuli. -### We're on slack! -Found something you like, hate, or find confusing? Join the discussion on Slack in the [#overmind](https://screeps.slack.com/messages/overmind) channel! - -### Find me in game! (username: Muon) -I've recently respawned to shard2 in the [`EXS4X` sector](https://screeps.com/a/#!/map/shard2?pos=5.826,44.939). - ## Using Overmind as your AI If you're new to Screeps, I wouldn't recommend using Overmind as your AI: most of the fun of the game is programming your own AI and watching your little ant farm run! However, I've tried to make the codebase readable and well-documented, so feel free to fork the project or use it as inspiration when writing your AI. If you do want to use Overmind as-is, the [latest release](https://github.com/bencbartlett/Overmind/releases) should work right out of the box. However, if you find something broken, please [submit an issue](https://github.com/bencbartlett/Overmind/issues/new) and I'll try to fix it. ### Out of the box -If you just want to run Overmind without modification, you can copy the compiled `main.js` file attached to the [latest release](https://github.com/bencbartlett/Overmind/releases) into your script. +If you just want to run Overmind without modification, you can copy the compiled `main.js` file attached to the [latest release](https://github.com/bencbartlett/Overmind/releases) into your script. Please note that Overmind is not (yet) fully automated; refer to the [Overmind wiki](https://github.com/bencbartlett/Overmind/wiki) for how to run the bot. -### Full installation +### Installing from source If you want to install the full codebase, download or clone the repository, then navigate to the Overmind root directory and run: ```npm install``` @@ -38,11 +34,11 @@ To compile and deploy the codebase, create a `screeps.json` file from the [examp - Compile and deploy to private server: `npm run push-pserver` - Compile without deploying: `rollup -c` -The deployment scripts are based on [`screeps-typescript-starter`](https://github.com/screepers/screeps-typescript-starter); for additional help, refer to their [GitBook](https://screepers.gitbooks.io/screeps-typescript-starter/getting-started/deploying.html) or [submit an issue](https://github.com/bencbartlett/Overmind/issues/new). +The deployment scripts are based on [`screeps-typescript-starter`](https://github.com/screepers/screeps-typescript-starter); for additional help, refer to their [GitBook](https://screepers.gitbooks.io/screeps-typescript-starter/getting-started/deploying.html) or [submit an issue](https://github.com/bencbartlett/Overmind/issues/new). Please note that while the latest release of Overmind should always be stable, the latest commit may contain unstable features. # Design overview -Check out the [Overmind wiki](https://github.com/bencbartlett/Overmind/wiki) for in-depth explanations of parts of the design of the AI. (Click the image below to see a higher-resolution version.) +Check out the [Overmind wiki](https://github.com/bencbartlett/Overmind/wiki) for in-depth explanations of parts of the design of the AI. (Click the diagram below to see a higher-resolution version.) ![[AI structural schematic](/assets/AIdiagram.png)](https://raw.githubusercontent.com/bencbartlett/Overmind/master/assets/AIdiagram.png) diff --git a/src/Overmind.ts b/src/Overmind.ts index bda9ed310..74d671246 100644 --- a/src/Overmind.ts +++ b/src/Overmind.ts @@ -75,14 +75,6 @@ export default class Overmind implements IOvermind { let outpostName = flag.pos.roomName; this.colonyMap[outpostName] = colonyName; // Create an association between room and colony name colonyOutposts[colonyName].push(outpostName); - - // // TODO: handle observer logic - // let thisRoom = Game.rooms[roomName]; - // if (thisRoom) { - // thisRoom.memory.colony = colonyName; - // } else { - // this.invisibleRooms.push(roomName); // register room as invisible to be handled by observer - // } } } @@ -129,39 +121,6 @@ export default class Overmind implements IOvermind { } } - // private handleObservers(): void { - // // Shuffle list of invisible rooms to allow different ones to be observed each tick - // this.invisibleRooms = _.shuffle(this.invisibleRooms); - // // Generate a map of available observers - // let availableObservers: { [colonyName: string]: StructureObserver } = {}; - // for (let colonyName in this.Colonies) { - // let colony = this.Colonies[colonyName]; - // if (colony.observer) { - // availableObservers[colonyName] = colony.observer; - // } - // } - // // Loop until you run out of rooms to observe or observers - // while (this.invisibleRooms.length > 0 && _.size(availableObservers) > 0) { - // let roomName = this.invisibleRooms.shift(); - // if (roomName) { - // let colonyName = this.colonyMap[roomName]; - // if (availableObservers[colonyName]) { - // availableObservers[colonyName].observeRoom(roomName); - // delete availableObservers[colonyName]; - // } else { - // let observerRooms = _.keys(availableObservers); - // let inRangeRoom = _.find(observerRooms, - // oRoomName => Game.map.getRoomLinearDistance(oRoomName, roomName!) - // <= OBSERVER_RANGE); - // if (inRangeRoom) { - // availableObservers[inRangeRoom].observeRoom(roomName); - // delete availableObservers[colonyName]; - // } - // } - // } - // } - // } - /* Global instantiation of Overmind object; run once every global refresh */ build(): void { this.cache.build(); @@ -179,12 +138,6 @@ export default class Overmind implements IOvermind { /* Intialize everything in pre-init phase of main loop. Does not call colony.init(). */ init(): void { - // // The order in which these functions are called is important - // this.verifyMemory(); - // this.registerColonies(); // 2: Initialize each colony. Build() is called in main.ts - // this.registerCreeps(); // 4: Wrap all the creeps and assign to respective colonies - // // this.buildColonies(); // 5: Build the colony, instantiating virtual components - // this.registerDirectives(); // 5: Wrap all the directives and assign to respective overlords for (let colonyName in this.Colonies) { let start = Game.cpu.getUsed(); this.Colonies[colonyName].init(); @@ -220,5 +173,6 @@ export default class Overmind implements IOvermind { Stats.log(`cpu.usage.${colonyName}.visuals`, Game.cpu.getUsed() - start); } } + }; diff --git a/src/hiveClusters/hiveCluster_miningSite.ts b/src/hiveClusters/hiveCluster_miningSite.ts index ee7ba32af..404f76d38 100644 --- a/src/hiveClusters/hiveCluster_miningSite.ts +++ b/src/hiveClusters/hiveCluster_miningSite.ts @@ -118,8 +118,24 @@ export class MiningSite extends HiveCluster { this.registerOutputRequests(); } - /* Calculate where the output will be built for this site */ - private calculateOutpotPos(): RoomPosition | undefined { + get outputPos(): RoomPosition | undefined { + if (this.output) { + return this.output.pos; + } else if (this.outputConstructionSite) { + return this.outputConstructionSite.pos; + } else { + if (!this._outputPos) { + this._outputPos = this.calculateContainerPos(); + if (!this._outputPos) { + log.warning(`Mining site at ${this.pos.print}: cannot determine outputPos!`); + } + } + return this._outputPos; + } + } + + /* Calculate where the container output will be built for this site */ + private calculateContainerPos(): RoomPosition | undefined { let originPos: RoomPosition | undefined = undefined; if (this.colony.storage) { originPos = this.colony.storage.pos; @@ -132,41 +148,87 @@ export class MiningSite extends HiveCluster { } } - get outputPos(): RoomPosition | undefined { - if (this.output) { - return this.output.pos; - } else if (this.outputConstructionSite) { - return this.outputConstructionSite.pos; - } else { - if (!this._outputPos) { - this._outputPos = this.calculateOutpotPos(); - if (!this._outputPos) { - log.warning(`Mining site at ${this.pos.print}: cannot determine outputPos!`); + /* Calculate where the link will be built */ + private calculateLinkPos(): RoomPosition | undefined { + let originPos: RoomPosition | undefined = undefined; + if (this.colony.storage) { + originPos = this.colony.storage.pos; + } else if (this.colony.roomPlanner.storagePos) { + originPos = this.colony.roomPlanner.storagePos; + } + if (originPos) { + let path = Pathing.findShortestPath(this.pos, originPos).path; + for (let pos of path) { + if (this.source.pos.getRangeTo(pos) == 2) { + return pos; } } - return this._outputPos; } } /* Build a container output at the optimal location */ - private buildContainerIfMissing(): void { + private buildOutputIfNeeded(): void { if (!this.output && !this.outputConstructionSite) { let buildHere = this.outputPos; if (buildHere) { - let result = buildHere.createConstructionSite(STRUCTURE_CONTAINER); + // Build a link if one is available + let structureType: StructureConstant = STRUCTURE_CONTAINER; + if (this.room == this.colony.room) { + let numLinks = this.colony.links.length + + _.filter(this.colony.constructionSites, + site => site.structureType == STRUCTURE_LINK).length; + let numLinksAllowed = CONTROLLER_STRUCTURES.link[this.colony.level]; + if (numLinks < numLinksAllowed && + this.colony.hatchery && this.colony.hatchery.link && + this.colony.commandCenter && this.colony.commandCenter.link) { + structureType = STRUCTURE_LINK; + buildHere = this.calculateLinkPos()!; // link pos will definitely be defined if buildHere is defined + } + } + let result = buildHere.createConstructionSite(structureType); if (result == OK) { return; } else { - log.warning(`Mining site at ${this.pos.print}: cannot build output! Result: ${result}`); + log.error(`Mining site at ${this.pos.print}: cannot build output! Result: ${result}`); } } } } + private destroyContainerIfNeeded(): void { + let storage = this.colony.storage; + let replaceContainerAboveDistance = 10; + // Possibly replace if you are in colony room, have a container output and are sufficiently far from storage + if (this.room == this.colony.room && this.output && this.output instanceof StructureContainer && + storage && Pathing.distance(this.output.pos, storage.pos) > replaceContainerAboveDistance) { + let numLinks = this.colony.links.length + + _.filter(this.colony.constructionSites, s => s.structureType == STRUCTURE_LINK).length; + let numLinksAllowed = CONTROLLER_STRUCTURES.link[this.colony.level]; + let miningSitesInRoom = _.filter(_.values(this.colony.miningSites), (site: MiningSite) => + site.pos.roomName == this.colony.pos.roomName) as MiningSite[]; + let fartherSites = _.filter(miningSitesInRoom, site => + Pathing.distance(storage!.pos, site.pos) > Pathing.distance(storage!.pos, this.pos)); + let everyFartherSiteHasLink = _.every(fartherSites, site => site.output instanceof StructureLink); + // Destroy the output if 1) more links can be built, 2) every farther site has a link and + // 3) hatchery and commandCenter both have links + if (numLinksAllowed - numLinks > 0 && everyFartherSiteHasLink && + this.colony.hatchery && this.colony.hatchery.link && + this.colony.commandCenter && this.colony.commandCenter.link) { + this.output.destroy(); + } + } + + }; + /* Run tasks: make output construciton site if needed; build and maintain the output structure */ run(): void { - if (Game.time % 10 == 5) { - this.buildContainerIfMissing(); + let rebuildOnTick = 5; + let rebuildFrequency = 10; + if (Game.time % rebuildFrequency == rebuildOnTick - 1) { + this.destroyContainerIfNeeded(); + } + if (Game.time % rebuildFrequency == rebuildOnTick) { + this.buildOutputIfNeeded(); } } diff --git a/src/hiveClusters/hiveCluster_upgradeSite.ts b/src/hiveClusters/hiveCluster_upgradeSite.ts index e735b1082..153698872 100644 --- a/src/hiveClusters/hiveCluster_upgradeSite.ts +++ b/src/hiveClusters/hiveCluster_upgradeSite.ts @@ -8,9 +8,11 @@ import {Mem} from '../memory'; import {Visualizer} from '../visuals/Visualizer'; import {log} from '../lib/logger/log'; import {WorkerSetup} from '../creepSetup/defaultSetups'; +import {Stats} from '../stats/stats'; interface UpgradeSiteMemory { input?: { pos: protoPos, tick: number }; + stats: { downtime: number }; } @profile @@ -56,7 +58,9 @@ export class UpgradeSite extends HiveCluster { this.energyPerTick = (_.sum(_.map(this.overlord.upgraders, upgrader => upgrader.getActiveBodyparts(WORK))) + _.sum(_.map(_.filter(this.colony.getCreepsByRole(WorkerSetup.role), worker => worker.pos.inRangeTo((this.input || this).pos, 2)), - worker => worker.getActiveBodyparts(WORK)))) * UPGRADE_CONTROLLER_POWER; + worker => worker.getActiveBodyparts(WORK)))); + // Compute stats + this.stats(); } get memory(): UpgradeSiteMemory { @@ -159,6 +163,18 @@ export class UpgradeSite extends HiveCluster { } } + private stats() { + let defaults = { + downtime: 0, + }; + if (!this.memory.stats) this.memory.stats = defaults; + _.defaults(this.memory.stats, defaults); + // Compute downtime + this.memory.stats.downtime = (this.memory.stats.downtime * (CREEP_LIFE_TIME - 1) + + (this.input ? +this.input.isEmpty : 0)) / CREEP_LIFE_TIME; + Stats.log(`colonies.${this.colony.name}.upgradeSite.downtime`, this.memory.stats.downtime); + } + run(): void { if (Game.time % 25 == 7) { this.buildContainerIfMissing(); @@ -166,14 +182,15 @@ export class UpgradeSite extends HiveCluster { } visuals() { + let info = []; if (this.controller.level != 8) { let progress = `${Math.floor(this.controller.progress / 1000)}K`; let progressTotal = `${Math.floor(this.controller.progressTotal / 1000)}K`; let percent = `${Math.floor(100 * this.controller.progress / this.controller.progressTotal)}`; - let info = [ - `Progress: ${progress}/${progressTotal} (${percent}%)`, - ]; - Visualizer.showInfo(info, this); + info.push(`Progress: ${progress}/${progressTotal} (${percent}%)`); + } + info.push(`Downtime: ${this.memory.stats.downtime.toPercent()}`); + Visualizer.showInfo(info, this); } } diff --git a/src/lib/logger/log.ts b/src/lib/logger/log.ts index 4ccd6828c..a6aa8b154 100644 --- a/src/lib/logger/log.ts +++ b/src/lib/logger/log.ts @@ -212,7 +212,7 @@ export class Log { out.push(color('WARNING', 'orange')); break; case LogLevels.ALERT: - out.push(color('ALERT', 'yellow')); + out.push(color('ALERT ', 'yellow')); break; case LogLevels.INFO: out.push(color('INFO ', 'green')); diff --git a/src/lib/traveler/Traveler.ts b/src/lib/traveler/Traveler.ts index 354db187d..99144cc2c 100644 --- a/src/lib/traveler/Traveler.ts +++ b/src/lib/traveler/Traveler.ts @@ -3,6 +3,7 @@ * Example: var Traveler = require('Traveler.js'); */ import {profile} from '../../profiler/decorator'; +import {log} from '../logger/log'; @profile export class Traveler { @@ -119,10 +120,10 @@ export class Traveler { let cpuUsed = Game.cpu.getUsed() - cpu; state.cpu = _.round(cpuUsed + state.cpu); - if (state.cpu > REPORT_CPU_THRESHOLD) { + if (Game.time % 10 == 0 && state.cpu > REPORT_CPU_THRESHOLD) { // see note at end of file for more info on this - console.log(`TRAVELER: heavy cpu use: ${creep.name}, cpu: ${state.cpu} origin: ${ - creep.pos}, dest: ${destination}`); + log.alert(`TRAVELER: heavy cpu use: ${creep.name}, cpu: ${state.cpu} origin: ${ + creep.pos.print}, dest: ${destination.print}`); } let color = 'orange'; @@ -358,11 +359,11 @@ export class Traveler { // can happen for situations where the creep would have to take an uncommonly indirect path // options.allowedRooms and options.routeCallback can also be used to handle this situation if (roomDistance <= 2) { - console.log(`TRAVELER: path failed without findroute, trying with options.useFindRoute = true`); - console.log(`from: ${origin}, destination: ${destination}`); + log.warning(`TRAVELER: path failed without findroute. Origin: ${origin.print}, ` + + `destination: ${destination.print}. Trying again with options.useFindRoute = true...`); options.useFindRoute = true; ret = this.findTravelPath(origin, destination, options); - console.log(`TRAVELER: second attempt was ${ret.incomplete ? 'not ' : ''}successful`); + log.warning(`TRAVELER: second attempt was ${ret.incomplete ? 'not ' : ''}successful`); return ret; } diff --git a/src/logistics/TerminalNetwork.ts b/src/logistics/TerminalNetwork.ts index d607fc3da..116915681 100644 --- a/src/logistics/TerminalNetwork.ts +++ b/src/logistics/TerminalNetwork.ts @@ -178,8 +178,8 @@ export class TerminalNetwork implements ITerminalNetwork { amount = Energetics.settings.terminal.energy.sendSize): number { let cost = Game.market.calcTransactionCost(amount, sender.room.name, receiver.room.name); let response = sender.send(RESOURCE_ENERGY, amount, receiver.room.name); - log.info(`Sent ${amount} energy from ${sender.room.name} to ` + - `${receiver.room.name}. Fee: ${cost}. Response: ${response}`); + log.info(`Sent ${amount} energy from ${sender.room.print} to ` + + `${receiver.room.print}. Fee: ${cost}. Response: ${response}`); if (response == OK) { TerminalNetwork.logTransfer(RESOURCE_ENERGY, amount, sender.room.name, receiver.room.name); this.alreadyReceived.push(receiver); diff --git a/src/overlords/combat/overlord_destroyer.ts b/src/overlords/combat/overlord_destroyer.ts index 2eb640bd8..4053e5750 100644 --- a/src/overlords/combat/overlord_destroyer.ts +++ b/src/overlords/combat/overlord_destroyer.ts @@ -4,7 +4,6 @@ import {Zerg} from '../../Zerg'; import {OverlordPriority} from '../priorities_overlords'; import {DirectiveTargetSiege} from '../../directives/targeting/directive_target_siege'; import {CombatOverlord} from './CombatOverlord'; -import {boostResources} from '../../maps/map_resources'; import {DirectiveDestroy} from '../../directives/combat/directive_destroy'; import {CreepSetup} from '../../creepSetup/CreepSetup'; import {profile} from '../../profiler/decorator'; @@ -36,9 +35,10 @@ export class DestroyerOverlord extends CombatOverlord { attackers: Zerg[]; healers: Zerg[]; - settings: { - retreatHitsPercent: number, - reengageHitsPercent: number, + + static settings = { + retreatHitsPercent : 0.85, + reengageHitsPercent: 0.95, }; constructor(directive: DirectiveDestroy, priority = OverlordPriority.offense.destroy) { @@ -46,18 +46,14 @@ export class DestroyerOverlord extends CombatOverlord { this.attackers = this.creeps('attacker'); this.healers = this.creeps('healer'); // Comment out boost lines if you don't want to spawn boosted attackers/healers - this.boosts.attacker = [ - boostResources.attack[3], - boostResources.tough[3], - ]; - this.boosts.healer = [ - boostResources.heal[3], - boostResources.tough[3], - ]; - this.settings = { - retreatHitsPercent : 0.85, - reengageHitsPercent: 0.95, - }; + // this.boosts.attacker = [ + // boostResources.attack[3], + // boostResources.tough[3], + // ]; + // this.boosts.healer = [ + // boostResources.heal[3], + // boostResources.tough[3], + // ]; } private findTarget(attacker: Zerg): Creep | Structure | undefined { @@ -81,9 +77,10 @@ export class DestroyerOverlord extends CombatOverlord { private retreatActions(attacker: Zerg, healer: Zerg): void { this.pairwiseMove(healer, attacker, this.fallback); - if (attacker.hits > this.settings.reengageHitsPercent * attacker.hits && - healer.hits > this.settings.reengageHitsPercent * healer.hits) { + if (attacker.hits > DestroyerOverlord.settings.reengageHitsPercent * attacker.hits && + healer.hits > DestroyerOverlord.settings.reengageHitsPercent * healer.hits) { attacker.memory.retreating = false; + // TODO: never actually do retreat actions??? } } @@ -109,8 +106,8 @@ export class DestroyerOverlord extends CombatOverlord { } } else { // have an active healer // Handle retreating actions - if (attacker.hits < this.settings.retreatHitsPercent * attacker.hitsMax || - healer.hits < this.settings.retreatHitsPercent * healer.hitsMax) { + if (attacker.hits < DestroyerOverlord.settings.retreatHitsPercent * attacker.hitsMax || + healer.hits < DestroyerOverlord.settings.retreatHitsPercent * healer.hitsMax) { attacker.memory.retreating = true; } if (attacker.memory.retreating) { diff --git a/src/overlords/core/overlord_mine.ts b/src/overlords/core/overlord_mine.ts index 0a78992fd..94dd2c85f 100644 --- a/src/overlords/core/overlord_mine.ts +++ b/src/overlords/core/overlord_mine.ts @@ -60,6 +60,10 @@ export class MiningOverlord extends Overlord { else { if (this.miningSite.outputConstructionSite) { miner.task = Tasks.build(this.miningSite.outputConstructionSite); + if (miner.pos.isEqualTo(this.miningSite.outputConstructionSite.pos)) { + // Move off of the contructionSite (link sites won't build) + miner.travelTo(this.colony.controller); + } } } } else { diff --git a/src/overlords/core/overlord_transport.ts b/src/overlords/core/overlord_transport.ts index a5eeada00..ae6190eae 100644 --- a/src/overlords/core/overlord_transport.ts +++ b/src/overlords/core/overlord_transport.ts @@ -24,6 +24,8 @@ export class TransportOverlord extends Overlord { private neededTransportPower(): number { let transportPower = 0; + let scaling = 2; // Average round-trip distance you have to carry resources + // Add contributions to transport power from hauling energy from mining sites let dropoffLocation: RoomPosition; if (this.colony.commandCenter) { dropoffLocation = this.colony.commandCenter.pos; @@ -37,7 +39,6 @@ export class TransportOverlord extends Overlord { if (site.output instanceof StructureContainer && site.overlord.miners.length > 0) { // Only count sites which have a container output and which have at least one miner present // (this helps in difficult "rebooting" situations) - let scaling = 1.75; // Average round-trip distance you have to carry resources transportPower += site.energyPerTick * (scaling * Pathing.distance(site.pos, dropoffLocation)); } } @@ -45,6 +46,10 @@ export class TransportOverlord extends Overlord { // Reduce needed transporters when colony is in low power mode transportPower *= 0.5; } + // Add transport power needed to move to upgradeSite + transportPower += this.colony.upgradeSite.upgradePowerNeeded * scaling * + Pathing.distance(dropoffLocation, (this.colony.upgradeSite.input || + this.colony.upgradeSite).pos); return transportPower / CARRY_CAPACITY; } @@ -69,14 +74,15 @@ export class TransportOverlord extends Overlord { if (request.target instanceof DirectiveLogisticsRequest) { task = Tasks.drop(request.target); } else { - task = Tasks.transfer(request.target); + task = Tasks.transfer(request.target, request.resourceType); } // TODO: buffer with parent system is causing bugs if (bestChoice.targetRef != request.target.ref) { // If we need to go to a buffer first to get more stuff let buffer = deref(bestChoice.targetRef) as BufferTarget; - let withdrawAmount = Math.min(buffer.store[request.resourceType] || 0, amount); - task = task.fork(Tasks.withdraw(buffer, request.resourceType, amount)); + let withdrawAmount = Math.min(buffer.store[request.resourceType] || 0, + transporter.carryCapacity - _.sum(transporter.carry), amount); + task = task.fork(Tasks.withdraw(buffer, request.resourceType, withdrawAmount)); } } else if (amount < 0) { // store needs withdrawal if (request.target instanceof DirectiveLogisticsRequest) { @@ -86,7 +92,7 @@ export class TransportOverlord extends Overlord { task = Tasks.pickup(resource); } } else { - task = Tasks.withdraw(request.target); + task = Tasks.withdraw(request.target, request.resourceType); } if (task && bestChoice.targetRef != request.target.ref) { // If we need to go to a buffer first to deposit stuff diff --git a/src/overlords/core/overlord_upgrade.ts b/src/overlords/core/overlord_upgrade.ts index 941a88b3e..f3b28cfea 100644 --- a/src/overlords/core/overlord_upgrade.ts +++ b/src/overlords/core/overlord_upgrade.ts @@ -5,6 +5,8 @@ import {Zerg} from '../../Zerg'; import {Tasks} from '../../tasks/Tasks'; import {OverlordPriority} from '../priorities_overlords'; import {profile} from '../../profiler/decorator'; +import minBy from 'lodash.minby'; +import {Pathing} from '../../pathing/pathing'; @profile export class UpgradingOverlord extends Overlord { @@ -54,7 +56,14 @@ export class UpgradingOverlord extends Overlord { if (this.upgradeSite.input && this.upgradeSite.input.energy > 0) { upgrader.task = Tasks.withdraw(this.upgradeSite.input); } else { - let target = upgrader.pos.findClosestByRange(_.filter(this.room.storageUnits, s => s.energy > 0)); + let rechargeTargets = _.filter(_.compact([this.colony.storage!, + this.colony.terminal!, + this.colony.upgradeSite.input!, + ..._.map(this.colony.miningSites, site => site.output!), + ...this.colony.tombstones]), + s => s.energy > 0); + let target = minBy(rechargeTargets, (s: RoomObject) => Pathing.distance(this.upgradeSite.pos, s.pos)); + // let target = upgrader.pos.findClosestByRange(_.filter(this.room.storageUnits, s => s.energy > 0)); if (target) upgrader.task = Tasks.withdraw(target); } } diff --git a/src/pathing/pathing.ts b/src/pathing/pathing.ts index 78310ac3a..556f63dfe 100644 --- a/src/pathing/pathing.ts +++ b/src/pathing/pathing.ts @@ -109,7 +109,7 @@ export class Pathing { allowSK : true, }); let ret = Traveler.findTravelPath(startPos, endPos, options); - if (ret.incomplete) log.info(`Incomplete travel path from ${startPos} to ${endPos}!`); + if (ret.incomplete) log.alert(`Incomplete travel path from ${startPos.print} to ${endPos.print}!`); return ret; } @@ -137,7 +137,7 @@ export class Pathing { range : 1, }); let ret = Traveler.findTravelPath(startPos, endPos, options); - if (ret.incomplete) log.info(`Incomplete travel path from ${startPos} to ${endPos}!`); + if (ret.incomplete) log.alert(`Incomplete travel path from ${startPos.print} to ${endPos.print}!`); return ret; } diff --git a/src/roomPlanner/RoomPlanner.ts b/src/roomPlanner/RoomPlanner.ts index 9546937ce..32a570739 100644 --- a/src/roomPlanner/RoomPlanner.ts +++ b/src/roomPlanner/RoomPlanner.ts @@ -346,8 +346,8 @@ export class RoomPlanner { if (count > 0 && RoomPlanner.shouldBuild(structureType, pos)) { let ret = pos.createConstructionSite(structureType); if (ret != OK) { - log.error(`${this.colony.name}: couldn't create construction site! ` + - `pos: ${pos.x} ${pos.y} ${pos.roomName}, type: ${structureType}, Result: ${ret}`); + log.error(`${this.colony.name}: couldn't create construction site of type` + + `"${structureType}" at ${pos.print}. Result: ${ret}`); } else { count--; } diff --git a/src/sandbox.ts b/src/sandbox.ts index 98ba0f045..26d4c0f3b 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -3,9 +3,8 @@ import {log} from './lib/logger/log'; export function sandbox() { - try { // Test code goes here - // let array = [{'n':1}, {'n':2}] - // console.log(minBy(array, (thing: any)=>thing.n).n) + try { + // Test code goes here } catch (e) { log.error(e); } diff --git a/src/settings/config.ts b/src/settings/config.ts index 83a49bdbb..87604365b 100644 --- a/src/settings/config.ts +++ b/src/settings/config.ts @@ -23,7 +23,7 @@ export const LOG_PRINT_LINES: boolean = false; /** * Load source maps and resolve source lines back to typeascript. */ -export const LOG_LOAD_SOURCE_MAP: boolean = false; +export const LOG_LOAD_SOURCE_MAP: boolean = true; /** * Maximum padding for source links (for aligning log output).