diff --git a/CHANGELOG.md b/CHANGELOG.md index bb55f3e54..337ba7c52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,8 +21,14 @@ All notable changes to this project will be documented in this file. The format - New `overload` stat tracks the rolling average of ticks where the hatchery is idle, wants to spawn something, but is unable to because it is being loaded - RoadPlanner improvements: - New routing algorithm allows for tunnel placement, although this will be relatively rare due to very high maintenance costs + - Changes to `Colony.destinations` ensures more determinism when recomputing road networks; you should notice a decrease in roads which become deprecated as levels grow or outposts are added - `RoadPlanner.roadCoverage` property tracks paving completion throughout a colony; transporter bodies will now use this stat rather than colony level to determine when to switch between setups with 1:1 and 2:1 carry:move ratios -- `CombatIntel.isEdgeDancing` uses new info tracked in `RoomIntel` to determine if a likely tower drain attack is occurring; towers will adjust their firing patterns accordingly. +- New information tracked with `RoomIntel`: + - (Approximate) harvesting data and rolling averages of energy/tick over 10k, 100k, and 1M ticks + - Casualty data, with effective energy costs and rolling average of cost/tick over 10k, 100k, 1M ticks + - Creep occupancy data over the last 25 ticks (computed only in owned rooms) + - Safety data, tracking consecutive safe, unsafe ticks and rolling average of safety over last 1k and 10k ticks +- `CombatIntel.isEdgeDancing` uses creep occupancy data tracked in `RoomIntel` to determine if a likely tower drain attack is occurring; towers will adjust their firing patterns accordingly. ### Changed @@ -34,6 +40,7 @@ All notable changes to this project will be documented in this file. The format - Workers will upgrade controllers sooner at higher levels and will spawn when a downgrade is imminent - Non-stationary managers have fewer move parts in bunker-type colonies - Reservers allow for a lower reservation buffer and will use the cached reservation info from `RoomIntel` if vision is unavailable +- Queens now spawn with 1:1 move:carry ratios until a storage is built ### Fixed @@ -202,6 +209,8 @@ Important notes as of this release: - Deprecated `DirectiveLogisticsRequest` - Removed `lodash.minBy` dependencies to reduce compiled codebase size + + ## Overmind [0.4.1] - 2018.6.15 This patch makes Abathur a little smarter in which reactions he chooses and fixes some bugs accidentally introduced by changes in the last release. @@ -220,6 +229,7 @@ This patch makes Abathur a little smarter in which reactions he chooses and fixe - Fixed a bug in LogisticsNetwork where predicted carry amounts could exceed carry capacity + ## Overmind [0.4.0]: "require('more-minerals')" - 2018.6.14 *"We require more minerals."* Well finally, we now have them! This long-overdue release (the largest update to date by additions/deletions) adds fully automatic mineral mining, processing, trading, and boosting capabilities to Overmind! @@ -297,7 +307,10 @@ Finally, we now have a [feature request](https://github.com/bencbartlett/Overmin ### Removed - Removed all contents from `src/deprecated` + + ## Overmind [0.3.1] - 2018.5.12 + ### Added - Workers sign controllers at low RCL @@ -308,6 +321,7 @@ Finally, we now have a [feature request](https://github.com/bencbartlett/Overmin - Removed `LabMineralType` directive as it is no longer relevant + ## Overmind [0.3.0]: "Back to base-ics" - 2018.5.9 This release adds a ton of new automation features to base planning, partially overhauling the old RoomPlanner. Once a room plan is set at RCL1, the RoomPlanner never needs to be reopened. Road networks are now continuously re-computed by the new `RoadPlanner` module, allowing you to dynamically add and remove outposts. UpgradeSites now determine their own optimal placements, and UpgradeSites and MiningSites now automatically build links when appropriate. @@ -366,7 +380,9 @@ The terminal network has been improved as well, and now tracks transfers between - Fixed a bug where upgradeSites could misidentify their input in some pathological room layouts + ## Overmind [0.2.1] - 2018.3.22 + ### Added - Memory stat collection and `User` variable (#3 - thanks CoolFeather2!) - Brief setup instructions for dashboard @@ -381,6 +397,7 @@ The terminal network has been improved as well, and now tracks transfers between - Moved changelog to root + ## Overmind [0.2.0]: "Logistics Logic" - 2018.3.15 This release completely overhauls the logistics system in Overmind, replacing haulers, suppliers, and mineralSuppliers, which functioned on a rigid rules-based system, with a more flexible universal "transport" creep. `LogisticsGroup`s employ a stable matching algorithm to efficiently assign resource collection and supply tasks to transporters, seeking to maximize aggregate change in resources per tick. @@ -406,8 +423,11 @@ This release completely overhauls the logistics system in Overmind, replacing ha release of the Task system) + ## Overmind [0.1.0]: "GL HF" - 2018.3.2 -This release was initially deployed on 2018.3.2 but was re-versioned on 2018.3.15. + +(This release was initially deployed on 2018.3.2 but was re-versioned on 2018.3.15.) + ### Added - Initial pre-release of Overmind after 190 commits and about 80,000 additions. diff --git a/src/Colony.ts b/src/Colony.ts index e19753f51..ce2813db8 100644 --- a/src/Colony.ts +++ b/src/Colony.ts @@ -120,7 +120,7 @@ export class Colony { repairables: Structure[]; // | Repairable structures, discounting barriers and roads rechargeables: rechargeObjectType[]; // | Things that can be recharged from // obstacles: RoomPosition[]; // | List of other obstacles, e.g. immobile creeps - destinations: RoomPosition[]; + destinations: { pos: RoomPosition, order: number }[]; // Hive clusters hiveClusters: HiveCluster[]; // List of all hive clusters commandCenter: CommandCenter | undefined; // Component with logic for non-spawning structures diff --git a/src/directives/resource/extract.ts b/src/directives/resource/extract.ts index 505b68c93..5fe6db072 100644 --- a/src/directives/resource/extract.ts +++ b/src/directives/resource/extract.ts @@ -18,7 +18,7 @@ export class DirectiveExtract extends Directive { constructor(flag: Flag) { super(flag); if (this.colony) { - this.colony.destinations.push(this.pos); + this.colony.destinations.push({pos: this.pos, order: this.memory.created || Game.time}); } } diff --git a/src/directives/resource/harvest.ts b/src/directives/resource/harvest.ts index 65addb83d..3b90b21af 100644 --- a/src/directives/resource/harvest.ts +++ b/src/directives/resource/harvest.ts @@ -41,7 +41,7 @@ export class DirectiveHarvest extends Directive { super(flag); if (this.colony) { this.colony.miningSites[this.name] = this; - this.colony.destinations.push(this.pos); + this.colony.destinations.push({pos: this.pos, order: this.memory.created || Game.time}); } _.defaultsDeep(this.memory, defaultDirectiveHarvestMemory); } diff --git a/src/hiveClusters/hatchery.ts b/src/hiveClusters/hatchery.ts index fa1b8dab8..354bc6faa 100644 --- a/src/hiveClusters/hatchery.ts +++ b/src/hiveClusters/hatchery.ts @@ -89,7 +89,7 @@ export class Hatchery extends HiveCluster { super(colony, headSpawn, 'hatchery'); // Register structure components this.memory = Mem.wrap(this.colony.memory, 'hatchery', HatcheryMemoryDefaults, true); - if (this.colony.layout == 'twoPart') this.colony.destinations.push(this.pos); + if (this.colony.layout == 'twoPart') this.colony.destinations.push({pos: this.pos, order: -1}); this.spawns = colony.spawns; this.availableSpawns = _.filter(this.spawns, spawn => !spawn.spawning); this.extensions = colony.extensions; diff --git a/src/hiveClusters/sporeCrawler.ts b/src/hiveClusters/sporeCrawler.ts index ef96eba35..047a25a25 100644 --- a/src/hiveClusters/sporeCrawler.ts +++ b/src/hiveClusters/sporeCrawler.ts @@ -88,7 +88,7 @@ export class SporeCrawler extends HiveCluster { // } private preventRampartDecay() { - if (this.colony.level <= 4 && this.towers.length > 0) { + if (this.colony.level < 7 && this.towers.length > 0) { // expensive to check all rampart hits; only run in intermediate RCL let dyingRamparts = _.filter(this.room.ramparts, rampart => rampart.hits < WorkerOverlord.settings.barrierHits.critical diff --git a/src/hiveClusters/upgradeSite.ts b/src/hiveClusters/upgradeSite.ts index d80c66b6d..726ccf25b 100644 --- a/src/hiveClusters/upgradeSite.ts +++ b/src/hiveClusters/upgradeSite.ts @@ -54,7 +54,7 @@ export class UpgradeSite extends HiveCluster { } return this.calculateBatteryPos() || log.alert(`Upgrade site at ${this.pos.print}: no batteryPos!`); }); - if (this.batteryPos) this.colony.destinations.push(this.batteryPos); + if (this.batteryPos) this.colony.destinations.push({pos: this.batteryPos, order: 0}); // Register link $.set(this, 'link', () => this.pos.findClosestByLimitedRange(colony.availableLinks, 3)); this.colony.linkNetwork.claimLink(this.link); diff --git a/src/overlords/core/worker.ts b/src/overlords/core/worker.ts index 62b0658e8..daa9d517b 100644 --- a/src/overlords/core/worker.ts +++ b/src/overlords/core/worker.ts @@ -52,9 +52,14 @@ export class WorkerOverlord extends Overlord { barrier => barrier.hits < WorkerOverlord.settings.barrierHits.critical), 10); // Generate a list of structures needing repairing (different from fortifying except in critical case) this.repairStructures = $.structures(this, 'repairStructures', () => - _.filter(this.colony.repairables, function (structure) { + _.filter(this.colony.repairables, structure => { if (structure.structureType == STRUCTURE_CONTAINER) { - return structure.hits < 0.5 * structure.hitsMax; + // only repair containers in owned room + if (structure.pos.roomName == this.colony.name) { + return structure.hits < 0.5 * structure.hitsMax; + } else { + return false; + } } else { return structure.hits < structure.hitsMax; } @@ -137,20 +142,21 @@ export class WorkerOverlord extends Overlord { } else { numWorkers = $.number(this, 'numWorkers', () => { // At higher levels, spawn workers based on construction and repair that needs to be done - const MAX_WORKERS = 3; // Maximum number of workers to spawn - let constructionTicks = _.sum(this.constructionSites, - site => site.progressTotal - site.progress) / BUILD_POWER; + const MAX_WORKERS = 5; // Maximum number of workers to spawn + let buildTicks = _.sum(this.constructionSites, + site => Math.max(site.progressTotal - site.progress, 0)) / BUILD_POWER; let repairTicks = _.sum(this.repairStructures, structure => structure.hitsMax - structure.hits) / REPAIR_POWER; - let paveTicks = _.sum(this.colony.rooms, room => this.colony.roadLogistics.energyToRepave(room)); + let paveTicks = _.sum(this.colony.rooms, + room => this.colony.roadLogistics.energyToRepave(room)) / 1; // repairCost=1 let fortifyTicks = 0; if (this.colony.assets.energy > WorkerOverlord.settings.fortifyDutyThreshold) { - fortifyTicks = 0.25 * _.sum(this.fortifyBarriers, - barrier => WorkerOverlord.settings.barrierHits[this.colony.level] - - barrier.hits) / REPAIR_POWER; + fortifyTicks = 0.25 * _.sum(this.fortifyBarriers, barrier => + Math.max(0, WorkerOverlord.settings.barrierHits[this.colony.level] + - barrier.hits)) / REPAIR_POWER; } // max constructionTicks for private server manually setting progress - let numWorkers = Math.ceil(2 * (Math.max(constructionTicks, 0) + repairTicks + fortifyTicks) / + let numWorkers = Math.ceil(2 * (5 * buildTicks + repairTicks + paveTicks + fortifyTicks) / (workPartsPerWorker * CREEP_LIFE_TIME)); numWorkers = Math.min(numWorkers, MAX_WORKERS); if (this.colony.controller.ticksToDowngrade <= (this.colony.level >= 4 ? 10000 : 2000)) { diff --git a/src/roomPlanner/RoadPlanner.ts b/src/roomPlanner/RoadPlanner.ts index b291c6331..70b411562 100644 --- a/src/roomPlanner/RoadPlanner.ts +++ b/src/roomPlanner/RoadPlanner.ts @@ -8,7 +8,7 @@ import {Colony, getAllColonies} from '../Colony'; import {MatrixTypes, Pathing} from '../movement/Pathing'; import {profile} from '../profiler/decorator'; import {$} from '../caching/GlobalCache'; -import {getCacheExpiration} from '../utilities/utils'; +import {getCacheExpiration, onPublicServer} from '../utilities/utils'; export interface RoadPlannerMemory { roadLookup: { [roomName: string]: { [roadCoordName: string]: boolean } }; @@ -22,6 +22,11 @@ export interface RoadPlannerMemory { } } +const PLAIN_COST = 3; +const SWAMP_COST = 4; +const WALL_COST = 15 * PLAIN_COST; +const EXISTING_PATH_COST = PLAIN_COST - 1; + let memoryDefaults: RoadPlannerMemory = { roadLookup : {}, roadCoverage : 0.0, @@ -38,10 +43,9 @@ export class RoadPlanner { costMatrices: { [roomName: string]: CostMatrix }; static settings = { - encourageRoadMerging : true, // will reduce cost of some tiles in existing paths to encourage merging - tileCostReductionInterval : 10, // spatial frequency of tile cost reduction - recalculateRoadNetworkInterval: 1000, // recalculate road networks every (this many) ticks - recomputeCoverageInterval : 500, // recompute coverage to each destination this often + encourageRoadMerging : true, + recalculateRoadNetworkInterval: onPublicServer() ? 3000 : 1000, // recalculate road networks this often + recomputeCoverageInterval : onPublicServer() ? 1000 : 500, // recompute coverage to each destination this often buildRoadsAtRCL : 4, }; @@ -65,12 +69,11 @@ export class RoadPlanner { private recomputeRoadCoverages(storagePos: RoomPosition) { // Compute coverage for each path - let destinations = _.sortBy(this.colony.destinations, pos => pos.getMultiRoomRangeTo(storagePos)); - for (let destination of destinations) { - let destName = destination.name; + for (let destination of this.colony.destinations) { + let destName = destination.pos.name; if (!this.memory.roadCoverages[destName] || Game.time > this.memory.roadCoverages[destName].exp) { - log.debug(`Recomputing road coverage from ${storagePos.print} to ${destination.print}...`); - let roadCoverage = this.computeRoadCoverage(storagePos, destination); + log.debug(`Recomputing road coverage from ${storagePos.print} to ${destination.pos.print}...`); + let roadCoverage = this.computeRoadCoverage(storagePos, destination.pos); if (roadCoverage != undefined) { // Set expiration to be longer if road is nearly complete let expiration = roadCoverage.roadCount / roadCoverage.length >= 0.75 @@ -82,6 +85,18 @@ export class RoadPlanner { exp : expiration }; log.debug(`Coverage: ${JSON.stringify(roadCoverage)}`); + } else { + if (this.memory.roadCoverages[destName]) { + // if you already have some data, use it for a little while + this.memory.roadCoverages[destName].exp += 200; + } else { + // otherwise put in a placeholder + this.memory.roadCoverages[destName] = { + roadCount: 0, + length : 1, + exp : Game.time + 100 + }; + } } } } @@ -133,10 +148,10 @@ export class RoadPlanner { private buildRoadNetwork(storagePos: RoomPosition, obstacles: RoomPosition[]): void { this.costMatrices = {}; this.roadPositions = []; - let destinations = _.sortBy(this.colony.destinations, pos => pos.getMultiRoomRangeTo(storagePos)); + let destinations = _.sortBy(this.colony.destinations, destination => destination.order); // Connect commandCenter to each destination in colony - for (let pos of destinations) { - this.planRoad(storagePos, pos, obstacles); + for (let destination of destinations) { + this.planRoad(storagePos, destination.pos, obstacles); } this.formatRoadPositions(); } @@ -155,19 +170,17 @@ export class RoadPlanner { let matrix = new PathFinder.CostMatrix(); const terrain = Game.map.getRoomTerrain(roomName); - const ROAD_PLANNER_WALL_COST = 50; - for (let y = 0 + 1; y < 50 - 1; ++y) { for (let x = 0 + 1; x < 50 - 1; ++x) { switch (terrain.get(x, y)) { case TERRAIN_MASK_SWAMP: - matrix.set(x, y, 1); + matrix.set(x, y, SWAMP_COST); break; case TERRAIN_MASK_WALL: - matrix.set(x, y, ROAD_PLANNER_WALL_COST); + matrix.set(x, y, WALL_COST); break; default: // plain - matrix.set(x, y, 1); + matrix.set(x, y, PLAIN_COST); break; } } @@ -210,25 +223,25 @@ export class RoadPlanner { if (Pathing.shouldAvoid(roomName) && roomName != origin.roomName && roomName != destination.roomName) { return false; } - return this.generateRoadPlanningCostMatrix(roomName, obstacles); + if (!this.costMatrices[roomName]) { + this.costMatrices[roomName] = this.generateRoadPlanningCostMatrix(roomName, obstacles); + } + return this.costMatrices[roomName]; }; - let ret = PathFinder.search(origin, {pos: destination, range: 1}, {roomCallback: callback}); + let ret = PathFinder.search(origin, {pos: destination, range: 1}, {roomCallback: callback, maxOps: 40000}); if (ret.incomplete) { log.warning(`Roadplanner for ${this.colony.print}: could not plan road path!`); return; } - // Set every n-th tile of a planned path to be cost 1 to encourage road overlap for future pathing + // Reduce the cost of planned paths to encourage road overlap for future pathing if (RoadPlanner.settings.encourageRoadMerging) { - let interval = RoadPlanner.settings.tileCostReductionInterval; for (let i of _.range(ret.path.length)) { - if (i % interval == interval - 1) { - let pos = ret.path[i]; - if (this.costMatrices[pos.roomName] && !pos.isEdge) { - this.costMatrices[pos.roomName].set(pos.x, pos.y, 0x01); - } + let pos = ret.path[i]; + if (i % 2 == 0 && this.costMatrices[pos.roomName] && !pos.isEdge) { + this.costMatrices[pos.roomName].set(pos.x, pos.y, EXISTING_PATH_COST); } } } diff --git a/src/strategy/ExpansionPlanner.ts b/src/strategy/ExpansionPlanner.ts index 3185cfb2a..8a18f44ac 100644 --- a/src/strategy/ExpansionPlanner.ts +++ b/src/strategy/ExpansionPlanner.ts @@ -58,7 +58,7 @@ export class ExpansionPlanner { return false; } - // compute possible outposts + // compute possible outposts (includes host room) let possibleOutposts = Cartographer.findRoomsInRange(room.name, 2); // find source positions @@ -109,10 +109,11 @@ export class ExpansionPlanner { break; } if (verbose) log.info(msg + ret.path.length); - roomScore += energyPerSource / ret.path.length; + let offset = 25; // prevents over-sensitivity to very close sources + roomScore += energyPerSource / (ret.path.length + offset); } if (valid) { - outpostScores[roomName] = roomScore; + outpostScores[roomName] = Math.floor(roomScore); } } @@ -122,6 +123,7 @@ export class ExpansionPlanner { let roomsByScore = _.sortBy(_.keys(outpostScores), roomName => -1 * outpostScores[roomName]); for (let roomName of roomsByScore) { if (sourceCount > Colony.settings.remoteSourcesByLevel[8]) break; + let factor = roomName == room.name ? 2 : 1; // weight owned room scores more heavily totalScore += outpostScores[roomName]; sourceCount += outpostSourcePositions[roomName].length; } @@ -136,6 +138,7 @@ export class ExpansionPlanner { outposts : outpostScores, }; } + return true; }