Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.rslbot</groupId>
<artifactId>rsl-bot</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>5.10.2</junit.version>
</properties>

<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<useModulePath>false</useModulePath>
</configuration>
</plugin>
</plugins>
</build>
</project>
76 changes: 76 additions & 0 deletions src/main/java/rslbot/BotCore.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package rslbot;

import rslbot.ocr.OcrReader;
import java.util.List;

/**
* Core automation routine that searches for battle buttons, waits for
* victory and applies auto-sell rules to obtained gear.
*/
public class BotCore {
private final ScreenUtils screen = new ScreenUtils();
private final OcrReader ocr = new OcrReader();
private volatile boolean running;

public interface ProgressListener {
void onProgress(int currentRun, int totalRuns);
}

/**
* Starts the bot loop for the given number of runs. Progress is reported
* via the supplied listener on the calling thread.
*/
public void startBot(int runs, ProgressListener progress) throws Exception {
running = true;
List<GearRules.SellRule> rules = GearRules.loadRules(
"src/main/resources/rules/Early Mid Game Basic Sell rules (1).hsf");

for (int i = 0; i < runs && running; i++) {
System.out.println("\u25B6 Starting run " + (i + 1));

// Locate and click the Fight button inside the game client.
if (!screen.findAndClick("images/fight_button.png")) {
System.out.println("Fight button not found!");
Thread.sleep(3000);
continue;
}

// Wait for the victory screen to appear.
while (running && screen.find("images/victory.png") == null) {
Thread.sleep(5000);
}
if (!running) {
break;
}

// OCR the gear popup and decide whether to sell or keep.
String gearText = ocr.readGearPopup();
if (gearText != null) {
boolean sell = GearRules.shouldSell(gearText, rules);
if (sell) {
screen.findAndClick("images/sell_button.png");
System.out.println("\uD83D\uDDD1 Sold gear: " + gearText);
} else {
screen.findAndClick("images/keep_button.png");
System.out.println("\uD83D\uDC8E Kept gear: " + gearText);
}
}

// Start another run.
screen.findAndClick("images/replay_button.png");
Thread.sleep(3000);

if (progress != null) {
progress.onProgress(i + 1, runs);
}
}
running = false;
}

/**
* Requests the running loop to stop after the current iteration.
*/
public void stop() {
running = false;
}
}
35 changes: 35 additions & 0 deletions src/main/java/rslbot/ChampionUpgrade.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package rslbot;

/**
* Utility for computing how many 1★ champions are required to create
* a champion of a given rank. In the game, promoting a champion to the
* next rank requires feeding it a number of fodder champions equal to
* its current rank, and each fodder champion must itself be ranked up
* in the same way. This results in a factorial growth in the amount of
* fodder needed.
*/
public class ChampionUpgrade {

/**
* Returns the total number of 1★ champions required to build a single
* champion of the supplied rank.
*
* @param rank target rank (must be positive)
* @return number of base 1★ champions needed
*/
public static int championsNeeded(int rank) {
if (rank < 1) {
throw new IllegalArgumentException("rank must be positive");
}
int total = 1;
for (int i = 2; i <= rank; i++) {
total *= i; // factorial growth
}
return total;
}

public static void main(String[] args) {
System.out.println("To upgrade 3★ → " + championsNeeded(3) + " fodders");
System.out.println("To upgrade 4★ → " + championsNeeded(4) + " fodders");
}
}
33 changes: 33 additions & 0 deletions src/main/java/rslbot/GearRules.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package rslbot;

import java.util.Collections;
import java.util.List;

/**
* Simple rule set that determines whether a piece of gear should be sold
* based on text extracted from the gear popup.
*/
public class GearRules {
public static class SellRule {
public String gearType;
public int minStars;
public int maxStars;
public String rarity;
public String mainStat;
public List<String> subStats;
}

public static List<SellRule> loadRules(String path) throws Exception {
// JSON parsing removed to keep the project self-contained.
return Collections.emptyList();
}

public static boolean shouldSell(String gearText, List<SellRule> rules) {
for (SellRule rule : rules) {
if (gearText.contains(rule.rarity) && gearText.contains(rule.gearType)) {
return true;
}
}
return false;
}
}
51 changes: 51 additions & 0 deletions src/main/java/rslbot/LogWindow.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package rslbot;

import javax.swing.*;
import java.io.OutputStream;
import java.io.PrintStream;

/**
* Utility that redirects System.out/err to a provided JTextArea.
* The textarea itself is expected to be displayed by the caller.
*/
public final class LogWindow {

private LogWindow() {
// utility
}

/**
* Redirects standard output streams to the given text area.
*/
public static void install(JTextArea area) {
PrintStream ps = new PrintStream(new TextAreaOutputStream(area), true);
System.setOut(ps);
System.setErr(ps);
}

private static class TextAreaOutputStream extends OutputStream {
private final JTextArea area;
private final StringBuilder buffer = new StringBuilder();

TextAreaOutputStream(JTextArea area) {
this.area = area;
}

@Override
public void write(int b) {
if (b == '\r') {
return;
}
if (b == '\n') {
String line = buffer.toString();
SwingUtilities.invokeLater(() -> {
area.append(line + System.lineSeparator());
area.setCaretPosition(area.getDocument().getLength());
});
buffer.setLength(0);
} else {
buffer.append((char) b);
}
}
}
}
12 changes: 12 additions & 0 deletions src/main/java/rslbot/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package rslbot;

/**
* Entry point for launching the bot without the settings UI.
*/
public class Main {
public static void main(String[] args) throws Exception {
System.out.println("=== RSL Bot starting ===");
BotCore bot = new BotCore();
bot.startBot(50, null); // run 50 battles by default
}
}
65 changes: 65 additions & 0 deletions src/main/java/rslbot/ScreenUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package rslbot;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;

/**
* Helper methods for locating images on the screen and clicking within the
* Raid: Shadow Legends window without hijacking the user's mouse.
*/
public class ScreenUtils {
/**
* Finds the given template and sends a click to its top-left coordinate.
*/
public boolean findAndClick(String templatePath) {
Point p = find(templatePath);
if (p != null) {
WindowClicker.click(p.x, p.y);
return true;
}
return false;
}

/**
* Returns the on-screen coordinates of the template if it is found with a
* reasonable match score, otherwise {@code null}.
*/
public Point find(String templatePath) {
try {
BufferedImage screenshot = new Robot().createScreenCapture(
new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()));

File file = new File(templatePath);
if (!file.exists()) {
System.out.println("Template not found: " + templatePath);
return null;
}

BufferedImage template = ImageIO.read(file);

for (int y = 0; y <= screenshot.getHeight() - template.getHeight(); y++) {
for (int x = 0; x <= screenshot.getWidth() - template.getWidth(); x++) {
if (matches(screenshot, template, x, y)) {
return new Point(x, y);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

private boolean matches(BufferedImage screen, BufferedImage template, int startX, int startY) {
for (int y = 0; y < template.getHeight(); y++) {
for (int x = 0; x < template.getWidth(); x++) {
if (screen.getRGB(startX + x, startY + y) != template.getRGB(x, y)) {
return false;
}
}
}
return true;
}
}
Loading