package dekk.pw.pokemate; import POGOProtos.Map.Fort.FortDataOuterClass.FortData; import com.google.maps.model.DirectionsStep; import com.lynden.gmapsfx.GoogleMapView; import com.lynden.gmapsfx.MapComponentInitializedListener; import com.lynden.gmapsfx.javascript.event.UIEventType; import com.lynden.gmapsfx.javascript.object.*; import com.lynden.gmapsfx.shapes.Polygon; import com.lynden.gmapsfx.shapes.PolygonOptions; import com.lynden.gmapsfx.shapes.Polyline; import com.lynden.gmapsfx.shapes.PolylineOptions; import com.pokegoapi.api.inventory.EggIncubator; import com.pokegoapi.api.inventory.Item; import com.pokegoapi.api.map.fort.Pokestop; import com.pokegoapi.api.player.PlayerProfile; import com.pokegoapi.api.pokemon.EggPokemon; import com.pokegoapi.api.pokemon.Pokemon; import com.pokegoapi.exceptions.LoginFailedException; import com.pokegoapi.exceptions.RemoteServerException; import dekk.pw.pokemate.tasks.Navigate; import dekk.pw.pokemate.util.Time; import javafx.application.Application; import javafx.application.Platform; import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import javafx.scene.web.WebEvent; import javafx.stage.Stage; import netscape.javascript.JSObject; import org.controlsfx.control.Notifications; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * Created by $ Tim Dekker on 7/23/2016. */ public class PokeMateUI extends Application implements MapComponentInitializedListener { private static final int UPDATE_TIME = 1000; private static final double XVARIANCE = Config.getRange() * 1.5; private static final double VARIANCE = Config.getRange(); private static final String NOTIFY = "$.notify({\n" + "icon: '%s',\n" + "message: '%s',\n" + "},{\n" + "icon_type: 'img'," + "type: \"info\",\n" + "animate: {\n" + "enter: 'animated bounceInDown',\n" + "exit: 'animated bounceOutUp'\n" + "},\n" + "});"; private static Marker marker; private static GoogleMapView mapComponent; private static PokeMate poke; private static String messagesForLog = ""; private static int experienceGained = 0; private static long lastExperience = 0; private GoogleMap map; private boolean directions; private final int[] requiredXp = new int[]{0, 1000, 3000, 6000, 10000, 15000, 21000, 28000, 36000, 45000, 55000, 65000, 75000, 85000, 100000, 120000, 140000, 160000, 185000, 210000, 260000, 335000, 435000, 560000, 710000, 900000, 1100000, 1350000, 1650000, 2000000, 2500000, 3000000, 3750000, 4750000, 6000000, 7500000, 9500000, 12000000, 15000000, 20000000}; //public static void main(String[] args) { // launch(args); //} static void setPoke(PokeMate p) { poke = p; } public static void toast(String message, String title, String image) { if (Config.isConsoleNotification()) System.out.println("[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "] - " + message); messagesForLog += "[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "] - " + message + "\\r\\n\\r\\n"; if (Config.isShowUI() && Config.isUserInterfaceNotification()) Platform.runLater(() -> mapComponent.getWebview().getEngine().executeScript(String.format(NOTIFY, image, message))); if (Config.isShowUI() && Config.isUiSystemNotification()) Platform.runLater(() -> Notifications.create() .graphic(new ImageView(new Image(image, 64, 64, false, false))) .title(title) .text(message) .darkStyle() .show()); } public static void addMessageToLog(String message) { messagesForLog += "[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "] - " + message + "\\r\\n\\r\\n"; } private static String millisToTimeString(long millis) { long seconds = (millis / 1000) % 60; long minutes = (millis / (1000 * 60)) % 60; long hours = (millis / (1000 * 60 * 60)) % 24; return String.format("%02d:%02d:%02d", hours, minutes, seconds); } @Override public void start(final Stage stage) throws Exception { stage.setTitle("Pokemate UI"); stage.setOnCloseRequest(t -> { Platform.exit(); System.exit(0); }); //This needs to be set to the resources directory, however, it is not play along nicely. mapComponent = new GoogleMapView("/map.html"); mapComponent.addMapInializedListener(this); mapComponent.getWebview().getEngine().setOnAlert((WebEvent<String> event) -> { }); BorderPane bp = new BorderPane(); bp.setCenter(mapComponent); Scene scene = new Scene(bp); stage.setScene(scene); stage.setWidth(1100); stage.setHeight(674); ClassLoader classloader = Thread.currentThread().getContextClassLoader(); stage.getIcons().add(new Image(classloader.getResourceAsStream("icon.png"))); stage.show(); } @Override public void mapInitialized() { Context context = PokeMate.getContext(); LatLong center = new LatLong(PokeMate.getContext().getLat().get(), PokeMate.getContext().getLng().get()); MapOptions options = new MapOptions(); options.center(center) .mapMarker(true) .zoom(16) .overviewMapControl(false) .panControl(false) .rotateControl(false) .scaleControl(false) .streetViewControl(false) .zoomControl(false) .mapType(MapTypeIdEnum.ROADMAP); map = mapComponent.createMap(options); //Highlight area we can walk in LatLong min = new LatLong(context.getLat().get() - VARIANCE, context.getLng().get() - XVARIANCE); LatLong miny = new LatLong(context.getLat().get() - VARIANCE, context.getLng().get() + XVARIANCE); LatLong minx = new LatLong(context.getLat().get() + VARIANCE, context.getLng().get() - XVARIANCE); LatLong max = new LatLong(context.getLat().get() + VARIANCE, context.getLng().get() + XVARIANCE); LatLong[] pAry = new LatLong[]{min, minx, max, miny}; MVCArray pmvc = new MVCArray(pAry); PolygonOptions polygOpts = new PolygonOptions() .paths(pmvc) .strokeColor("blue") .strokeWeight(2) .editable(false) .clickable(false) .fillColor("lightBlue") .fillOpacity(0.3); Polygon pg = new Polygon(polygOpts); map.addMapShape(pg); Time.sleep(5000); try { for(FortData gym : context.getMap().getMapObjects().getGyms()) { LatLong position = new LatLong(gym.getLatitude(), gym.getLongitude()); Marker gymMap = new Marker(new MarkerOptions().position(position).title(gym.getId()).icon("icons/gym.png")); map.addMarker(gymMap); } for(Pokestop pokestop : context.getMap().getMapObjects().getPokestops()) { LatLong position = new LatLong(pokestop.getLatitude(), pokestop.getLongitude()); Marker pokestopMap = new Marker(new MarkerOptions().position(position).title(pokestop.getId()).icon("icons/pokestop_small.png")); map.addMarker(pokestopMap); } } catch(Exception e) { e.printStackTrace(); } //Marker of current player, thread to update a 'hack refresh' marker = new Marker(new MarkerOptions().position(center).title("Player").icon("icons/trainer.gif")); map.addMarker(marker); final InfoWindowOptions infoOptions = new InfoWindowOptions(); infoOptions.content("<h3>Loading...</h3>") .position(center); InfoWindow window = new InfoWindow(infoOptions); map.addUIEventHandler(marker, UIEventType.click, (JSObject obj) -> window.open(map, marker)); window.open(map, marker); new Thread(() -> { while (true) { Platform.runLater(() -> { List<DirectionsStep[]> directionsSteps = Navigate.getDirections(); if (Navigate.getNavigationType() == Navigate.NavigationType.STREETS &&directionsSteps != null && Navigate.populated && !directions) { synchronized (poke) { List<LatLong> locs = new ArrayList<>(); for (DirectionsStep[] steps : directionsSteps) { for (DirectionsStep step : steps) { step.polyline.decodePath().forEach(a -> locs.add(new LatLong(a.lat, a.lng))); } } LatLong[] array = locs.toArray(new LatLong[0]); MVCArray mvc = new MVCArray(array); PolylineOptions polyOpts = new PolylineOptions() .path(mvc) .strokeColor("red") .strokeWeight(1) .strokeOpacity(0.8); Polyline poly = new Polyline(polyOpts); map.addMapShape(poly); directions = true; } } else if (Navigate.getNavigationType() == Navigate.NavigationType.POKESTOPS && Navigate.populated && !directions) { List<LatLong> locs = new ArrayList<>(); Navigate.getRoute().forEach(a -> locs.add(new LatLong(a.latDegrees(), a.lngDegrees()))); LatLong[] array = locs.toArray(new LatLong[0]); MVCArray mvc = new MVCArray(array); PolylineOptions polyOpts = new PolylineOptions() .path(mvc) .strokeColor("red") .strokeWeight(1) .strokeOpacity(0.8); Polyline poly = new Polyline(polyOpts); map.addMapShape(poly); directions = true; } marker.setPosition(new LatLong(context.getLat().get(), context.getLng().get())); int currentZoom = map.getZoom(); map.setZoom(currentZoom - 1); map.setZoom(currentZoom); }); //Update Thread try { Platform.runLater(() -> { updatePlayer(context, window); updatePokemon(context); updateItems(context); updateLog(); updateIncubators(context); updateEggs(context); }); Thread.sleep(UPDATE_TIME); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } private void updateEggs(Context context) { String eggsList = "\""; try { for (EggPokemon egg : context.getInventories().getHatchery().getEggs()) { String imgSrc = "icons/items/egg.png"; String walked = new DecimalFormat("#0.#").format(egg.getEggKmWalked()); String percent = new DecimalFormat("#0.#").format(((egg.getEggKmWalked() * 100) / (egg.getEggKmWalkedTarget() * 100)) * 100); String percentClass; double percentTmp = Double.valueOf(percent.replace(",", ".")); if (percentTmp >= 66) { percentClass = " progress-bar-success"; } else if (percentTmp >= 33) { percentClass = " progress-bar-warning"; } else { percentClass = " progress-bar-danger"; } eggsList += "<tr><td style='width:72px;'><img style=\'width: 70px; height: 70px;\' " + "src=\'" + imgSrc + "\'" + "></td>" + "<td>Incubated : " + (egg.isIncubate() ? "<b style='color:#00ff00;'>yes</b>" : "<b style='color:#ff0000;'>no</b>") + "<br/>State : " + walked + "/" + egg.getEggKmWalkedTarget() + "km<br/>" + "<div class='progress'><div class='progress-bar active progress-bar-striped" + percentClass + "' role='progressbar' aria-valuenow='" + percent + "' aria-valuemin='0' aria-valuemax='100' style='min-width: 2em; width: " + percent.replace(",", ".") + "%;'>" + percent + "%</div></div></td></tr>"; } } catch (LoginFailedException | RemoteServerException e) { e.printStackTrace(); } eggsList += "\""; mapComponent.getWebview().getEngine().executeScript("document.getElementById('info-eggs').innerHTML = " + eggsList); } private void updateIncubators(Context context) { String incubatorsList = "\""; try { for (EggIncubator incubator : context.getInventories().getIncubators()) { String imgSrc = "icons/items/" + (incubator.getUsesRemaining() > 0 ? "902" : "901") + ".png"; String walked = new DecimalFormat("#0.#").format(incubator.getKmCurrentlyWalked()); incubatorsList += "<tr><td style='width:72px;'><img style=\'width: 70px; height: 70px;\' " + "src=\'" + imgSrc + "\'" + "></td>" + "<td style='width: 200px;'>Currently: " + (incubator.isInUse() ? "<b style='color:#ff0000;'>In use</b>" : "<b style='color:#00ff00;'>unused</b>") + "<br/>Remaining use : " + (incubator.getUsesRemaining() > 0 ? incubator.getUsesRemaining() : "\u221e") + "<br/>Km walked : " + walked + "</td></tr>"; } } catch (LoginFailedException | RemoteServerException e) { e.printStackTrace(); } incubatorsList += "\""; mapComponent.getWebview().getEngine().executeScript("document.getElementById('info-incubators').innerHTML = " + incubatorsList); } private void updatePlayer(Context context, InfoWindow window) { PlayerProfile player = context.getProfile(); long runTime = System.currentTimeMillis() - PokeMate.startTime; try { double nextXP = requiredXp[player.getStats().getLevel()] - requiredXp[player.getStats().getLevel() - 1]; double curLevelXP = player.getStats().getExperience() - requiredXp[player.getStats().getLevel() - 1]; long curTotalXP = player.getStats().getExperience(); if (curTotalXP > lastExperience) { if (lastExperience != 0) { experienceGained += curTotalXP - lastExperience; } lastExperience = curTotalXP; } String ratio = new DecimalFormat("#0.00").format(curLevelXP / nextXP * 100.D); window.setContent("<h4>" + player.getPlayerData().getUsername() + "</h4><h5>Current Level: " + player.getStats().getLevel() + " - Progress: " + ratio + "%</h5><h5>XP/Hour: " + new DecimalFormat("###,###,###").format((experienceGained / (runTime / 3.6E6))) + "</h5><h5>XP to next level: " + new DecimalFormat("###,###,###").format(nextXP - curLevelXP) + "</h5><h5>Runtime: " + millisToTimeString(runTime) + "</h5>"); } catch (LoginFailedException | RemoteServerException e) { e.printStackTrace(); } } private void updateItems(Context context) { String itemsList = "\""; for (Item item : context.getInventories().getItemBag().getItems()) { if (item.getCount() > 0) { String imgSrc = "icons/items/" + item.getItemId().getNumber() + ".png"; itemsList += "<tr><td><img style=\'width: 70px; height: 70px;\' " + "src=\'" + imgSrc + "\'" + "></td><td>" + item.getCount() + "</td></tr>"; } } itemsList += "\""; mapComponent.getWebview().getEngine().executeScript("document.getElementById('info-items').innerHTML = " + itemsList); } private void updatePokemon(Context context) { //Update Pokemon table String pokeFilter = mapComponent.getWebview().getEngine().executeScript("$( \"#pokeFilter\" ).val();").toString(); String pokeSortType = mapComponent.getWebview().getEngine().executeScript("$( \"#pokeSortType\" ).val();").toString(); String pokeSort = pokeFilter + "-" + pokeSortType; try { switch (pokeSort) { case "pokedex-des": context.getInventories().getPokebank().getPokemons().sort((a, b) -> b.getPokemonId().getNumber() - a.getPokemonId().getNumber()); break; case "pokedex-asc": context.getInventories().getPokebank().getPokemons().sort((a, b) -> a.getPokemonId().getNumber() - b.getPokemonId().getNumber()); break; case "cp-des": context.getInventories().getPokebank().getPokemons().sort((a, b) -> b.getCp() - a.getCp()); break; case "cp-asc": context.getInventories().getPokebank().getPokemons().sort((a, b) -> a.getCp() - b.getCp()); break; case "recent-des": context.getInventories().getPokebank().getPokemons().sort((a, b) -> Long.compare(b.getCreationTimeMs(), a.getCreationTimeMs())); break; case "recent-asc": context.getInventories().getPokebank().getPokemons().sort((a, b) -> Long.compare(a.getCreationTimeMs(), b.getCreationTimeMs())); break; case "candy-des": context.getInventories().getPokebank().getPokemons().sort((a, b) -> { try { return b.getCandy() - a.getCandy(); } catch (LoginFailedException e) { e.printStackTrace(); return 0; } catch (RemoteServerException e) { e.printStackTrace(); return 0; } }); break; case "candy-asc": context.getInventories().getPokebank().getPokemons().sort((a, b) -> { try { return a.getCandy() - b.getCandy(); } catch (LoginFailedException e) { e.printStackTrace(); return 0; } catch (RemoteServerException e) { e.printStackTrace(); return 0; } }); break; case "iv-des": context.getInventories().getPokebank().getPokemons().sort((a, b) -> context.getIvRatio(b) - context.getIvRatio(a)); break; case "iv-asc": context.getInventories().getPokebank().getPokemons().sort((a, b) -> context.getIvRatio(a) - context.getIvRatio(b)); break; default: context.getInventories().getPokebank().getPokemons().sort((a, b) -> b.getCp() - a.getCp()); break; } String rows = "\""; for (Pokemon pokemon : context.getInventories().getPokebank().getPokemons()) { if (pokemon.getPokemonFamily() != null) { rows += "<tr> <td><img width=\'80\' height=\'80\' src=\'icons/" + pokemon.getPokemonId().getNumber() + ".png\'></td> <td>" + pokemon.getCp() + "</td> <td>" + pokemon.getCandy() + "</td> <td>" + context.getIvRatio(pokemon) + "</td> </tr>"; } } rows += "\""; mapComponent.getWebview().getEngine().executeScript("document.getElementById('info-body').innerHTML = " + rows); } catch (LoginFailedException | RemoteServerException e) { e.printStackTrace(); } } private void updateLog() { mapComponent.getWebview().getEngine().executeScript("document.getElementById('logTextArea').value = document.getElementById('logTextArea').value + \"" + messagesForLog + "\""); mapComponent.getWebview().getEngine().executeScript("document.getElementById('logTextArea').scrollTop = document.getElementById('logTextArea').scrollHeight"); messagesForLog = ""; } }