diff --git a/packages/cli/bin/super-cli-upgrade-buildings.js b/packages/cli/bin/super-cli-upgrade-buildings.js
index 0625f1f..ae444e8 100644
--- a/packages/cli/bin/super-cli-upgrade-buildings.js
+++ b/packages/cli/bin/super-cli-upgrade-buildings.js
@@ -2,13 +2,51 @@ import program from 'commander'
import _ from 'lodash'
import * as runner from '../src/task-runner'
-let list = (str) => str.split(',')
-
program
- .option('-l, --loop', 'wait for builds to finish and run again')
- .option('-p --planet [name]', 'specify planet(s) to upgrade buildings on', list)
- .option('-s --skip [planet name]', 'specify planet(s) to skip', list)
+ .option('-p --planet [name]', 'planet to upgrade buildings on (or "all")')
+ .option('--all', 'upgrade all eligible buildings regardless of category')
+ .option('--ship', 'upgrade ship buildings')
+ .option('--spy', 'upgrade spy buildings')
+ .option('--defense', 'upgrade defense buildings')
+ .option('--food', 'upgrade food buildings')
+ .option('--ore', 'upgrade ore buildings')
+ .option('--water', 'upgrade water buildings')
+ .option('--energy', 'upgrade energy buildings')
+ .option('--waste', 'upgrade waste management buildings')
+ .option('--storage', 'upgrade storage buildings')
+ .option('--archaeology', 'upgrade the Archaeology Ministry')
+ .option('--government', 'upgrade government buildings')
+ .option('--station', 'upgrade the Space Station Labs')
+ .option('--terraforming', 'upgrade the Terraforming Lab and Gas Giant Lab')
+ .option('--happiness', 'upgrade happiness buildings')
+ .option('--tyleon', 'upgrade the Lost City of Tyleon')
+ .option('-l --loop', 'wait for builds to finish and run again, instead of a single run')
+ .option('-d --dry-run', 'show what would happen without actually changing anything')
.parse(process.argv)
-let options = _.pick(program, ['loop', 'planet', 'skip'])
-runner.run('upgrade-buildings', options)
+let options = {
+ planet: program.planet,
+ categoryAll: program.all,
+ categoryShip: program.ship,
+ categorySpy: program.spy,
+ categoryDefense: program.defense,
+ categoryFood: program.food,
+ categoryOre: program.ore,
+ categoryWater: program.water,
+ categoryEnergy: program.energy,
+ categoryWaste: program.waste,
+ categoryStorage: program.storage,
+ categoryArchaeology: program.archaeology,
+ categoryGovernment: program.government,
+ categoryStation: program.station,
+ categoryTerraforming: program.terraforming,
+ categoryHappiness: program.happiness,
+ categoryTyleon: program.tyleon,
+ loop: program.loop,
+ dryRun: program.dryRun,
+}
+
+runner.run(
+ 'upgrade-buildings',
+ _.pickBy(options, (v) => !_.isUndefined(v))
+)
diff --git a/packages/core/index.js b/packages/core/index.js
index 8b82310..a374725 100644
--- a/packages/core/index.js
+++ b/packages/core/index.js
@@ -12,6 +12,7 @@ import {
eachPlanet,
} from './src/planets'
import { findBuilding, findBuildings, bodyBuildings, archGetInventory } from './src/buildings'
+import { CATEGORY_BUILDINGS, NEVER_UPGRADE } from './src/building-categories'
import captchaPrompt from './src/captcha-prompt'
import constants from './src/constants'
import log from './src/log'
@@ -38,6 +39,8 @@ export {
findBuildings,
bodyBuildings,
archGetInventory,
+ CATEGORY_BUILDINGS,
+ NEVER_UPGRADE,
captchaPrompt,
constants,
log,
diff --git a/packages/core/src/building-categories.js b/packages/core/src/building-categories.js
new file mode 100644
index 0000000..78b1546
--- /dev/null
+++ b/packages/core/src/building-categories.js
@@ -0,0 +1,193 @@
+const CATEGORY_BUILDINGS = {
+ ship: [
+ 'Shipyard',
+ 'Space Port',
+ 'Observatory',
+ 'Pilot Training Facility',
+ 'Cloaking Lab',
+ 'Munitions Lab',
+ 'Propulsion System Factory',
+ 'Trade Ministry',
+ ],
+
+ spy: [
+ 'Espionage Ministry',
+ 'Intelligence Ministry',
+ 'Intel Training Facility',
+ 'Mayhem Training Facility',
+ 'Politics Training Facility',
+ 'Theft Training Facility',
+ 'Mercenaries Guild',
+ 'Genetics Lab',
+ ],
+
+ defense: ['Shield Against Weapons', 'Security Ministry'],
+
+ food: [
+ 'Algae Cropper',
+ 'Apple Orchard',
+ 'Amalgus Bean Plantation',
+ 'Beeldeban Herder',
+ 'Lapis Orchard',
+ 'Malcud Fungus Farm',
+ 'Denton Root Patch',
+ 'Corn Plantation',
+ 'Potato Patch',
+ 'Wheat Farm',
+ 'Bakery',
+ 'Burger Packer',
+ 'Cheese Maker',
+ 'Denton Root Chip Frier',
+ 'Apple Cider Bottler',
+ 'Corn Meal Grinder',
+ 'Dairy Farm',
+ 'Pancake Factory',
+ 'Pie Bakery',
+ 'Beeldeban Protein Shake Factory',
+ 'Soup Cannery',
+ 'Syrup Bottler',
+ ],
+
+ ore: ['Mine', 'Ore Refinery', 'Mining Ministry'],
+
+ water: [
+ 'Water Purification Plant',
+ 'Water Production Plant',
+ 'Water Reclamation Facility',
+ 'Atmospheric Evaporator',
+ ],
+
+ energy: [
+ 'Geo-Energy Plant',
+ 'Fission Energy Plant',
+ 'Fusion Energy Plant',
+ 'Singularity Energy Plant',
+ 'Hydrocarbon Energy Plant',
+ 'Waste Energy Plant',
+ ],
+
+ waste: ['Waste Digester', 'Waste Recycling Center', 'Waste Treatment Center', 'Waste Exchanger'],
+
+ storage: [
+ 'Warehouse',
+ 'Distribution Center',
+ 'Ore Storage Tanks',
+ 'Food Reserve',
+ 'Water Storage Tank',
+ 'Energy Reserve',
+ 'Waste Sequestration Well',
+ 'Stockpile',
+ ],
+
+ archaeology: ['Archaeology Ministry'],
+
+ government: [
+ 'Planetary Command Center',
+ 'Development Ministry',
+ 'Oversight Ministry',
+ 'Mission Command',
+ 'Subspace Transporter',
+ 'Capitol',
+ 'Embassy',
+ ],
+
+ station: [
+ 'Space Station Lab (A)',
+ 'Space Station Lab (B)',
+ 'Space Station Lab (C)',
+ 'Space Station Lab (D)',
+ ],
+
+ terraforming: ['Terraforming Lab', 'Gas Giant Lab'],
+
+ happiness: [
+ 'Theme Park',
+ 'Entertainment District',
+ 'Park',
+ 'Luxury Housing',
+ 'Network 19 Affiliate',
+ ],
+
+ tyleon: [
+ 'Lost City of Tyleon (A)',
+ 'Lost City of Tyleon (B)',
+ 'Lost City of Tyleon (C)',
+ 'Lost City of Tyleon (D)',
+ 'Lost City of Tyleon (E)',
+ 'Lost City of Tyleon (F)',
+ 'Lost City of Tyleon (G)',
+ 'Lost City of Tyleon (H)',
+ 'Lost City of Tyleon (I)',
+ ],
+}
+
+const NEVER_UPGRADE = [
+ // Happiness (expensive)
+ 'Pyramid Junk Sculpture',
+ 'Space Junk Park',
+ 'Metal Junk Arches',
+ 'Great Ball of Junk',
+ 'Junk Henge Sculpture',
+
+ // Natural features
+ 'Patch of Sand',
+ 'Grove of Trees',
+ 'Lagoon',
+ 'Beach [1]',
+ 'Beach [2]',
+ 'Beach [3]',
+ 'Beach [4]',
+ 'Beach [5]',
+ 'Beach [6]',
+ 'Beach [7]',
+ 'Beach [8]',
+ 'Beach [9]',
+ 'Beach [10]',
+ 'Beach [11]',
+ 'Beach [12]',
+ 'Beach [13]',
+ 'Crater',
+ 'Smoldering Crater',
+ 'Lake',
+ 'Rocky Outcropping',
+
+ // Glyph buildings
+ 'Geo Thermal Vent',
+ 'Natural Spring',
+ 'Kalavian Ruins',
+ 'Volcano',
+ 'Ravine',
+ 'Essentia Vein',
+ 'Fissure',
+ 'Algae Pond',
+ 'Amalgus Meadow',
+ 'Beeldeban Nest',
+ 'Denton Brambles',
+ 'Lapis Forest',
+ 'Malcud Field',
+ 'Black Hole Generator',
+ 'Oracle of Anid',
+ 'Pantheon of Hagness',
+ 'Citadel of Knope',
+ `Gratch's Gauntlet`,
+ 'Library of Jith',
+ 'Temple of the Drajilites',
+ 'Interdimensional Rift',
+ `Massad's Henge`,
+ `Kastern's Keep`,
+ 'The Dillon Forge',
+
+ // Situational plot-expansion platforms
+ 'Gas Giant Settlement Platform',
+ 'Terraforming Platform',
+
+ // Temporary/colony-setup
+ 'Supply Pod',
+ 'Crashed Ship Site',
+ 'Subspace Supply Depot',
+
+ // Hostile/non-player
+ 'Deployed Bleeder',
+]
+
+export { CATEGORY_BUILDINGS, NEVER_UPGRADE }
diff --git a/packages/core/src/util.js b/packages/core/src/util.js
index 6bac9d6..a62251f 100644
--- a/packages/core/src/util.js
+++ b/packages/core/src/util.js
@@ -48,6 +48,15 @@ const util = {
/**
* Formats time (in milliseconds) to a human-readable form of `days:hours:minutes:seconds`.
+ * Leading units that are zero are dropped, down to a minimum of `minutes:seconds`.
+ *
+ * @example
+ * util.formatMs(10 * 1000)
+ * // => '0:10'
+ *
+ * @example
+ * util.formatMs(60 * 60 * 1000 + 10 * 1000)
+ * // => '1:00:10'
*
* @param {number} ms - time you want formatted in milliseconds
* @return {string} human-readable time in the form `days:hours:minutes:seconds`
@@ -57,9 +66,20 @@ const util = {
return ''
}
- console.log('TODO: duration blah blah')
- return '00:00'
- // return moment.duration(ms, 'milliseconds').format();
+ let totalSeconds = Math.max(0, Math.round(ms / 1000))
+ let parts = [
+ Math.floor(totalSeconds / 86400),
+ Math.floor(totalSeconds / 3600) % 24,
+ Math.floor(totalSeconds / 60) % 60,
+ totalSeconds % 60,
+ ]
+
+ // Drop leading zero units, but always keep minutes and seconds.
+ while (parts.length > 2 && parts[0] === 0) {
+ parts.shift()
+ }
+
+ return _.map(parts, (part, i) => (i === 0 ? `${part}` : _.padStart(part, 2, '0'))).join(':')
},
/**
diff --git a/packages/tasks/src/index.js b/packages/tasks/src/index.js
index a922e57..8612d70 100644
--- a/packages/tasks/src/index.js
+++ b/packages/tasks/src/index.js
@@ -283,13 +283,30 @@ const taskDefinitions = [
{
name: 'upgrade-buildings',
title: 'Upgrade Buildings',
- description: `Warning: this is only setup for nataliethistime - use at own risk.`,
- platforms: ['cli'],
+ description:
+ 'Upgrade buildings on one planet or all planets, lowest level first, filtered by category.',
+ platforms: ['cli', 'web'],
TaskClass: upgradeBuildings,
defaults: {
+ planet: '',
+ categoryAll: false,
+ categoryShip: false,
+ categorySpy: false,
+ categoryDefense: false,
+ categoryFood: false,
+ categoryOre: false,
+ categoryWater: false,
+ categoryEnergy: false,
+ categoryWaste: false,
+ categoryStorage: false,
+ categoryArchaeology: false,
+ categoryGovernment: false,
+ categoryStation: false,
+ categoryTerraforming: false,
+ categoryHappiness: false,
+ categoryTyleon: false,
loop: false,
- skip: [],
- planet: [],
+ dryRun: false,
},
},
diff --git a/packages/tasks/src/upgrade-buildings.js b/packages/tasks/src/upgrade-buildings.js
index 59fa263..60750c4 100644
--- a/packages/tasks/src/upgrade-buildings.js
+++ b/packages/tasks/src/upgrade-buildings.js
@@ -3,480 +3,175 @@ import _ from 'lodash'
import {
lacuna,
call,
- colonies,
findPlanets,
eachPlanet,
- findBuildings,
log,
timelist as Timelist,
util,
+ CATEGORY_BUILDINGS,
+ NEVER_UPGRADE,
} from '@tlecommunity/super-core'
-// This is the template that the upgrader follows.
-// The template is comprised of sections.
-// Each section is an array of buildings to upgrade.
-// Each building is an object containing the name of the building and the level to upgrade to.
-// Only one section is upgraded at a time.
-
-const TEMPLATE = [
- // I tend to max my glyph buildings as soon as I colonise a colony. Hence, this is here.
- [
- {
- level: 30,
- name: 'Algae Pond',
- },
- {
- level: 30,
- name: 'Amalgus Meadow',
- },
- {
- level: 30,
- name: 'Beeldeban Nest',
- },
- {
- level: 30,
- name: 'Denton Brambles',
- },
- {
- level: 30,
- name: 'Lapis Forest',
- },
- {
- level: 30,
- name: 'Malcud Field',
- },
- {
- level: 30,
- name: 'Volcano',
- },
- {
- level: 30,
- name: 'Natural Spring',
- },
- {
- level: 30,
- name: 'Geo Thermal Vent',
- },
- {
- level: 30,
- name: 'Kalavian Ruins',
- },
- {
- level: 30,
- name: 'Interdimensional Rift',
- },
- {
- level: 30,
- name: 'Crashed Ship Site',
- },
- {
- level: 30,
- name: 'Pantheon of Hagness',
- },
- ],
-
- // Basic construction stuff.
- [
- {
- level: 30,
- name: 'Oversight Ministry',
- },
- {
- level: 5,
- name: 'Development Ministry',
- },
- ],
-
- // Get the Archaeology up a bit
- [
- {
- level: 25,
- name: 'Archaeology Ministry',
- },
- ],
-
- // Upgrade the shipyards so we can build some Excavators.
- [
- {
- level: 20,
- name: 'Shipyard',
- },
- ],
-
- // Upgrade the spaceports so we can hold some Excavators.
- [
- {
- level: 5,
- name: 'Space Port',
- },
- ],
-
- // Upgrade the Dev Min so we can build more.
- [
- {
- level: 15,
- name: 'Development Ministry',
- },
- ],
-
- // Some defence would be nice, too.
- [
- {
- level: 10,
- name: 'Shield Against Weapons',
- },
- ],
-
- // Upgrade the Archaeology all the way.
- [
- {
- level: 30,
- name: 'Archaeology Ministry',
- },
- ],
-
- // If you're cever enough to have one of these - go nuts.
- [
- {
- level: 30,
- name: 'Lost City of Tyleon (A)',
- },
- {
- level: 30,
- name: 'Lost City of Tyleon (B)',
- },
- {
- level: 30,
- name: 'Lost City of Tyleon (C)',
- },
- {
- level: 30,
- name: 'Lost City of Tyleon (D)',
- },
- {
- level: 30,
- name: 'Lost City of Tyleon (E)',
- },
- {
- level: 30,
- name: 'Lost City of Tyleon (F)',
- },
- {
- level: 30,
- name: 'Lost City of Tyleon (G)',
- },
- {
- level: 30,
- name: 'Lost City of Tyleon (H)',
- },
- {
- level: 30,
- name: 'Lost City of Tyleon (I)',
- },
- ],
-
- // Get started on all the spy releated stuff.
- [
- {
- level: 20,
- name: 'Intelligence Ministry',
- },
- {
- level: 20,
- name: 'Security Ministry',
- },
- {
- level: 20,
- name: 'Espionage Ministry',
- },
- {
- level: 20,
- name: 'Intel Training',
- },
- {
- level: 20,
- name: 'Mayhem Training',
- },
- {
- level: 20,
- name: 'Politics Training',
- },
- {
- level: 20,
- name: 'Theft Training',
- },
- ],
-
- // Get setup to make Space Station plans, too.
- [
- {
- level: 20,
- name: 'Space Station Lab (A)',
- },
- {
- level: 20,
- name: 'Space Station Lab (B)',
- },
- {
- level: 20,
- name: 'Space Station Lab (C)',
- },
- {
- level: 20,
- name: 'Space Station Lab (D)',
- },
- ],
-
- // Finish off the shipyards.
- [
- {
- level: 30,
- name: 'Shipyard',
- },
- ],
-
- // Max out the ship attribute buildings.
- [
- {
- level: 30,
- name: 'Trade Ministry',
- },
- {
- level: 30,
- name: 'Pilot Training Facility',
- },
- {
- level: 30,
- name: 'Munitions Lab',
- },
- {
- level: 30,
- name: 'Cloaking Lab',
- },
- {
- level: 30,
- name: 'Propulsion System Factory',
- },
- ],
-
- // Upgrade the defences a bit further.
- [
- {
- level: 20,
- name: 'Shield Against Weapons',
- },
- ],
-
- // Finish off the spy stuff.
- [
- {
- level: 30,
- name: 'Intelligence Ministry',
- },
- {
- level: 30,
- name: 'Security Ministry',
- },
- {
- level: 30,
- name: 'Espionage Ministry',
- },
- {
- level: 30,
- name: 'Intel Training',
- },
- {
- level: 30,
- name: 'Mayhem Training',
- },
- {
- level: 30,
- name: 'Politics Training',
- },
- {
- level: 30,
- name: 'Theft Training',
- },
- ],
-
- // Finish off the SAWs
- [
- {
- level: 30,
- name: 'Shield Against Weapons',
- },
- ],
-
- // These are all 'bonus buildings' that need to be upgraded eventually, I guess.
- [
- {
- level: 30,
- name: 'Mission Command',
- },
- {
- level: 30,
- name: 'Entertainment District',
- },
- {
- level: 30,
- name: 'Observatory',
- },
- {
- level: 30,
- name: 'Terraforming Lab',
- },
- {
- level: 30,
- name: 'Gas Giant Lab',
- },
- {
- level: 30,
- name: 'Subspace Transporter',
- },
- {
- level: 30,
- name: 'Planetary Command Center',
- },
- {
- level: 30,
- name: 'Network 19',
- },
- {
- level: 30,
- name: 'Genetics Lab',
- },
- {
- level: 30,
- name: 'Waste Sequestration Well',
- },
- {
- level: 30,
- name: 'Development Ministry',
- },
- ],
-
- // Waste some time on these...
- [
- {
- level: 30,
- name: 'Food Reserve',
- },
- {
- level: 30,
- name: 'Ore Storage Tanks',
- },
- {
- level: 30,
- name: 'Water Storage Tank',
- },
- {
- level: 30,
- name: 'Energy Reserve',
- },
- ],
-
- // Finally, upgrade all the Space Port. Only up to level 28 because 30 uses too much energy.
- [
- {
- level: 28,
- name: 'Space Port',
- },
- ],
-]
+const CATEGORY_KEYS = _.keys(CATEGORY_BUILDINGS)
class UpgradeBuildings {
constructor(options) {
this.options = options
- this.options.skip = util.array(this.options.skip)
- this.options.planet = util.array(this.options.planet)
-
- // This is for tracking the number of buildings that have been upgraded this run.
this.upgraded = 0
-
this.timelist = new Timelist()
}
- // This method checks if a building can be upgraded.
- // It handles:
- // - rejecting if there's any error
- checkPossibleUpgrade(bd) {
- return new Promise((resolve, reject) => {
- let b = lacuna.buildingFromUrl(bd.url)
+ // Returns the flat list of building names eligible for upgrade given the selected category
+ // checkboxes, or `null` if every category (other than the permanent NEVER_UPGRADE list) is
+ // eligible because "All Buildings" was selected.
+ eligibleBuildingNames() {
+ if (this.options.categoryAll) {
+ return null
+ }
+
+ return _.chain(CATEGORY_KEYS)
+ .filter((key) => this.options[`category${_.upperFirst(key)}`])
+ .flatMap((key) => CATEGORY_BUILDINGS[key])
+ .value()
+ }
- call(() => b.view({ building_id: bd.id }))
+ async upgradeBuildings(colony, buildings) {
+ let upgradedHere = 0
+ let blocked = []
+
+ for (const b of buildings) {
+ log.info(`Upgrading ${b.name} from level ${b.level} to ${b.level + 1}`)
+
+ if (this.options.dryRun) {
+ continue
+ }
+
+ await call(() => lacuna.buildingFromUrl(b.url).upgrade({ building_id: b.id }))
.then((result) => {
- if (result.building.upgrade.can) {
- resolve()
+ this.upgraded++
+ upgradedHere++
+ this.timelist.add(result.building.pending_build.end)
+ })
+ .catch((err) => {
+ if (err === `There's no room left in the build queue.`) {
+ throw err
+ } else if (
+ typeof err === 'string' &&
+ util.regexMatch(/not enough \S+ in storage to build this/i, err)
+ ) {
+ // A cheaper building on this planet may still be affordable, so keep going.
+ log.warn(err)
+ blocked.push(b)
} else {
- let textReason = result.building.upgrade.reason[1]
- reject(textReason)
+ // Swallow errors right here so we can move on to other buildings on this planet.
+ util.handlePromiseError(err)
}
})
- .catch(reject)
- })
+ }
+
+ if (upgradedHere === 0 && blocked.length > 0) {
+ await this.waitForResources(colony, blocked)
+ }
}
- // This method upgrades the given building.
- // It handles:
- // - logging the action to the screen
- // - incrementing the buildings upgraded counter
- //
- upgradeBuilding(bd) {
- log.info(`Upgrading ${bd.name} (${bd.x}, ${bd.y}) from ${bd.level} to ` + `${bd.level * 1 + 1}`)
+ // Milliseconds until `body` has stockpiled enough of every resource in `cost`, or Infinity if
+ // it never will (not producing the resource, or the cost exceeds storage capacity).
+ msUntilAffordable(cost, body) {
+ let ms = 0
- return this.checkPossibleUpgrade(bd).then(() => {
- let b = lacuna.buildingFromUrl(bd.url)
+ for (const res of ['food', 'ore', 'water', 'energy']) {
+ let needed = util.int(cost[res])
+ let shortfall = needed - util.int(body[`${res}_stored`])
- return call(() => b.upgrade({ building_id: bd.id })).then((result) => {
- this.timelist.add(result.building.pending_build.end)
- this.upgraded++
- })
- })
- }
+ if (shortfall <= 0) {
+ continue
+ }
- // This method upgrades a list of buildings according to the template.
- // It handles:
- // - making sure that one section is upgraded at a time
- // - makue sure we don't try to upgrade a building thats already upgrading
- //
- async upgradeBuildings(buildings) {
- // This variable is used to ensure that one section is upgraded at a time.
- let sectionCompleted = true
-
- for (const section of TEMPLATE) {
- // If the section isn't complete, don't go any further in the template.
- if (!sectionCompleted) {
- return
+ let perHour = util.int(body[`${res}_hour`])
+
+ if (perHour <= 0 || needed > util.int(body[`${res}_capacity`])) {
+ return Infinity
}
- for (const build of section) {
- // A build is an upgrade that should be done according to the template.
- // Here, we go through each one and see if it can be done.
+ ms = Math.max(ms, (shortfall / perHour) * 60 * 60 * 1000)
+ }
- await findBuildings(buildings, build.name).then(async (matches) => {
- // A 'match' is a building that matches the current 'build' within
- // the current 'section'.
+ return ms
+ }
- matches = _.sortBy(matches, (b) => util.int(b.level))
+ // Nothing on this planet could be upgraded for lack of resources. Work out when the cheapest
+ // blocked building becomes affordable and add that to the timelist, so the loop wakes up and
+ // tries again once the resources are there.
+ async waitForResources(colony, blocked) {
+ let soonest = Infinity
+ let soonestBuilding
- for (const match of matches) {
- if (build.level > match.level) {
- // A section is considered incomplete when there's still more
- // that needs upgrading.
- sectionCompleted = false
+ for (const b of blocked) {
+ let view = await call(() => lacuna.buildingFromUrl(b.url).view({ building_id: b.id }))
+ let body =
+ (view.status && view.status.body) ||
+ (await call(() => lacuna.body.getStatus({ body_id: colony.id }))).body
- // Don't try and upgrade something that's already upgrading.
- if (match.pending_build) {
- return
- }
+ let ms = this.msUntilAffordable(view.building.upgrade.cost, body)
- await this.upgradeBuilding(match)
- }
- }
- })
+ if (ms < soonest) {
+ soonest = ms
+ soonestBuilding = b
}
}
+
+ if (soonest === Infinity) {
+ log.error(
+ `${colony.name} can't afford to upgrade anything and isn't producing enough to catch up`
+ )
+ return
+ }
+
+ let minutes = Math.ceil(soonest / (60 * 1000))
+ log.info(
+ `Nothing on ${colony.name} is affordable; ${soonestBuilding.name} should be in ` +
+ `${minutes} ${util.handlePlurality(minutes, 'minute')}`
+ )
+
+ this.timelist.add(new Date(Date.now() + soonest))
+ }
+
+ handleColony(colony, buildings) {
+ // Add existing builds to the time list.
+ _.each(buildings, (b) => {
+ if (b.pending_build) {
+ this.timelist.add(b.pending_build.end)
+ }
+ })
+
+ let eligibleNames = this.eligibleBuildingNames()
+
+ let toUpgrade = _.chain(buildings)
+ .map((b) => {
+ b.level = util.int(b.level)
+ return b
+ })
+ .sortBy('level')
+ .filter((b) => {
+ if (b.level === 30) {
+ return false
+ // Level 30 Space Ports use too much energy.
+ } else if (b.level >= 28 && b.name === 'Space Port') {
+ return false
+ } else if (_.includes(NEVER_UPGRADE, b.name)) {
+ return false
+ } else if (eligibleNames && !_.includes(eligibleNames, b.name)) {
+ return false
+ } else if (b.pending_build) {
+ return false
+ } else {
+ return true
+ }
+ })
+ .value()
+
+ return this.upgradeBuildings(colony, toUpgrade)
}
handleSleep() {
@@ -493,8 +188,14 @@ class UpgradeBuildings {
validateOptions() {
return new Promise((resolve, reject) => {
+ let anyCategorySelected =
+ this.options.categoryAll ||
+ _.some(CATEGORY_KEYS, (key) => this.options[`category${_.upperFirst(key)}`])
+
if (!this.options.planet) {
- reject('please specify a planet to upgrade buildings on')
+ reject('please specify a planet')
+ } else if (!anyCategorySelected) {
+ reject('please select at least one building category to upgrade')
} else {
resolve(true)
}
@@ -503,21 +204,9 @@ class UpgradeBuildings {
run() {
return new Promise((resolve, reject) => {
- colonies()
+ findPlanets(this.options.planet)
.then((colonies) => {
- return findPlanets(this.options.planet, this.options.skip)
- })
- .then((colonies) => {
- return eachPlanet(colonies, (colony, buildings) => {
- // Add existing build queues to the list.
- _.each(buildings, (b) => {
- if (b.pending_build) {
- this.timelist.add(b.pending_build.end)
- }
- })
-
- return this.upgradeBuildings(buildings)
- })
+ return eachPlanet(colonies, _.bind(this.handleColony, this))
})
.then(() => {
let plural = util.handlePlurality(this.upgraded, 'building')
@@ -526,17 +215,17 @@ class UpgradeBuildings {
? `Didn't upgrade any buildings`
: `Successfully upgraded ${this.upgraded} ${plural}`
- if (this.options.loop) {
- log.newline()
- log.info(message)
+ log.newline()
+ log.info(message)
+ if (this.options.loop && !this.options.dryRun) {
return this.handleSleep().then(() => {
this.upgraded = 0
this.timelist.clear()
this.run()
})
} else {
- resolve(message)
+ resolve()
}
})
.catch(reject)
diff --git a/packages/web/src/components/screens/task-configuration.vue b/packages/web/src/components/screens/task-configuration.vue
index b428234..a2e9880 100644
--- a/packages/web/src/components/screens/task-configuration.vue
+++ b/packages/web/src/components/screens/task-configuration.vue
@@ -35,6 +35,7 @@ import SpySkills from '../task-configs/spy-skills.vue'
import SpyStatus from '../task-configs/spy-status.vue'
import SpyTrainer from '../task-configs/spy-trainer.vue'
import ThrowParties from '../task-configs/throw-parties.vue'
+import UpgradeBuildings from '../task-configs/upgrade-buildings.vue'
import ViewLaws from '../task-configs/view-laws.vue'
import * as App from '../../app'
@@ -80,6 +81,7 @@ export default {
'spy-status': SpyStatus,
'spy-trainer': SpyTrainer,
'throw-parties': ThrowParties,
+ 'upgrade-buildings': UpgradeBuildings,
'view-laws': ViewLaws,
},
}
diff --git a/packages/web/src/components/task-configs/upgrade-buildings.vue b/packages/web/src/components/task-configs/upgrade-buildings.vue
new file mode 100644
index 0000000..17eeb4e
--- /dev/null
+++ b/packages/web/src/components/task-configs/upgrade-buildings.vue
@@ -0,0 +1,94 @@
+
+
+
+
+
diff --git a/test/util-test.js b/test/util-test.js
index 8b1ba28..c8827d9 100644
--- a/test/util-test.js
+++ b/test/util-test.js
@@ -40,9 +40,12 @@ describe('util', () => {
expect(util.formatMs(null)).to.equal('')
})
- it.skip('should format milliseconds properly', () => {
- expect(util.formatMs(60 * 60 * 1000)).to.equal('1h')
+ it('should format milliseconds properly', () => {
+ expect(util.formatMs(10 * 1000)).to.equal('0:10')
+ expect(util.formatMs(5 * 60 * 1000 + 3 * 1000)).to.equal('5:03')
+ expect(util.formatMs(60 * 60 * 1000)).to.equal('1:00:00')
expect(util.formatMs(60 * 60 * 1000 + 10 * 1000)).to.equal('1:00:10')
+ expect(util.formatMs(2 * 24 * 60 * 60 * 1000 + 3 * 60 * 60 * 1000)).to.equal('2:03:00:00')
})
})