From b41f1a3060aa034b5506966824958bfe01abb825 Mon Sep 17 00:00:00 2001 From: KSPOG Date: Fri, 22 Aug 2025 02:33:54 +0200 Subject: [PATCH] Add Windows batch launcher for GUI --- .gitignore | 2 + README.md | 38 ++++++++ requirements.txt | 1 + rsl_farmer.py | 215 +++++++++++++++++++++++++++++++++++++++++++++ run_farmer_gui.bat | 3 + 5 files changed, 259 insertions(+) create mode 100644 .gitignore create mode 100644 requirements.txt create mode 100644 rsl_farmer.py create mode 100644 run_farmer_gui.bat diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a60b85 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +*.pyc diff --git a/README.md b/README.md index f976cc2..65a3367 100644 --- a/README.md +++ b/README.md @@ -16,3 +16,41 @@ This produces `build\RaidClient.dll` exporting: - `BOOL clickRaid(int x, int y)` — post a left click to the game window - `BOOL captureRaid(const char* path)` — save the client area as a BMP file + +## Python farmer + +A minimal farmer script inspired by [rslhelper](https://github.com/KSPOG/rslhelper) +resides in `rsl_farmer.py`. It uses [PyAutoGUI](https://pyautogui.readthedocs.io/) +to click the "Start"/"Replay" button and detect when a run has finished. +PyAutoGUI is available on PyPI and can be installed from your terminal with +`python -m pip install pyautogui`, or via the requirements file below. + +Install dependencies and run the farmer: + +Run these commands from a shell (not the Python `help>` prompt): + +```bash +python -m pip install -r requirements.txt # installs PyAutoGUI and other dependencies +python rsl_farmer.py --start-x 1000 --start-y 800 --complete-img victory.png --runs 10 +``` + +`complete-img` should point to an image that appears when the run ends (for example, a +screenshot of the "Victory" banner). + +Instead of supplying command line arguments, you can launch a small +configuration window: + +```bash +python rsl_farmer.py --gui +# or simply: +python rsl_farmer.py +``` + +The GUI lets you capture the start button coordinates, choose the completion +image and tweak run settings. + +On Windows you can also launch the GUI with a batch file: + +``` +run_farmer_gui.bat +``` diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..71ae159 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +pyautogui>=0.9.50 diff --git a/rsl_farmer.py b/rsl_farmer.py new file mode 100644 index 0000000..ab7bdf0 --- /dev/null +++ b/rsl_farmer.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python3 +"""Simple Raid: Shadow Legends farmer. + +This script uses :mod:`pyautogui` to click the "Start"/"Replay" button of a +Raid: Shadow Legends dungeon and waits for an image indicating the run has +finished. It is inspired by the automation features of +`rslhelper `_. + +The game client must be visible on screen and the coordinates of the +"Start"/"Replay" button supplied. An image of the "Victory" (or "Defeat") +screen is required so the script knows when to start the next run. + +Requires the [PyAutoGUI](https://pyautogui.readthedocs.io/) package, which can +be installed with ``python -m pip install pyautogui``. +""" + +from __future__ import annotations + +import argparse +import sys +import time +from dataclasses import dataclass +from typing import Tuple + +try: # pragma: no cover - tkinter may be unavailable in some environments + import tkinter as tk + from tkinter import filedialog, messagebox +except Exception: # pragma: no cover + tk = None # type: ignore + +try: + import pyautogui +except Exception: # pragma: no cover - environment may not have pyautogui + pyautogui = None # type: ignore + + +@dataclass +class FarmerConfig: + """Configuration for :class:`RSLFarmer`. + + Attributes + ---------- + start_button: + Coordinates of the "Start"/"Replay" button. + complete_img: + Path to an image on disk that appears when a run is complete, + e.g. a screenshot of the "Victory" banner. + run_delay: + Delay between checks in seconds while waiting for a run to finish. + timeout: + Maximum time in seconds to wait for a run to finish before aborting. + """ + + start_button: Tuple[int, int] + complete_img: str + run_delay: float = 2.0 + timeout: float = 120.0 + + +class RSLFarmer: + """Automates repeated runs in Raid: Shadow Legends.""" + + def __init__(self, config: FarmerConfig): + if pyautogui is None: + raise RuntimeError( + "pyautogui is required but not installed. Install it with 'python -m pip install pyautogui'." + ) + self.config = config + + def _wait_for_complete(self) -> None: + start_time = time.time() + while True: + if pyautogui.locateOnScreen(self.config.complete_img, confidence=0.8): + return + if time.time() - start_time > self.config.timeout: + raise RuntimeError("Run timed out waiting for completion image.") + time.sleep(self.config.run_delay) + + def run(self, runs: int) -> None: + """Execute a number of runs. + + Parameters + ---------- + runs: + Number of runs to perform. + """ + + for i in range(1, runs + 1): + pyautogui.click(*self.config.start_button) + self._wait_for_complete() + # click the "Replay" button, assumed to be at the same coordinates + pyautogui.click(*self.config.start_button) + time.sleep(self.config.run_delay) + + +def launch_gui() -> None: + """Open a simple Tkinter window to configure and start the farmer.""" + if tk is None: + raise RuntimeError("tkinter is required for the GUI but is not available.") + if pyautogui is None: + raise RuntimeError( + "pyautogui is required but not installed. Install it with 'python -m pip install pyautogui'." + ) + + root = tk.Tk() + root.title("RSL Farmer") + + start_x_var = tk.StringVar() + start_y_var = tk.StringVar() + img_var = tk.StringVar() + runs_var = tk.StringVar(value="1") + delay_var = tk.StringVar(value="2.0") + timeout_var = tk.StringVar(value="120.0") + + def capture_start() -> None: + root.withdraw() + time.sleep(3) + x, y = pyautogui.position() + start_x_var.set(str(x)) + start_y_var.set(str(y)) + root.deiconify() + + def browse_img() -> None: + path = filedialog.askopenfilename() + if path: + img_var.set(path) + + def start_runs() -> None: + try: + config = FarmerConfig( + start_button=(int(start_x_var.get()), int(start_y_var.get())), + complete_img=img_var.get(), + run_delay=float(delay_var.get()), + timeout=float(timeout_var.get()), + ) + runs = int(runs_var.get()) + except Exception as exc: # pragma: no cover - user input errors + messagebox.showerror("Invalid input", str(exc)) + return + + root.withdraw() + try: + RSLFarmer(config).run(runs) + except Exception as exc: # pragma: no cover - runtime errors + messagebox.showerror("Error", str(exc)) + finally: + root.deiconify() + + # Layout + frm = tk.Frame(root) + frm.pack(padx=10, pady=10) + + tk.Label(frm, text="Start X").grid(row=0, column=0, sticky="e") + tk.Entry(frm, textvariable=start_x_var, width=6).grid(row=0, column=1) + tk.Label(frm, text="Start Y").grid(row=0, column=2, sticky="e") + tk.Entry(frm, textvariable=start_y_var, width=6).grid(row=0, column=3) + tk.Button(frm, text="Capture", command=capture_start).grid(row=0, column=4, padx=(5, 0)) + + tk.Label(frm, text="Completion image").grid(row=1, column=0, sticky="e") + tk.Entry(frm, textvariable=img_var, width=25).grid(row=1, column=1, columnspan=3) + tk.Button(frm, text="Browse", command=browse_img).grid(row=1, column=4, padx=(5, 0)) + + tk.Label(frm, text="Runs").grid(row=2, column=0, sticky="e") + tk.Entry(frm, textvariable=runs_var, width=6).grid(row=2, column=1) + + tk.Label(frm, text="Delay").grid(row=2, column=2, sticky="e") + tk.Entry(frm, textvariable=delay_var, width=6).grid(row=2, column=3) + + tk.Label(frm, text="Timeout").grid(row=3, column=0, sticky="e") + tk.Entry(frm, textvariable=timeout_var, width=6).grid(row=3, column=1) + + tk.Button(frm, text="Start", command=start_runs).grid(row=4, column=0, columnspan=5, pady=(10, 0)) + + root.mainloop() + + +def _parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--gui", action="store_true", help="Launch configuration window") + parser.add_argument("--start-x", type=int, help="X coordinate of Start/Replay button") + parser.add_argument("--start-y", type=int, help="Y coordinate of Start/Replay button") + parser.add_argument("--complete-img", help="Path to an image that signifies a completed run") + parser.add_argument("--runs", type=int, default=1, help="Number of runs to perform") + parser.add_argument("--delay", type=float, default=2.0, help="Delay between checks in seconds") + parser.add_argument("--timeout", type=float, default=120.0, help="Maximum time to wait for completion image") + + argv = sys.argv[1:] + if not argv: + return argparse.Namespace(gui=True, start_x=None, start_y=None, + complete_img=None, runs=1, delay=2.0, timeout=120.0) + args = parser.parse_args(argv) + if not args.gui and ( + args.start_x is None or args.start_y is None or args.complete_img is None + ): + parser.error("--start-x, --start-y and --complete-img are required unless --gui is used") + return args + + +def main() -> None: + args = _parse_args() + if args.gui: + launch_gui() + return + config = FarmerConfig( + start_button=(args.start_x, args.start_y), + complete_img=args.complete_img, + run_delay=args.delay, + timeout=args.timeout, + ) + farmer = RSLFarmer(config) + farmer.run(args.runs) + + +if __name__ == "__main__": + main() diff --git a/run_farmer_gui.bat b/run_farmer_gui.bat new file mode 100644 index 0000000..428c549 --- /dev/null +++ b/run_farmer_gui.bat @@ -0,0 +1,3 @@ +@echo off +cd /d %~dp0 +python rsl_farmer.py --gui %*