package me.corriekay.pokegoutil.utils.pokemon; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.function.BiConsumer; import org.apache.commons.lang3.StringUtils; import com.pokegoapi.api.pokemon.Pokemon; import com.pokegoapi.exceptions.CaptchaActiveException; import com.pokegoapi.exceptions.LoginFailedException; import com.pokegoapi.exceptions.RemoteServerException; import com.pokegoapi.exceptions.hash.HashException; import com.pokegoapi.main.PokemonMeta; import POGOProtos.Networking.Responses.NicknamePokemonResponseOuterClass.NicknamePokemonResponse; import me.corriekay.pokegoutil.data.enums.PokeColumn; import me.corriekay.pokegoutil.utils.Utilities; import me.corriekay.pokegoutil.utils.helpers.UnicodeHelper; public class PokeHandler { private final ArrayList<Pokemon> mons; public PokeHandler(final Pokemon pokemon) { this(new Pokemon[] {pokemon}); } public PokeHandler(final Pokemon[] pokemon) { this(Arrays.asList(pokemon)); } public PokeHandler(final List<Pokemon> pokemon) { mons = new ArrayList<>(pokemon); } // region Static helper methods to handle Pokémon public static PokeNick generatePokemonNickname(final String pattern, final Pokemon pokemon) { final PokeNick nick = new PokeNick(pattern, pokemon); return nick; } /** * Rename a single Pokemon based on a pattern * * @param pattern The pattern to use for renaming * @param pokemon The Pokemon to rename * @return The result of type <c>NicknamePokemonResponse.Result</c> */ public static NicknamePokemonResponse.Result renameWithPattern(final String pattern, final Pokemon pokemon) { final PokeNick pokeNick = generatePokemonNickname(pattern, pokemon); if (pokeNick.toString().equals(pokemon.getNickname())) { // Why renaming to the same nickname? return NicknamePokemonResponse.Result.UNSET; // We need to use UNSET here. No chance to extend the enum } // Actually renaming the Pokémon with the calculated nickname try { final NicknamePokemonResponse.Result result = pokemon.renamePokemon(pokeNick.toString()); return result; } catch (LoginFailedException | RemoteServerException | CaptchaActiveException | HashException e) { System.out.println("Error while renaming " + PokemonUtils.getLocalPokeName(pokemon) + "(" + pokemon.getNickname() + ")! " + Utilities.getRealExceptionMessage(e)); return NicknamePokemonResponse.Result.UNRECOGNIZED; } } // endregion /** * Rename a bunch of Pokemon based on a pattern * * @param pattern The pattern to use for renaming * @param perPokeCallback Will be called for each Pokémon that has been (tried) to * rename. * @return A <c>LinkedHashMap</c> with each Pokémon as key and the result as * value. */ public LinkedHashMap<Pokemon, NicknamePokemonResponse.Result> bulkRenameWithPattern(final String pattern, final BiConsumer<NicknamePokemonResponse.Result, Pokemon> perPokeCallback) { final LinkedHashMap<Pokemon, NicknamePokemonResponse.Result> results = new LinkedHashMap<>(); mons.forEach(p -> { final NicknamePokemonResponse.Result result = renameWithPattern(pattern, p); if (perPokeCallback != null) { perPokeCallback.accept(result, p); } results.put(p, result); }); return results; } public void bulkRenameWithPattern(final String pattern) { bulkRenameWithPattern(pattern, null); } /** * This enum represents the definition of placeholder strings for Pokémon * renaming. To add a new placeholder rule, add a new enum constant, pass a * friendly Name as constructor parameter and override the <c>get</c> method * to return what should be the replacement for the enum. The enum constant * serves as placeholder. * <p> * Example: String "%cp%_%name%" for a 200cp Magikarp will result in a * renaming of that Magikarp to "200_Magicarp". */ public enum ReplacePattern { NICK("Nickname") { @Override public String get(final Pokemon p) { return PokeColumn.NICKNAME.get(p).toString(); } }, NAME("Pokémon Name") { @Override public String get(final Pokemon p) { return PokeColumn.SPECIES.get(p).toString(); } }, NAME_2("Pokémon Name (First two letters) [2]") { @Override public String get(final Pokemon p) { final int length = 2; return StringUtils.substring(PokeColumn.SPECIES.get(p).toString(), 0, length); } }, NAME_4("Pokémon Name (First four letters) [4]") { @Override public String get(final Pokemon p) { final int length = 4; return StringUtils.substring(PokeColumn.SPECIES.get(p).toString(), 0, length); } }, NAME_6("Pokémon Name (First six letters) [6]") { @Override public String get(final Pokemon p) { final int length = 6; return StringUtils.substring(PokeColumn.SPECIES.get(p).toString(), 0, length); } }, NAME_8("Pokémon Name (First eight letters) [8]") { @Override public String get(final Pokemon p) { final int length = 8; return StringUtils.substring(PokeColumn.SPECIES.get(p).toString(), 0, length); } }, CP("Combat Points") { @Override public String get(final Pokemon p) { return PokeColumn.CP.get(p).toString(); } }, CP_EVOLVED("CP if pokemon was fully evolved (equals %cp% for highest species in the family)") { @Override public String get(final Pokemon p) { return PokeColumn.CP_EVOLVED.get(p).toString(); } }, HP("Hit Points") { @Override public String get(final Pokemon p) { return PokeColumn.HP.get(p).toString(); } }, LEVEL("Pokémon Level") { @Override public String get(final Pokemon p) { return PokeColumn.LEVEL.get(p).toString(); } }, IV_RATING("IV Rating in two digits (XX for 100%) [2]") { @Override public String get(final Pokemon p) { return padPercentage((double) PokeColumn.IV_RATING.get(p)); } }, IV_HEX("IV Values in hexadecimal, like \"9FA\" (F = 15) [3]") { @Override public String get(final Pokemon p) { return (Integer.toHexString((int) PokeColumn.IV_ATTACK.get(p)) + Integer.toHexString((int) PokeColumn.IV_DEFENSE.get(p)) + Integer.toHexString((int) PokeColumn.IV_STAMINA.get(p))).toUpperCase(); } }, IV_ATT("IV Attack [2]") { @Override public String get(final Pokemon p) { return pad((int) PokeColumn.IV_ATTACK.get(p), 2); } }, IV_DEF("IV Defense [2]") { @Override public String get(final Pokemon p) { return pad((int) PokeColumn.IV_DEFENSE.get(p), 2); } }, IV_STAM("IV Stamina [2]") { @Override public String get(final Pokemon p) { return pad((int) PokeColumn.IV_STAMINA.get(p), 2); } }, IV_ATT_UNI("IV Attack Unicode (⓯ for 15) [1]") { @Override public String get(final Pokemon p) { return UnicodeHelper.get(PokeColumn.IV_ATTACK.get(p).toString()); } }, IV_DEF_UNI("IV Defense Unicode (⓯ for 15) [1]") { @Override public String get(final Pokemon p) { return UnicodeHelper.get(PokeColumn.IV_DEFENSE.get(p).toString()); } }, IV_STAM_UNI("IV Stamina Unicode (⓯ for 15) {1]") { @Override public String get(final Pokemon p) { return UnicodeHelper.get(PokeColumn.IV_STAMINA.get(p).toString()); } }, DUEL_ABILITY("Duel Ability in two digits (XX for 100%) [2]") { @Override public String get(final Pokemon p) { return padPercentage((double) PokeColumn.DUEL_ABILITY.get(p)); } }, GYM_OFFENSE("Gym Offense in two digits (XX for 100%) [2]") { @Override public String get(final Pokemon p) { return padPercentage((double) PokeColumn.GYM_OFFENSE.get(p)); } }, GYM_DEFENSE("Gym Defense in two digits (XX for 100%) [2]") { @Override public String get(final Pokemon p) { return padPercentage((double) PokeColumn.GYM_DEFENSE.get(p)); } }, DUEL_ABILITY_RATING("Duel Ability Rating in two digits (XX for 100%) [2]") { @Override public String get(final Pokemon p) { return padPercentage((double) PokeColumn.DUEL_ABILITY_RATING.get(p)); } }, GYM_OFFENSE_RATING("Gym Offense Rating in two digits (XX for 100%) [2]") { @Override public String get(final Pokemon p) { return padPercentage((double) PokeColumn.GYM_OFFENSE_RATING.get(p)); } }, GYM_DEFENSE_RATING("Gym Defense Rating in two digits (XX for 100%) [2]") { @Override public String get(final Pokemon p) { return padPercentage((double) PokeColumn.GYM_DEFENSE_RATING.get(p)); } }, MAX_CP("Maximum possible CP (with Trainer Level 40)") { @Override public String get(final Pokemon p) { return PokeColumn.MAX_CP_40.get(p).toString(); } }, MOVE_TYPE_1("Move 1 abbreviated (Ghost = Gh) [2]") { @Override public String get(final Pokemon p) { final String type = PokemonUtils.formatType(PokemonMeta.getMoveSettings(p.getMove1()).getPokemonType()); return PokemonUtils.hasStab(p.getPokemonId(), p.getMove1()) ? abbreviateType(type).toUpperCase() : abbreviateType(type).toLowerCase(); } }, MOVE_TYPE_2("Move 2 abbreviated (Ghost = Gh) [2]") { @Override public String get(final Pokemon p) { final String type = PokemonUtils.formatType(PokemonMeta.getMoveSettings(p.getMove2()).getPokemonType()); return PokemonUtils.hasStab(p.getPokemonId(), p.getMove2()) ? abbreviateType(type).toUpperCase() : abbreviateType(type).toLowerCase(); } }, MOVE_TYPE_1_UNI("Move 1 abbreviated (Eletric = ⚡) [1]") { @Override public String get(final Pokemon p) { final String type = PokemonMeta.getMoveSettings(p.getMove1()).getPokemonType().toString(); return UnicodeHelper.get(type); } }, MOVE_TYPE_2_UNI("Move 2 abbreviated (Eletric = ⚡) [1]") { @Override public String get(final Pokemon p) { final String type = PokemonMeta.getMoveSettings(p.getMove2()).getPokemonType().toString(); return UnicodeHelper.get(type); } }, DPS_1("Damage per second for Move 1") { @Override public String get(final Pokemon p) { return String.valueOf(Math.round(PokemonCalculationUtils.dpsForMove(p, true))); } }, DPS_2("Damage per second for Move 2") { @Override public String get(final Pokemon p) { return String.valueOf(Math.round(PokemonCalculationUtils.dpsForMove(p, false))); } }, MOVE_1_RATING("Rating for Move 1 (Percentage of max possible) in two digits (XX for 100%)") { @Override public String get(final Pokemon p) { return padPercentage((double) PokeColumn.MOVE_1_RATING.get(p)); } }, MOVE_2_RATING("Rating for Move 2 (Percentage of max possible) in two digits (XX for 100%)") { @Override public String get(final Pokemon p) { return padPercentage((double) PokeColumn.MOVE_2_RATING.get(p)); } }, TYPE_1("Pokémon Type 1 abbreviated (Ghost = Gh) [2]") { @Override public String get(final Pokemon p) { final String type = p.getSettings().getType().toString(); return abbreviateType(type); } }, TYPE_2("Pokémon Type 2 abbreviated (Ghost = Gh) [2]") { @Override public String get(final Pokemon p) { final String type = p.getSettings().getType2().toString(); return abbreviateType(type); } }, TYPE_1_UNI("Pokémon Type 1 Unicode (Eletric = ⚡) [1]") { @Override public String get(final Pokemon p) { final String type = p.getSettings().getType().toString(); return UnicodeHelper.get(type); } }, TYPE_2_UNI("Pokémon Type 2 Unicode (Eletric = ⚡) [1]") { @Override public String get(final Pokemon p) { final String type = p.getSettings().getType2().toString(); return UnicodeHelper.get(type); } }, SWORD_UNICODE("Sword in Unicode symbol ⚔ to represent the best offensive moveset [1]") { @Override public String get(final Pokemon p) { return UnicodeHelper.get("sword"); } }, SHIELD_UNICODE("Shield in Unicode symbol ⛨ to represent the best defensive moveset [1]") { @Override public String get(final Pokemon p) { return UnicodeHelper.get("shield"); } }, ID("Pokédex Id [3]") { @Override public String get(final Pokemon p) { final int length = 3; return pad((int) PokeColumn.POKEDEX_ID.get(p), length); } }; /** * Abbreviate Movement/Pokémon type, to two character. * "Gr" is Grass, so we make Ground "Gd". "Fi" is Fire, so we make Fighting "Fg". * * @param type The type. * @return The abbreviated type with two characters. */ private static String abbreviateType(final String type) { if ("none".equalsIgnoreCase(type)) { return "__"; } else if ("fighting".equalsIgnoreCase(type) || "ground".equalsIgnoreCase(type)) { // "Gr" is Grass, so we make Ground "Gd". "Fi" is Fire, so we make Fighting "Fg" return type.substring(0, 1).toUpperCase() + type.substring(type.length() - 1).toLowerCase(); } else { return StringUtils.capitalize(type.substring(0, 2).toLowerCase()); } } /** * Displays a number with given length. Pads if needed, makes 100 to XX if needed. * * @param number The number to pad. * @param length The number of characters to display. * @return The two-length-number. */ private static String pad(final int number, final int length) { final int max = (int) Math.pow(10, length); return (number < max) ? StringUtils.leftPad(String.valueOf(number), length, '0') : StringUtils.repeat('X', length); } /** * Display a percentage with two characters, based on the pad() function. * * @param percentage The percentage (eg. 0.75013). * @return The two-length-percentage. */ private static String padPercentage(final double percentage) { final int number = (int) Math.round(percentage * Utilities.PERCENTAGE_FACTOR); return pad(number, 2); } private final String friendlyName; ReplacePattern(final String friendlyName) { this.friendlyName = friendlyName; } /** * Returns the friendly name for the replacement pattern. Can be used to * generate explanations for possible placeholders automatically. */ @Override public String toString() { return friendlyName; } /** * This method must be overwritten and should return what should be the * replacement. * * @param p The Pokémon that receives the new nick name. * @return The value that the placeholder should be replaced with */ public abstract String get(final Pokemon p); } }