package me.corriekay.pokegoutil.utils.pokemon; import java.util.List; import com.pokegoapi.api.pokemon.Pokemon; import com.pokegoapi.main.PokemonMeta; import POGOProtos.Enums.PokemonIdOuterClass.PokemonId; import POGOProtos.Enums.PokemonMoveOuterClass.PokemonMove; import POGOProtos.Settings.Master.MoveSettingsOuterClass.MoveSettings; import POGOProtos.Settings.Master.PokemonSettingsOuterClass.PokemonSettings; import POGOProtos.Settings.Master.Pokemon.StatsAttributesOuterClass.StatsAttributes; import me.corriekay.pokegoutil.utils.ConfigKey; import me.corriekay.pokegoutil.utils.ConfigNew; import me.corriekay.pokegoutil.utils.Utilities; /** * A Utility class providing several methods to calculate stats and values of Pokémon. * Most important ivRating, Duel Ability, Gym Offense and Defense. */ public final class PokemonCalculationUtils { public static final double NORMAL_MULTIPLIER = 1.0; public static final double STAB_MULTIPLIER = 1.25; /** * Damage bonus from a critical hit - currently no damage bonus in game, change when game is fixed. */ public static final double CRIT_DAMAGE_BONUS = 0; public static final int MOVE2_CHARGE_DELAY_MS = 500; public static final int MILLISECONDS_FACTOR = 1000; public static final int WEAVE_NUMBER = 100000; public static final int MOVE_2_ADDITIONAL_DELAY = 2000; public static final int WEAVE_LENGTH_SECONDS = 100; public static final int MAX_MOVE_ENERGY = 100; /** Prevent initializing this class. */ private PokemonCalculationUtils() { } /** * Rates the IV of given Pokémon. * If the setting for Alternative IV Calculation is chosen, it uses the advanced calculation, otherwise simple * * @param p The Pokémon to rate. * @return IV Rating. */ public static double ivRating(final Pokemon p) { if (ConfigNew.getConfig().getBool(ConfigKey.ALTERNATIVE_IV_CALCULATION)) { final StatsAttributes statsAttributes = p.getSettings().getStats(); final double cpMax = (statsAttributes.getBaseAttack() + PokemonUtils.MAX_IV) * Math.pow(statsAttributes.getBaseDefense() + PokemonUtils.MAX_IV, 0.5) * Math.pow(statsAttributes.getBaseStamina() + PokemonUtils.MAX_IV, 0.5); final double cpMin = statsAttributes.getBaseAttack() * Math.pow(statsAttributes.getBaseDefense(), 0.5) * Math.pow(statsAttributes.getBaseStamina(), 0.5); final double cpIv = (statsAttributes.getBaseAttack() + p.getIndividualAttack()) * Math.pow(statsAttributes.getBaseDefense() + p.getIndividualDefense(), 0.5) * Math.pow(statsAttributes.getBaseStamina() + p.getIndividualStamina(), 0.5); return (cpIv - cpMin) / (cpMax - cpMin); } else { return Utilities.percentage(p.getIndividualAttack() + p.getIndividualDefense() + p.getIndividualStamina(), PokemonUtils.MAX_IV + PokemonUtils.MAX_IV + PokemonUtils.MAX_IV); } } /** * Calculates the no weave dps for current move. Just plain damage, without dodging or any other attack. * * @param p A Pokemon object. * @param primary If it should be calculated for the primary more or the secondary. * @return The clean dps. */ public static double dpsForMove(final Pokemon p, final boolean primary) { final PokemonMove move = primary ? p.getMove1() : p.getMove2(); return dpsForMove(p.getPokemonId(), move, primary); } /** * Calculates the no weave dps for current move. Just plain damage, without dodging or any other attack. * * @param pokemonId The pokemonId to check for. * @param move The move to calculate the dps for. * @param primary If it should be calculated for the primary more or the secondary. * @return The clean dps. */ private static double dpsForMove(final PokemonId pokemonId, final PokemonMove move, final boolean primary) { final MoveSettings moveMeta = PokemonMeta.getMoveSettings(move); final int moveDelay = primary ? 0 : MOVE2_CHARGE_DELAY_MS; double dps = (double) moveMeta.getPower() / (double) (moveMeta.getDurationMs() + moveDelay) * MILLISECONDS_FACTOR; if (PokemonUtils.hasStab(pokemonId, move)) { dps = dps * STAB_MULTIPLIER; } return dps; } /** * Rates the moveset against the best possible moveset of that species. * * @param p The Pokémon. * @param primary If primary move should be rated, or secondary. * @return The percentage rating how good it is compared to the best. */ public static double moveRating(final Pokemon p, final boolean primary) { final PokemonSettings pMeta = PokemonMeta.getPokemonSettings(p.getPokemonId()); double highestDps = 0; final List<PokemonMove> moves = primary ? pMeta.getQuickMovesList() : pMeta.getCinematicMovesList(); for (final PokemonMove move : moves) { final double dps = dpsForMove(p.getPokemonId(), move, primary); if (dps > highestDps) { highestDps = dps; } } // Now rate it final double currentDps = dpsForMove(p, primary); return Utilities.percentage(currentDps, highestDps); } /** * Duel Ability is Tankiness * Gym Offense. A reasonable measure if you don't often/ever dodge, * as then you can only attack for as long as you can stay positive on HP. * * @param p A Pokemon object * @return Rating of a Pokemon's overall attacking power considering damage, health & defense * @link https://www.reddit.com/r/TheSilphRoad/comments/4vcobt/posthotfix_pokemon_go_full_moveset_rankings/ * @link i607ch00 */ public static long duelAbility(final Pokemon p) { return duelAbility(p.getPokemonId(), p.getMove1(), p.getMove2(), p.getIndividualAttack(), p.getIndividualDefense(), p.getIndividualStamina()); } /** * Duel Ability is Tankiness * Gym Offense. A reasonable measure if you don't often/ever dodge, * as then you can only attack for as long as you can stay positive on HP. * * @param pokemonId The pokemonId of the pokemon * @param move1 The first move of the pokemon * @param move2 The second move of the pokemon * @param attackIV The attackIV of the pokemon * @param defenseIV The defenseIV of the pokemon * @param staminaIV The staminaIV of the pokemon * @return Rating of a Pokemon's overall attacking power considering damage, health & defense * @link https://www.reddit.com/r/TheSilphRoad/comments/4vcobt/posthotfix_pokemon_go_full_moveset_rankings/ * @link i607ch00 */ public static long duelAbility(final PokemonId pokemonId, final PokemonMove move1, final PokemonMove move2, final int attackIV, final int defenseIV, final int staminaIV) { final double duelAbility = PokemonCalculationUtils.gymOffense(pokemonId, move1, move2, attackIV) * PokemonCalculationUtils.tankiness(pokemonId, defenseIV, staminaIV); return Math.round(duelAbility); } /** * Gym Offense takes the better of No Weave/Weave Damage over 100s and multiplies by the * Pokemon's base attack to arrive at a ranking of raw damage output. * * @param p A Pokemon object * @return Rating of a Pokemon's pure offensive ability over time considering move set * @link https://www.reddit.com/r/TheSilphRoad/comments/4vcobt/posthotfix_pokemon_go_full_moveset_rankings/ * @link i607ch00 */ public static double gymOffense(final Pokemon p) { return gymOffense(p.getPokemonId(), p.getMove1(), p.getMove2(), p.getIndividualAttack()); } /** * Gym Offense takes the better of No Weave/Weave Damage over 100s and multiplies by the * Pokemon's base attack to arrive at a ranking of raw damage output. * * @param pokemonId The pokemonId of the pokemon * @param move1 The first move of the pokemon * @param move2 The second move of the pokemon * @param attackIV The attackIV of the pokemon * @return Rating of a Pokemon's pure offensive ability over time considering move set * @link https://www.reddit.com/r/TheSilphRoad/comments/4vcobt/posthotfix_pokemon_go_full_moveset_rankings/ * @link i607ch00 */ public static double gymOffense(final PokemonId pokemonId, final PokemonMove move1, final PokemonMove move2, final int attackIV) { final PokemonSettings meta = PokemonMeta.getPokemonSettings(pokemonId); return Math.max( PokemonCalculationUtils.dpsForMove(pokemonId, move1, true) * WEAVE_LENGTH_SECONDS, PokemonCalculationUtils.weaveDps(pokemonId, move1, move2, 0) ) * (meta.getStats().getBaseAttack() + attackIV); } /** * Gym Defense takes the calculated Gym Weave Damage over 100s and multiplies by Tankiness * to arrive at a ranking of how much damage a Pokemon will output when defending a gym. * * @param p A Pokemon object * @return Rating of a Pokemon's AI controlled gym defense over time considering move set * @link https://www.reddit.com/r/TheSilphRoad/comments/4vcobt/posthotfix_pokemon_go_full_moveset_rankings/ * @link i607ch00 */ public static long gymDefense(final Pokemon p) { return gymDefense(p.getPokemonId(), p.getMove1(), p.getMove2(), p.getIndividualAttack(), p.getIndividualDefense(), p.getIndividualStamina()); } /** * Gym Defense takes the calculated Gym Weave Damage over 100s and multiplies by Tankiness * to arrive at a ranking of how much damage a Pokemon will output when defending a gym. * * @param pokemonId The pokemonId of the pokemon * @param move1 The first move of the pokemon * @param move2 The second move of the pokemon * @param attackIV The attackIV of the pokemon * @param defenseIV The defenseIV of the pokemon * @param staminaIV The staminaIV of the pokemon * @return Rating of a Pokemon's AI controlled gym defense over time considering move set * @link https://www.reddit.com/r/TheSilphRoad/comments/4vcobt/posthotfix_pokemon_go_full_moveset_rankings/ * @link i607ch00 */ public static long gymDefense(final PokemonId pokemonId, final PokemonMove move1, final PokemonMove move2, final int attackIV, final int defenseIV, final int staminaIV) { final double gymDefense = PokemonCalculationUtils.weaveDps(pokemonId, move1, move2, MOVE_2_ADDITIONAL_DELAY) * (PokemonMeta.getPokemonSettings(pokemonId).getStats().getBaseAttack() + attackIV) * PokemonCalculationUtils.tankiness(pokemonId, defenseIV, staminaIV); return Math.round(gymDefense); } /** * Tankiness is basically Base HP * Base Def. An approximation of a Pokemon's relative ability * to soak damage compared to other species. * <p> * Used for duel ability & gym defense calculations * * @param p A Pokemon object * @return Rating of a Pokemon's tankiness :) * @link https://www.reddit.com/r/TheSilphRoad/comments/4vcobt/posthotfix_pokemon_go_full_moveset_rankings/ * @link i607ch00 */ public static long tankiness(final Pokemon p) { return tankiness(p.getPokemonId(), p.getIndividualDefense(), p.getIndividualStamina()); } /** * Tankiness is basically Base HP * Base Def. An approximation of a Pokemon's relative ability * to soak damage compared to other species. * <p> * Used for duel ability & gym defense calculations * * @param pokemonId The pokemonId of the pokemon * @param defenseIV The defenseIV of the pokemon * @param staminaIV The staminaIV of the pokemon * @return Rating of a Pokemon's tankiness :) * @link https://www.reddit.com/r/TheSilphRoad/comments/4vcobt/posthotfix_pokemon_go_full_moveset_rankings/ * @link i607ch00 */ public static long tankiness(final PokemonId pokemonId, final int defenseIV, final int staminaIV) { final PokemonSettings meta = PokemonMeta.getPokemonSettings(pokemonId); return (meta.getStats().getBaseStamina() + staminaIV) * (meta.getStats().getBaseDefense() + defenseIV); } /** * Weave Damage/100s is determined by figuring out the total Power achieved over 100 seconds * by using basic attack enough to charge up enough energy to do a charge attack, and then * using charge attack as soon as possible to not waste energy. It is highlighted in green if doing * this is the best way to output damage for a moveset. * * @param p The pokemon * @param additionalDelay Allow a delay in milliseconds for gym offense (0ms) vs gym defense (2000ms) * @return Damage over 100 seconds for a Pokemon's moveset * @link https://www.reddit.com/r/TheSilphRoad/comments/4vcobt/posthotfix_pokemon_go_full_moveset_rankings/ * @link i607ch00 */ public static double weaveDps(final Pokemon p, final int additionalDelay) { return weaveDps(p.getPokemonId(), p.getMove1(), p.getMove2(), additionalDelay); } /** * Weave Damage/100s is determined by figuring out the total Power achieved over 100 seconds * by using basic attack enough to charge up enough energy to do a charge attack, and then * using charge attack as soon as possible to not waste energy. It is highlighted in green if doing * this is the best way to output damage for a moveset. * * @param pokemonId The pokemonId of the pokemon * @param move1 The first move of the pokemon * @param move2 The second move of the pokemon * @param additionalDelay Allow a delay in milliseconds for gym offense (0ms) vs gym defense (2000ms) * @return Damage over 100 seconds for a Pokemon's moveset * @link https://www.reddit.com/r/TheSilphRoad/comments/4vcobt/posthotfix_pokemon_go_full_moveset_rankings/ * @link i607ch00 */ public static double weaveDps(final PokemonId pokemonId, final PokemonMove move1, final PokemonMove move2, final int additionalDelay) { final MoveSettings pm1 = PokemonMeta.getMoveSettings(move1); final MoveSettings pm2 = PokemonMeta.getMoveSettings(move2); final double moveOneStab = PokemonUtils.hasStab(pokemonId, move1) ? STAB_MULTIPLIER : NORMAL_MULTIPLIER; final double moveTwoStab = PokemonUtils.hasStab(pokemonId, move2) ? STAB_MULTIPLIER : NORMAL_MULTIPLIER; //Translation reference //R = Move 1 Power //S = Move 1 Stab //T = Move 1 Speed //U = Move 1 Energy //X = Move 2 Power //Y = Move 2 Stab //Z = Move 2 Crit Chance //AA = Move 2 Speed //AB = Move 2 Energy //AC = Weave Cycle Damage //AD = Average Weave Cycle Length (ms) //AF = Average Gym Weave Cycle Length (ms) //AJ1 = Crit Damage Bonus //AL1 = Charge Delay //=IF(AB2=100,CEILING(AB2/U2),AB2/U2) final double weaveEnergyUsageRatio; if (Math.abs(pm2.getEnergyDelta()) == MAX_MOVE_ENERGY) { weaveEnergyUsageRatio = Math.ceil((double) Math.abs(pm2.getEnergyDelta()) / (double) pm1.getEnergyDelta()); } else { weaveEnergyUsageRatio = (double) Math.abs(pm2.getEnergyDelta()) / (double) pm1.getEnergyDelta(); } //=IF(AB2=100,CEILING(AB2/U2),AB2/U2)*T2+(AA2+$AL$1) //=IF(AB2=100,CEILING(AB2/U2),AB2/U2)*(T2+2000)+(AA2+$AL$1) final double weaveCycleLength = weaveEnergyUsageRatio * (pm1.getDurationMs() + additionalDelay) + pm2.getDurationMs() + PokemonCalculationUtils.MOVE2_CHARGE_DELAY_MS; //=FLOOR(100000/AD2) //*(X2*(1+Y2*0.25) * (1+($AJ$1*Z2/100))) //+CEILING(FLOOR(100000/AD2)*IF(AB2=100,CEILING(AB2/U2),AB2/U2)) //*(R2*(1+(S2*0.25))) //+FLOOR((100000-(FLOOR(100000/AD2)*(AA2+$AL$1)+CEILING(FLOOR(100000/AD2)*IF(AB2=100,CEILING(AB2/U2),AB2/U2))*T2))/T2) //*(R2*(1+(S2*0.25))) //=FLOOR(100000/AF2)*(X2*(1+Y2*0.25)*(1+($AJ$1*Z2/100)))+CEILING(FLOOR(100000/AF2)*IF(AB2=100,CEILING(AB2/U2),AB2/U2))*(R2*(1+(S2*0.25))) // +FLOOR((100000-(FLOOR(100000/AF2)*(AA2+$AL$1)+CEILING(FLOOR(100000/AF2)*IF(AB2=100,CEILING(AB2/U2),AB2/U2))*(T2+2000)))/(T2+2000))*(R2*(1+(S2*0.25))) final double floorThingyCalculation = ( WEAVE_NUMBER - ( Math.floor(WEAVE_NUMBER / weaveCycleLength) * (pm2.getDurationMs() + PokemonCalculationUtils.MOVE2_CHARGE_DELAY_MS) + Math.ceil(Math.floor(WEAVE_NUMBER / weaveCycleLength) * weaveEnergyUsageRatio) * (pm1.getDurationMs() + additionalDelay) ) ) / (pm1.getDurationMs() + additionalDelay); //noinspection UnnecessaryLocalVariable final double weaveDPS = Math.floor(WEAVE_NUMBER / weaveCycleLength) * (pm2.getPower() * moveTwoStab * (1 + (PokemonCalculationUtils.CRIT_DAMAGE_BONUS * pm2.getCriticalChance()))) + Math.ceil(Math.floor(WEAVE_NUMBER / weaveCycleLength) * weaveEnergyUsageRatio) * (pm1.getPower() * moveOneStab) + Math.floor(floorThingyCalculation) * (pm1.getPower() * moveOneStab); return weaveDPS; } }