635 lines
22 KiB
Java
635 lines
22 KiB
Java
package com.pokemongo;
|
||
|
||
import java.awt.AWTException;
|
||
import java.awt.MouseInfo;
|
||
import java.awt.Point;
|
||
import java.awt.PointerInfo;
|
||
import java.awt.Rectangle;
|
||
import java.awt.Robot;
|
||
import java.awt.event.InputEvent;
|
||
import java.awt.image.BufferedImage;
|
||
import java.io.IOException;
|
||
import java.util.ArrayList;
|
||
import java.util.List;
|
||
|
||
import javax.imageio.ImageIO;
|
||
|
||
/**
|
||
* Automation tool for Pokémon GO.
|
||
* Finds running application, selects all Pokémon and clicks Transfer button.
|
||
*/
|
||
public class PokemonGoAutomation {
|
||
|
||
private Robot robot;
|
||
private Rectangle windowBounds;
|
||
private BufferedImage t1Template; // TRANSFER button template
|
||
private BufferedImage t2Template; // Confirmation button template
|
||
private BufferedImage t3Template;
|
||
private BufferedImage pok1Template; // Pokemon detection template
|
||
private BufferedImage includeTemplate; // Include button template
|
||
private static final int DELAY_BETWEEN_CLICKS = 100; // ms
|
||
private static final int DELAY_AFTER_ACTION = 100; // ms
|
||
private int transferredPokemonCount = 0; // Count of transferred pokemon
|
||
private Point initialMousePosition = null; // Cursor position before start of automation
|
||
private volatile boolean stopRequested = false;
|
||
|
||
public PokemonGoAutomation() throws AWTException {
|
||
this.robot = new Robot();
|
||
this.robot.setAutoDelay(50);
|
||
this.robot.setAutoWaitForIdle(true);
|
||
loadButtonTemplates();
|
||
}
|
||
|
||
/**
|
||
* Returns count of pokemon transferred so far
|
||
*/
|
||
public int getTransferredCount() {
|
||
return transferredPokemonCount;
|
||
}
|
||
|
||
/**
|
||
* Resets counter of transferred pokemon
|
||
*/
|
||
public void resetTransferredCount() {
|
||
transferredPokemonCount = 0;
|
||
}
|
||
|
||
public void requestStop() {
|
||
stopRequested = true;
|
||
}
|
||
|
||
private boolean shouldStop() {
|
||
return stopRequested || Thread.currentThread().isInterrupted();
|
||
}
|
||
|
||
/**
|
||
* Takes screenshot of window area
|
||
*/
|
||
private BufferedImage captureScreen(Rectangle area) {
|
||
return robot.createScreenCapture(area);
|
||
}
|
||
|
||
/**
|
||
* Loads templates for TRANSFER button (t1.png) and confirmation (t2.png)
|
||
*/
|
||
private void loadButtonTemplates() {
|
||
try {
|
||
String t1Paths = "/t1.png";
|
||
|
||
t1Template = ImageIO.read(getClass().getResourceAsStream(t1Paths));
|
||
System.out.println("✅ Template TRANSFER loaded from: " + t1Paths);
|
||
|
||
String t2Paths = "/t2.png";
|
||
|
||
t2Template = ImageIO.read(getClass().getResourceAsStream(t2Paths));
|
||
System.out.println("✅ Template Confirmation loaded from: " + t2Paths);
|
||
|
||
String t3Paths = "/t3.png";
|
||
|
||
t3Template = ImageIO.read(getClass().getResourceAsStream(t3Paths));
|
||
System.out.println("✅ Template Confirmation loaded from: " + t3Paths);
|
||
|
||
String pok1Paths = "/pok1.png";
|
||
|
||
pok1Template = ImageIO.read(getClass().getResourceAsStream(pok1Paths));
|
||
System.out.println("✅ Template Pokémon loaded from: " + pok1Paths);
|
||
|
||
String includePaths = "/include.png";
|
||
|
||
includeTemplate = ImageIO.read(getClass().getResourceAsStream(includePaths));
|
||
System.out.println("✅ Template INCLUDE loaded from: " + includePaths);
|
||
|
||
if (t1Template == null) {
|
||
System.out.println("⚠️ Template t1.png not found");
|
||
}
|
||
if (t2Template == null) {
|
||
System.out.println("⚠️ Template t2.png not found");
|
||
}
|
||
if (pok1Template == null) {
|
||
System.out.println("⚠️ Template pok1.png not found");
|
||
}
|
||
if (includeTemplate == null) {
|
||
System.out.println("⚠️ Template include.png not found");
|
||
}
|
||
} catch (IOException e) {
|
||
System.err.println("⚠️ Error loading templates: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Compares template with area in image - simple pixel comparison with
|
||
* optimization
|
||
*/
|
||
private double templateMatch(BufferedImage screenshot, int startX, int startY, BufferedImage template,
|
||
int tolerance) {
|
||
if (template == null)
|
||
return 0.0;
|
||
|
||
if (shouldStop()) {
|
||
return 0.0;
|
||
}
|
||
|
||
int tWidth = template.getWidth();
|
||
int tHeight = template.getHeight();
|
||
|
||
// Kontrola hranic
|
||
if (startX + tWidth > screenshot.getWidth() || startY + tHeight > screenshot.getHeight()) {
|
||
return 0.0;
|
||
}
|
||
if (startX < 0 || startY < 0) {
|
||
return 0.0;
|
||
}
|
||
|
||
int matchingPixels = 0;
|
||
int totalPixels = tWidth * tHeight;
|
||
int requiredMatch = (int) (totalPixels * 0.75); // 75% shody
|
||
|
||
for (int y = 0; y < tHeight; y++) {
|
||
if (shouldStop()) {
|
||
return 0.0;
|
||
}
|
||
for (int x = 0; x < tWidth; x++) {
|
||
int templateRGB = template.getRGB(x, y);
|
||
int screenRGB = screenshot.getRGB(startX + x, startY + y);
|
||
|
||
// Split RGB values
|
||
int tR = (templateRGB >> 16) & 0xFF;
|
||
int tG = (templateRGB >> 8) & 0xFF;
|
||
int tB = templateRGB & 0xFF;
|
||
|
||
int sR = (screenRGB >> 16) & 0xFF;
|
||
int sG = (screenRGB >> 8) & 0xFF;
|
||
int sB = screenRGB & 0xFF;
|
||
|
||
// Compare colors - increased tolerance for better detection
|
||
int rDiff = Math.abs(tR - sR);
|
||
int gDiff = Math.abs(tG - sG);
|
||
int bDiff = Math.abs(tB - sB);
|
||
|
||
if (rDiff < tolerance && gDiff < tolerance && bDiff < tolerance) {
|
||
matchingPixels++;
|
||
|
||
// Early exit - if we have not reached minimum, stop
|
||
if (matchingPixels < requiredMatch / 2 && (y * tWidth + x) > (totalPixels / 2)) {
|
||
return 0.0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Return percentage match (0.0-1.0)
|
||
return (double) matchingPixels / totalPixels;
|
||
}
|
||
|
||
/**
|
||
* Finds and returns position of confirmation button without clicking
|
||
*/
|
||
private Point findConfirmTransferButtonPosition(BufferedImage template, int tolerance) {
|
||
System.out.println("Looking for confirm TRANSFER button using template matching...");
|
||
|
||
if (shouldStop()) {
|
||
return null;
|
||
}
|
||
|
||
BufferedImage screenshot = captureScreen(windowBounds);
|
||
|
||
int tWidth = template.getWidth();
|
||
int tHeight = template.getHeight();
|
||
|
||
// Dialog is usually in upper half after TRANSFER click
|
||
int searchStartY = (int) (windowBounds.height * 0.40);
|
||
int searchEndY = (int) (windowBounds.height * 0.75);
|
||
|
||
double bestScore = 0.0;
|
||
int bestX = 0;
|
||
int bestY = 0;
|
||
|
||
// Scan Y positions with larger step - 15px instead of 5px for speed
|
||
for (int y = searchStartY; y <= searchEndY - tHeight; y += 15) {
|
||
if (shouldStop()) {
|
||
return null;
|
||
}
|
||
// Also scan X positions to find center of match
|
||
for (int x = 0; x <= windowBounds.width - tWidth; x += 30) {
|
||
double score = templateMatch(screenshot, x, y, template, tolerance);
|
||
|
||
if (score > bestScore) {
|
||
bestScore = score;
|
||
bestX = x;
|
||
bestY = y;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (bestScore > 0.85) {
|
||
Point bestMatch = new Point(
|
||
windowBounds.x + bestX + (tWidth / 2),
|
||
windowBounds.y + bestY + (tHeight / 2));
|
||
System.out.println("✅ Found confirmation button (template match) at: " + bestMatch + " (match: "
|
||
+ String.format("%.1f", bestScore * 100) + "%)");
|
||
return bestMatch;
|
||
}
|
||
|
||
System.out.println("Template not found");
|
||
return null;
|
||
}
|
||
|
||
private Point findIncludeButtonPosition(int tolerance) {
|
||
System.out.println("Looking for INCLUDE button using template matching (include.png)...");
|
||
|
||
if (shouldStop()) {
|
||
return null;
|
||
}
|
||
|
||
BufferedImage screenshot = captureScreen(windowBounds);
|
||
|
||
int tWidth = includeTemplate.getWidth();
|
||
int tHeight = includeTemplate.getHeight();
|
||
|
||
// Dialog is usually in upper half after TRANSFER click
|
||
int searchStartY = (int) (windowBounds.height * 0.40);
|
||
int searchEndY = (int) (windowBounds.height * 0.75);
|
||
|
||
double bestScore = 0.0;
|
||
int bestX = 0;
|
||
int bestY = 0;
|
||
|
||
// Scan Y positions with larger step - 15px instead of 5px for speed
|
||
for (int y = searchStartY; y <= searchEndY - tHeight; y += 15) {
|
||
if (shouldStop()) {
|
||
return null;
|
||
}
|
||
// Also scan X positions to find center of match
|
||
for (int x = 0; x <= windowBounds.width - tWidth; x += 30) {
|
||
double score = templateMatch(screenshot, x, y, includeTemplate, tolerance);
|
||
|
||
if (score > bestScore) {
|
||
bestScore = score;
|
||
bestX = x;
|
||
bestY = y;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (bestScore > 0.80) {
|
||
Point bestMatch = new Point(
|
||
windowBounds.x + bestX + (tWidth / 2),
|
||
windowBounds.y + bestY + (tHeight / 2));
|
||
System.out.println("✅ Found INCLUDE button (template match) at: " + bestMatch + " (match: "
|
||
+ String.format("%.1f", bestScore * 100) + "%)");
|
||
return bestMatch;
|
||
}
|
||
|
||
System.out.println("Template not found");
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* Finds window with name containing "Pokémon" or "Pokemon"
|
||
*/
|
||
public boolean findPokemonGoWindow() {
|
||
try {
|
||
Rectangle bounds = WindowFinder.findWindowByName("Lenovo");
|
||
|
||
if (bounds != null && bounds.width > 0 && bounds.height > 0) {
|
||
windowBounds = bounds;
|
||
System.out.println("Window found: " + windowBounds);
|
||
return true;
|
||
}
|
||
|
||
System.out.println("Pokémon GO window not found.");
|
||
return false;
|
||
|
||
} catch (Exception e) {
|
||
System.err.println("Error finding window: " + e.getMessage());
|
||
e.printStackTrace();
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Aktivuje okno aplikace
|
||
*/
|
||
public void activateWindow() {
|
||
try {
|
||
// Click on top panel of window (title bar) for activation
|
||
// Title bar is usually 30-40px high at top of window
|
||
int centerX = windowBounds.x + windowBounds.width / 2;
|
||
int titleBarY = windowBounds.y - 15; // 15px from top of window = top panel
|
||
|
||
System.out.println("Activating window by clicking title bar: (" + centerX + ", " + titleBarY + ")");
|
||
|
||
robot.mouseMove(centerX, titleBarY);
|
||
robot.delay(200);
|
||
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
|
||
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
|
||
robot.delay(DELAY_AFTER_ACTION);
|
||
|
||
System.out.println("Window activated");
|
||
} catch (Exception e) {
|
||
System.err.println("Error activating window: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Clicks Transfer button at bottom of screen
|
||
*/
|
||
public void clickTransferButton() {
|
||
if (shouldStop()) {
|
||
return;
|
||
}
|
||
|
||
// Use button detection
|
||
// Point buttonPos = findTransferButtonPosition();
|
||
Point buttonPos = getAbsolutePoint(300, 1156);
|
||
|
||
if (buttonPos == null) {
|
||
System.err.println("TRANSFER button not found!");
|
||
return;
|
||
}
|
||
|
||
robot.mouseMove(buttonPos.x, buttonPos.y);
|
||
robot.delay(DELAY_AFTER_ACTION);
|
||
|
||
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
|
||
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
|
||
|
||
System.out.println("TRANSFER button clicked!");
|
||
robot.delay(DELAY_AFTER_ACTION);
|
||
}
|
||
|
||
/**
|
||
* Confirms transfer (if confirmation dialog is needed)
|
||
*/
|
||
public void clickConfirmTransferButton(BufferedImage template, int tolerance) {
|
||
System.out.println("Confirming transfer - looking for confirmation button...");
|
||
|
||
if (shouldStop()) {
|
||
return;
|
||
}
|
||
|
||
robot.delay(100); // Wait for dialog to appear
|
||
|
||
// Find green TRANSFER button in confirmation dialog
|
||
Point buttonPos = findConfirmTransferButtonPosition(template, tolerance);
|
||
if (buttonPos == null) {
|
||
System.out.println("Confirmation button not found, skipping confirmation.");
|
||
return;
|
||
}
|
||
|
||
robot.mouseMove(buttonPos.x, buttonPos.y);
|
||
robot.delay(DELAY_BETWEEN_CLICKS);
|
||
|
||
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
|
||
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
|
||
|
||
System.out.println("Transfer finally confirmed!");
|
||
robot.delay(DELAY_AFTER_ACTION);
|
||
}
|
||
|
||
public boolean clickIncludeButton(int tolerance) {
|
||
System.out.println("Confirming transfer - looking for include.png (confirmation button)...");
|
||
|
||
if (shouldStop()) {
|
||
return false;
|
||
}
|
||
|
||
robot.delay(100); // Wait for dialog to appear
|
||
|
||
// Find green INCLUDE button in confirmation dialog
|
||
Point buttonPos = findIncludeButtonPosition(tolerance);
|
||
if (buttonPos == null) {
|
||
System.out.println("INCLUDE button not found, skipping confirmation.");
|
||
return false;
|
||
}
|
||
|
||
robot.mouseMove(buttonPos.x, buttonPos.y);
|
||
robot.delay(DELAY_BETWEEN_CLICKS);
|
||
|
||
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
|
||
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
|
||
|
||
System.out.println("Transfer finally confirmed!");
|
||
robot.delay(DELAY_AFTER_ACTION);
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Starts Transfer automation with specific number of pokemon
|
||
*
|
||
* @param totalPokemonCount Total number of pokemon to transfer
|
||
* @param delaySeconds Delay between iterations (in seconds)
|
||
*/
|
||
public void runWithCount(int totalPokemonCount, int delaySeconds) {
|
||
System.out.println("Starting Transfer automation - Pokémon count: " + totalPokemonCount + ", Delay: "
|
||
+ delaySeconds + "s");
|
||
|
||
// Save current cursor position
|
||
PointerInfo pointerInfo = MouseInfo.getPointerInfo();
|
||
initialMousePosition = pointerInfo != null ? pointerInfo.getLocation() : null;
|
||
if (initialMousePosition != null) {
|
||
System.out.println("Initial cursor position saved: " + initialMousePosition);
|
||
}
|
||
|
||
if (!findPokemonGoWindow()) {
|
||
System.err.println("Failed to find Pokémon GO window!");
|
||
return;
|
||
}
|
||
|
||
activateWindow();
|
||
|
||
System.out.println("Waiting 1 second before start...");
|
||
robot.delay(1000);
|
||
|
||
int transferredCount = 0;
|
||
|
||
// Transfer until we reach desired count
|
||
try {
|
||
while (transferredCount < totalPokemonCount) {
|
||
// Check interruption
|
||
if (shouldStop()) {
|
||
System.out.println("\n⚠️ Automation was interrupted!");
|
||
return;
|
||
}
|
||
|
||
int pokemonThisRound = Math.min(12, totalPokemonCount - transferredCount);
|
||
|
||
System.out.println(
|
||
"\n=== Iteration " + (transferredCount / 9 + 1) + " - Transferring max " + pokemonThisRound
|
||
+ " pokemon ===");
|
||
|
||
// Select pokemon and get actual number selected
|
||
int actualTransferredCount = selectAllPokemonCount(pokemonThisRound);
|
||
|
||
if (shouldStop()) {
|
||
System.out.println("\n⚠️ Automation was interrupted during selection!");
|
||
return;
|
||
}
|
||
|
||
System.out.println("Waiting for TRANSFER...");
|
||
clickTransferButton();
|
||
|
||
if (shouldStop()) {
|
||
System.out.println("\n⚠️ Automation was interrupted after clicking TRANSFER!");
|
||
return;
|
||
}
|
||
|
||
System.out.println("Waiting for INCLUDE dialog...");
|
||
robot.delay(100);
|
||
if (clickIncludeButton(110)) {
|
||
System.out.println("Waiting for confirmation dialog t3...");
|
||
robot.delay(100);
|
||
clickConfirmTransferButton(t3Template, 70);
|
||
} else {
|
||
System.out.println("Waiting for confirmation dialog t2...");
|
||
robot.delay(100);
|
||
clickConfirmTransferButton(t2Template, 70);
|
||
}
|
||
|
||
transferredCount += actualTransferredCount;
|
||
transferredPokemonCount += actualTransferredCount;
|
||
|
||
System.out.println("Transferred in this iteration: " + actualTransferredCount + "pokemon (total: "
|
||
+ transferredCount + ")");
|
||
|
||
// If there is still something to transfer, wait
|
||
if (transferredCount < totalPokemonCount) {
|
||
System.out.println("Break: " + delaySeconds + " seconds...");
|
||
// Wait with checking interruption
|
||
long endTime = System.currentTimeMillis() + (delaySeconds * 1000L);
|
||
while (System.currentTimeMillis() < endTime) {
|
||
if (shouldStop()) {
|
||
System.out.println("\n⚠️ Automation was interrupted during break!");
|
||
break;
|
||
}
|
||
robot.delay(100);
|
||
}
|
||
}
|
||
}
|
||
} finally {
|
||
// Obnovit pozici kurzoru
|
||
if (initialMousePosition != null) {
|
||
try {
|
||
robot.mouseMove(initialMousePosition.x, initialMousePosition.y);
|
||
System.out.println("Cursor returned to initial position: " + initialMousePosition);
|
||
} catch (Exception e) {
|
||
System.err.println("Error restoring cursor position: " + e.getMessage());
|
||
}
|
||
}
|
||
}
|
||
|
||
System.out.println("\n✅ Transfer automation complete! Transferred: " + transferredCount + "pokemon");
|
||
}
|
||
|
||
/**
|
||
* Selects specific number of pokemon
|
||
*
|
||
* @return Actual number of selected pokemon
|
||
*/
|
||
private int selectAllPokemonCount(int count) {
|
||
System.out.println("Starting to select " + count + "pokemon (template matching only)...");
|
||
|
||
List<Point> positions = getPossitionsAbsolute();
|
||
|
||
int maxPokemon = Math.min(count, positions.size());
|
||
System.out.println("Will select " + maxPokemon + "pokemon...");
|
||
|
||
for (int i = 0; i < maxPokemon; i++) {
|
||
if (shouldStop()) {
|
||
System.out.println("Selection interrupted.");
|
||
return i;
|
||
}
|
||
|
||
Point pos = positions.get(i);
|
||
System.out.println("Clicking on Pokémon " + (i + 1) + "/" + maxPokemon + " at position: " + pos);
|
||
|
||
robot.mouseMove(pos.x, pos.y);
|
||
|
||
if (i == 0) {
|
||
System.out.println(" -> Long press (activate multi-select)");
|
||
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
|
||
robot.delay(700);
|
||
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
|
||
robot.delay(DELAY_AFTER_ACTION);
|
||
} else {
|
||
System.out.println(" -> Normal click");
|
||
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
|
||
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
|
||
}
|
||
|
||
robot.delay(DELAY_BETWEEN_CLICKS);
|
||
}
|
||
|
||
System.out.println("Selected " + maxPokemon + "pokemon!");
|
||
System.out.println("Waiting before clicking TRANSFER...");
|
||
robot.delay(800);
|
||
|
||
return maxPokemon;
|
||
}
|
||
|
||
private List<Point> getPossitionsAbsolute() {
|
||
List<Point> possitions = new ArrayList<>();
|
||
|
||
possitions.add(getAbsolutePoint(115, 290));
|
||
possitions.add(getAbsolutePoint(310, 290));
|
||
possitions.add(getAbsolutePoint(504, 290));
|
||
|
||
possitions.add(getAbsolutePoint(115, 510));
|
||
possitions.add(getAbsolutePoint(310, 510));
|
||
possitions.add(getAbsolutePoint(504, 510));
|
||
|
||
possitions.add(getAbsolutePoint(115, 730));
|
||
possitions.add(getAbsolutePoint(310, 730));
|
||
possitions.add(getAbsolutePoint(504, 730));
|
||
|
||
possitions.add(getAbsolutePoint(115, 950));
|
||
possitions.add(getAbsolutePoint(310, 950));
|
||
possitions.add(getAbsolutePoint(504, 950));
|
||
return possitions;
|
||
}
|
||
|
||
/**
|
||
* Converts relative position in window (0.0-1.0) to absolute screen coordinates
|
||
*
|
||
* @param relativeX Relative X position (0.0 = left edge, 1.0 = right edge)
|
||
* @param relativeY Relative Y position (0.0 = top edge, 1.0 = bottom edge)
|
||
* @return Absolute position on screen
|
||
*/
|
||
private Point getAbsolutePoint(int relativeX, int relativeY) {
|
||
int xCorrection = 0;
|
||
int yCorrection = 0;
|
||
int absoluteX = windowBounds.x + relativeX + xCorrection;
|
||
int absoluteY = windowBounds.y + relativeY + yCorrection;
|
||
return new Point(absoluteX, absoluteY);
|
||
}
|
||
|
||
/**
|
||
* Cleans up all resources used by automation
|
||
* Call after automation finishes
|
||
*/
|
||
public void cleanup() {
|
||
try {
|
||
System.out.println("Cleaning up automation resources...");
|
||
|
||
// Release references to images
|
||
t1Template = null;
|
||
t2Template = null;
|
||
t3Template = null;
|
||
pok1Template = null;
|
||
includeTemplate = null;
|
||
windowBounds = null;
|
||
|
||
// Explicitly invoke garbage collector
|
||
System.gc();
|
||
System.out.println("Automation resources cleaned");
|
||
} catch (Exception e) {
|
||
System.err.println("Error cleaning up resources: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
public static void main(String[] args) {
|
||
System.out.println("=== Pokémon GO Automation Tool ===");
|
||
System.out.println("Make sure Pokémon GO is running...");
|
||
}
|
||
}
|