diff --git a/config.properties.template b/config.properties.template index d1fd815d4..d959371d5 100644 --- a/config.properties.template +++ b/config.properties.template @@ -147,6 +147,11 @@ timer_walk_to_start_pokestop=-1 # Set profile update timer (Default: 60) profile_update_timer=60 +# Whether to run an evolution strategy every time the profile is updated +auto_evolve=false +# Evolution strategy to use, blank will use max_iv by default +evolution_strategy= + # Minimum IV percentage to keep a pokemon (to ignore IV: use -1) # between 0 and 100, suggested 80 transfer_iv_threshold=80 diff --git a/src/main/kotlin/ink/abb/pogo/scraper/Bot.kt b/src/main/kotlin/ink/abb/pogo/scraper/Bot.kt index cf6337911..4737e2a4b 100644 --- a/src/main/kotlin/ink/abb/pogo/scraper/Bot.kt +++ b/src/main/kotlin/ink/abb/pogo/scraper/Bot.kt @@ -91,7 +91,7 @@ class Bot(val api: PokemonGo, val settings: Settings) { val profile = UpdateProfile() val catch = CatchOneNearbyPokemon() val release = ReleasePokemon() - val evolve = EvolvePokemon() + val evolve = Evolve() val hatchEggs = HatchEggs() val export = Export() @@ -138,8 +138,15 @@ class Bot(val api: PokemonGo, val settings: Settings) { task(hatchEggs) if (settings.export.length > 0) task(export) - if (settings.evolveStackLimit > 0) - task(evolve) + if (settings.autoEvolve) { + // Pausing to not cause too much strain if a bunch or evolves happen at the same time + try { + ctx.pauseWalking.set(true) + task(evolve) + } finally { + ctx.pauseWalking.set(false) + } + } } runLoop(TimeUnit.SECONDS.toMillis(5), "BotLoop") { @@ -156,7 +163,6 @@ class Bot(val api: PokemonGo, val settings: Settings) { task(drop) if (settings.autotransfer) task(release) - } runLoop(500, "PokestopLoop") { diff --git a/src/main/kotlin/ink/abb/pogo/scraper/Settings.kt b/src/main/kotlin/ink/abb/pogo/scraper/Settings.kt index 6da7a6ce3..888090217 100644 --- a/src/main/kotlin/ink/abb/pogo/scraper/Settings.kt +++ b/src/main/kotlin/ink/abb/pogo/scraper/Settings.kt @@ -12,6 +12,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties import POGOProtos.Enums.PokemonIdOuterClass.PokemonId import POGOProtos.Inventory.Item.ItemIdOuterClass.ItemId import com.pokegoapi.google.common.geometry.S2LatLng +import ink.abb.pogo.scraper.evolve.EvolutionStrategy import ink.abb.pogo.scraper.util.Log import ink.abb.pogo.scraper.util.credentials.* import java.io.BufferedReader @@ -129,7 +130,10 @@ class SettingsParser(val properties: Properties) { waitTimeMin = getPropertyIfSet("Minimal time to wait", "wait_time_min", defaults.waitTimeMin, String::toInt), - waitTimeMax = getPropertyIfSet("Maximal time to wait", "wait_time_max", defaults.waitTimeMax, String::toInt) + waitTimeMax = getPropertyIfSet("Maximal time to wait", "wait_time_max", defaults.waitTimeMax, String::toInt), + + autoEvolve = getPropertyIfSet("Should auto evolve", "auto_evolve", defaults.autoEvolve, String::toBoolean), + evolutionStrategy = getPropertyIfSet("Evolution strategy to use", "evolution_strategy", defaults.evolutionStrategy, String::toString) ) } @@ -254,6 +258,9 @@ data class Settings( val export: String = "", + val autoEvolve: Boolean = false, + val evolutionStrategy: String = "", + val guiPortSocket: Int = 8001, var initialMapSize: Int = 9, diff --git a/src/main/kotlin/ink/abb/pogo/scraper/evolve/EvolutionStrategy.kt b/src/main/kotlin/ink/abb/pogo/scraper/evolve/EvolutionStrategy.kt new file mode 100644 index 000000000..0de775fcd --- /dev/null +++ b/src/main/kotlin/ink/abb/pogo/scraper/evolve/EvolutionStrategy.kt @@ -0,0 +1,9 @@ +package ink.abb.pogo.scraper.evolve + +import ink.abb.pogo.scraper.Bot +import ink.abb.pogo.scraper.Context +import ink.abb.pogo.scraper.Settings + +interface EvolutionStrategy { + fun evolve(bot: Bot, ctx: Context, settings: Settings) +} \ No newline at end of file diff --git a/src/main/kotlin/ink/abb/pogo/scraper/evolve/IvMaximizingStrategy.kt b/src/main/kotlin/ink/abb/pogo/scraper/evolve/IvMaximizingStrategy.kt new file mode 100644 index 000000000..5d6acd610 --- /dev/null +++ b/src/main/kotlin/ink/abb/pogo/scraper/evolve/IvMaximizingStrategy.kt @@ -0,0 +1,89 @@ +package ink.abb.pogo.scraper.evolve + +import POGOProtos.Enums.PokemonFamilyIdOuterClass +import POGOProtos.Enums.PokemonIdOuterClass +import com.pokegoapi.api.pokemon.Pokemon +import ink.abb.pogo.scraper.Bot +import ink.abb.pogo.scraper.Context +import ink.abb.pogo.scraper.Settings +import ink.abb.pogo.scraper.util.Log +import ink.abb.pogo.scraper.util.pokemon.getIvPercentage + +/* + * Evolution strategy that prioritizes maximizing IV, then prioritizes getting to highest evolution + */ +class IvMaximizingStrategy : EvolutionStrategy { + + lateinit private var EEVEE_EVOLUTION_DATA: Map + + constructor() { + EEVEE_EVOLUTION_DATA = mapOf( + Pair(PokemonIdOuterClass.PokemonId.VAPOREON, "Rainer"), + Pair(PokemonIdOuterClass.PokemonId.FLAREON, "Pyro"), + Pair(PokemonIdOuterClass.PokemonId.JOLTEON, "Sparky") + ) + } + + override fun evolve(bot: Bot, ctx: Context, settings: Settings) { + val pokemonFamilies = ctx.api.inventories.pokebank.pokemons.groupBy { it.pokemonFamily } + + pokemonFamilies.forEach { + var run = true + while (run) { + val pokemon = nextPokemonToEvolve(ctx, settings, it.key) + if (pokemon == null) { + run = false + continue + } + + Log.green("Evolving ${pokemon.pokemonId.name} with IV ${pokemon.ivRatio} and ${pokemon.cp}cp") + pokemon.evolve() + } + } + } + + fun nextPokemonToEvolve(ctx: Context, settings: Settings, family: PokemonFamilyIdOuterClass.PokemonFamilyId) : Pokemon? { + val candies = ctx.api.inventories.candyjar.getCandies(family) + val pokemonFamily = ctx.api.inventories.pokebank.pokemons.groupBy { it.pokemonFamily }.get(family) + + var evolvePriority = pokemonFamily.orEmpty().sortedByDescending { it.ivRatio } + var pokemonToEvolve = evolvePriority[0] + + // Highest in family cannot evolve and no others are high enough priority + if (pokemonToEvolve.getIvPercentage() < settings.transferIvThreshold) { + if (pokemonToEvolve.candiesToEvolve == 0) { + return null + } + } else { + val priorityEvolves = evolvePriority.filter { it.ivRatio * 100 >= settings.transferIvThreshold } + pokemonToEvolve = priorityEvolves.find { it.candiesToEvolve > 0 } + + if (pokemonToEvolve == null) { + return null + } + } + + if (pokemonToEvolve.candiesToEvolve > candies) { + Log.yellow("Would like to evolve ${pokemonToEvolve.pokemonId.name} with IV ${pokemonToEvolve.ivRatio * 100}%,\n" + + "\tbut only have ${candies}/${pokemonToEvolve.candiesToEvolve} candies") + return null + } + + if (pokemonToEvolve.pokemonId == PokemonIdOuterClass.PokemonId.EEVEE) { + EEVEE_EVOLUTION_DATA.forEach { + if (ctx.api.inventories.pokedex.getPokedexEntry(it.component1()) == null) { + pokemonToEvolve.renamePokemon(it.component2()) + return pokemonToEvolve + } + + val current = ctx.api.inventories.pokebank.getPokemonByPokemonId(it.key).sortedByDescending { it.ivRatio } + if (current[0].ivRatio < pokemonToEvolve.ivRatio) { + pokemonToEvolve.renamePokemon(it.component2()) + return pokemonToEvolve + } + } + } + + return pokemonToEvolve + } +} \ No newline at end of file diff --git a/src/main/kotlin/ink/abb/pogo/scraper/tasks/EvolvePokemon.kt b/src/main/kotlin/ink/abb/pogo/scraper/evolve/XpBatchStrategy.kt similarity index 96% rename from src/main/kotlin/ink/abb/pogo/scraper/tasks/EvolvePokemon.kt rename to src/main/kotlin/ink/abb/pogo/scraper/evolve/XpBatchStrategy.kt index 06c9bb13b..c067f3e1e 100644 --- a/src/main/kotlin/ink/abb/pogo/scraper/tasks/EvolvePokemon.kt +++ b/src/main/kotlin/ink/abb/pogo/scraper/evolve/XpBatchStrategy.kt @@ -6,7 +6,7 @@ * For more information, refer to the LICENSE file in this repositories root directory */ -package ink.abb.pogo.scraper.tasks +package ink.abb.pogo.scraper.evolve import POGOProtos.Networking.Responses.ReleasePokemonResponseOuterClass import com.pokegoapi.api.pokemon.Pokemon @@ -20,8 +20,8 @@ import ink.abb.pogo.scraper.util.cachedInventories import ink.abb.pogo.scraper.util.pokemon.getIv import ink.abb.pogo.scraper.util.pokemon.getIvPercentage -class EvolvePokemon : Task { - override fun run(bot: Bot, ctx: Context, settings: Settings) { +class XpBatchStrategy : EvolutionStrategy { + override fun evolve(bot: Bot, ctx: Context, settings: Settings) { //count the current stack of possible evolves var countEvolveStack = 0 val groupedPokemonForCount = ctx.api.inventories.pokebank.pokemons.groupBy { it.pokemonId } diff --git a/src/main/kotlin/ink/abb/pogo/scraper/tasks/Evolve.kt b/src/main/kotlin/ink/abb/pogo/scraper/tasks/Evolve.kt new file mode 100644 index 000000000..8fb9640ed --- /dev/null +++ b/src/main/kotlin/ink/abb/pogo/scraper/tasks/Evolve.kt @@ -0,0 +1,32 @@ +package ink.abb.pogo.scraper.tasks + +import ink.abb.pogo.scraper.Bot +import ink.abb.pogo.scraper.Context +import ink.abb.pogo.scraper.Settings +import ink.abb.pogo.scraper.Task +import ink.abb.pogo.scraper.evolve.EvolutionStrategy +import ink.abb.pogo.scraper.evolve.IvMaximizingStrategy +import ink.abb.pogo.scraper.evolve.XpBatchStrategy +import ink.abb.pogo.scraper.util.Log + +class Evolve : Task { + + private val DEFAULT_EVOLUTION_STRATEGY = "xp_batch" + + private val EVOLVE_STRATEGY_MAPPER = mapOf( + Pair("xp_batch", XpBatchStrategy()), + Pair("max_iv", IvMaximizingStrategy()) + ) + + override fun run(bot: Bot, ctx: Context, settings: Settings) { + if (EVOLVE_STRATEGY_MAPPER.containsKey(settings.evolutionStrategy)) { + EVOLVE_STRATEGY_MAPPER.get(settings.evolutionStrategy)?.evolve(bot, ctx, settings) + } else { + if (settings.evolutionStrategy.isNotBlank()) { + Log.red("Evolution strategy ${settings.evolutionStrategy} does not exist. Not running this task") + } else { + EVOLVE_STRATEGY_MAPPER.get(DEFAULT_EVOLUTION_STRATEGY)?.evolve(bot, ctx, settings) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/ink/abb/pogo/scraper/tasks/ReleasePokemon.kt b/src/main/kotlin/ink/abb/pogo/scraper/tasks/ReleasePokemon.kt index 6cfa14639..d981a79ec 100644 --- a/src/main/kotlin/ink/abb/pogo/scraper/tasks/ReleasePokemon.kt +++ b/src/main/kotlin/ink/abb/pogo/scraper/tasks/ReleasePokemon.kt @@ -9,6 +9,7 @@ package ink.abb.pogo.scraper.tasks import POGOProtos.Networking.Responses.ReleasePokemonResponseOuterClass.ReleasePokemonResponse.Result +import com.pokegoapi.exceptions.AsyncPokemonGoException import ink.abb.pogo.scraper.Bot import ink.abb.pogo.scraper.Context import ink.abb.pogo.scraper.Settings @@ -45,7 +46,14 @@ class ReleasePokemon : Task { if (shouldRelease) { Log.yellow("Going to transfer ${pokemon.pokemonId.name} with " + "CP ${pokemon.cp} and IV $ivPercentage%; reason: $reason") - val result = pokemon.transferPokemon() + val result : Result + try { + result = pokemon.transferPokemon() + } catch(e: AsyncPokemonGoException) { + Log.red("Failed to transfer ${pokemon.pokemonId.name} with " + + "CP ${pokemon.cp} and IV $ivPercentage% due to ${e.cause?.message}") + continue + } if(ctx.pokemonInventoryFullStatus.second.get() && !settings.catchPokemon) { // Just released a pokemon so the inventory is not full anymore