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') }) })