diff --git a/fishy/__init__.py b/fishy/__init__.py index ebc28bf..124b664 100644 --- a/fishy/__init__.py +++ b/fishy/__init__.py @@ -1,2 +1,4 @@ +from fishy import constants + from fishy.__main__ import main -__version__ = "0.5.2" +__version__ = constants.version diff --git a/fishy/__main__.py b/fishy/__main__.py index 8537191..1b66f90 100644 --- a/fishy/__main__.py +++ b/fishy/__main__.py @@ -9,13 +9,14 @@ import win32gui import fishy from fishy import gui, helper, web -from fishy.constants import chalutier, lam2 +from fishy.constants import chalutier, lam2, fishyqr, libgps from fishy.engine.common.event_handler import EngineEventHandler from fishy.gui import GUI, splash, update_dialog from fishy.helper import hotkey from fishy.helper.active_poll import active from fishy.helper.config import config from fishy.helper.hotkey.hotkey_process import hotkey +from fishy.helper.migration import Migration def check_window_name(title): @@ -28,6 +29,8 @@ def check_window_name(title): # noinspection PyBroadException def initialize(window_to_hide): + Migration.migrate() + helper.create_shortcut_first() helper.initialize_uid() @@ -62,10 +65,7 @@ def initialize(window_to_hide): helper.install_thread_excepthook() sys.excepthook = helper.unhandled_exception_logging - if not config.get("addoninstalled", 0) or helper.get_addonversion(chalutier[0]) < chalutier[2]: - helper.install_addon(*chalutier) - helper.install_addon(*lam2) - config.set("addoninstalled", helper.get_addonversion(chalutier[0])) + helper.install_required_addons() def main(): diff --git a/fishy/constants.py b/fishy/constants.py index ec39bc7..ba0e03c 100644 --- a/fishy/constants.py +++ b/fishy/constants.py @@ -1,6 +1,11 @@ apiversion = 2 -chalutier = ("Chalutier", "https://www.esoui.com/downloads/dl2934/Chalutier_1.1.4.zip", 114) -lam2 = ("LibAddonMenu-2.0", "https://www.esoui.com/downloads/dl7/LibAddonMenu-2.0r32.zip", 32) -fishyqr = ("FishyQR", "https://github.com/fishyboteso/FishyQR/files/6329586/FishyQR.zip", 100) +# removed since 0.5.3 +chalutier = ("Chalutier", "https://www.esoui.com/downloads/dl2934/Chalutier_1.1.4.zip", 114) + +# addons used +lam2 = ("LibAddonMenu-2.0", "https://www.esoui.com/downloads/dl7/LibAddonMenu-2.0r32.zip", 32) +fishyqr = ("FishyQR", "https://github.com/fishyboteso/FishyQR/files/7575974/FishyQR.zip", 101) libgps = ("LibGPS", "https://cdn.esoui.com/downloads/file601/LibGPS_3_0_3.zip", 30) + +version = "0.5.3" diff --git a/fishy/engine/fullautofisher/qr_detection.py b/fishy/engine/common/qr_detection.py similarity index 82% rename from fishy/engine/fullautofisher/qr_detection.py rename to fishy/engine/common/qr_detection.py index b31baa5..17fdcc1 100644 --- a/fishy/engine/fullautofisher/qr_detection.py +++ b/fishy/engine/common/qr_detection.py @@ -9,6 +9,15 @@ from pyzbar.pyzbar import decode, ZBarSymbol from fishy.helper.helper import get_documents +def image_pre_process(img): + scale_percent = 100 # percent of original size + width = int(img.shape[1] * scale_percent / 100) + height = int(img.shape[0] * scale_percent / 100) + dim = (width, height) + img = cv2.resize(img, dim, interpolation=cv2.INTER_AREA) + return img + + def get_qr_location(og_img): """ code from https://stackoverflow.com/a/45770227/4512396 @@ -41,7 +50,7 @@ def get_values_from_image(img): try: for qr in decode(img, symbols=[ZBarSymbol.QRCODE]): vals = qr.data.decode('utf-8').split(",") - return float(vals[0]), float(vals[1]), float(vals[2]) + return tuple(float(v) for v in vals) logging.error("FishyQR not found") return None diff --git a/fishy/engine/common/window_server.py b/fishy/engine/common/window_server.py index bad4b83..ad433db 100644 --- a/fishy/engine/common/window_server.py +++ b/fishy/engine/common/window_server.py @@ -1,5 +1,6 @@ import logging import math +import traceback from enum import Enum from threading import Thread @@ -75,10 +76,15 @@ def loop_end(): cv2.waitKey(25) +# noinspection PyBroadException def run(): # todo use config while WindowServer.status == Status.RUNNING: - loop() + try: + loop() + except Exception: + traceback.print_exc() + WindowServer.status = Status.CRASHED loop_end() diff --git a/fishy/engine/fullautofisher/engine.py b/fishy/engine/fullautofisher/engine.py index 9a04e04..9412798 100644 --- a/fishy/engine/fullautofisher/engine.py +++ b/fishy/engine/fullautofisher/engine.py @@ -1,4 +1,3 @@ -import math import logging import math import time @@ -16,8 +15,8 @@ from fishy.engine.fullautofisher.mode.calibrator import Calibrator from fishy.engine.fullautofisher.mode.imode import FullAutoMode from fishy.engine.fullautofisher.mode.player import Player from fishy.engine.fullautofisher.mode.recorder import Recorder -from fishy.engine.fullautofisher.qr_detection import (get_qr_location, - get_values_from_image) +from fishy.engine.common.qr_detection import (get_qr_location, + get_values_from_image, image_pre_process) from fishy.engine.semifisher import fishing_event, fishing_mode from fishy.engine.semifisher.fishing_mode import FishingMode from fishy.helper import helper, hotkey @@ -29,15 +28,6 @@ mse = mouse.Controller() kb = keyboard.Controller() -def image_pre_process(img): - scale_percent = 100 # percent of original size - width = int(img.shape[1] * scale_percent / 100) - height = int(img.shape[0] * scale_percent / 100) - dim = (width, height) - img = cv2.resize(img, dim, interpolation=cv2.INTER_AREA) - return img - - class FullAuto(IEngine): rotate_by = 30 @@ -56,12 +46,6 @@ class FullAuto(IEngine): self.mode = None def run(self): - - addons_req = [libgps, lam2, fishyqr] - for addon in addons_req: - if not helper.addon_exists(*addon): - helper.install_addon(*addon) - self.gui.bot_started(True) self.window = WindowClient(color=cv2.COLOR_RGB2GRAY, show_name="Full auto debug") diff --git a/fishy/engine/semifisher/engine.py b/fishy/engine/semifisher/engine.py index e42e9a0..4e0be35 100644 --- a/fishy/engine/semifisher/engine.py +++ b/fishy/engine/semifisher/engine.py @@ -4,14 +4,16 @@ import typing from threading import Thread from typing import Callable, Optional +import cv2 + +from fishy.helper.helper import log_raise from playsound import playsound from fishy.engine.common.IEngine import IEngine +from fishy.engine.common.qr_detection import get_qr_location, get_values_from_image, image_pre_process from fishy.engine.common.window import WindowClient from fishy.engine.semifisher import fishing_event, fishing_mode from fishy.engine.semifisher.fishing_event import FishEvent -from fishy.engine.semifisher.fishing_mode import Colors, FishingMode -from fishy.engine.semifisher.pixel_loc import PixelLoc from fishy.helper import helper from fishy.helper.luaparser import sv_color_extract @@ -22,7 +24,7 @@ if typing.TYPE_CHECKING: class SemiFisherEngine(IEngine): def __init__(self, gui_ref: Optional['Callable[[], GUI]']): super().__init__(gui_ref) - self.fishPixWindow = None + self.window = None def run(self): """ @@ -30,34 +32,43 @@ class SemiFisherEngine(IEngine): code explained in comments in detail """ fishing_event.init() - self.fishPixWindow = WindowClient() + self.window = WindowClient(color=cv2.COLOR_RGB2GRAY, show_name="semifisher debug") # check for game window and stuff self.gui.bot_started(True) - sv_color_extract(Colors) - if self.get_gui: logging.info("Starting the bot engine, look at the fishing hole to start fishing") Thread(target=self._wait_and_check).start() - while self.start and WindowClient.running(): - capture = self.fishPixWindow.get_capture() + self.window.crop = get_qr_location(self.window.get_capture()) + if self.window.crop is None: + log_raise("FishyQR not found") + while self.start and WindowClient.running(): + capture = self.window.processed_image(func=image_pre_process) + + # if window server crashed if capture is None: - # if window server crashed self.gui.bot_started(False) self.toggle_start() continue - self.fishPixWindow.crop = PixelLoc.val - fishing_mode.loop(capture[0][0]) + # crop qr and get the values from it + values = get_values_from_image(capture) + if values is None: + self.gui.bot_started(False) + self.toggle_start() + continue + + fishing_mode.loop(values[3]) time.sleep(0.1) + self.window.show(False) logging.info("Fishing engine stopped") self.gui.bot_started(False) fishing_event.unsubscribe() - self.fishPixWindow.destory() + self.window.destory() def _wait_and_check(self): time.sleep(10) @@ -71,7 +82,7 @@ class SemiFisherEngine(IEngine): t = 0 while t < 10.0: t += freq - logging.debug(str(FishingMode.CurrentMode) + ":" + str(self.fishPixWindow.get_capture()[0][0])) + logging.debug(str(FishingMode.CurrentMode) + ":" + str(self.window.get_capture()[0][0])) time.sleep(freq) logging.debug("Will display pixel values for 10 seconds") diff --git a/fishy/engine/semifisher/fishing_event.py b/fishy/engine/semifisher/fishing_event.py index de8b839..8619c88 100644 --- a/fishy/engine/semifisher/fishing_event.py +++ b/fishy/engine/semifisher/fishing_event.py @@ -107,7 +107,10 @@ def fisher_callback(event: State): def on_idle(): - if FishEvent.previousState in (State.FISHING, State.REELIN): + if FishEvent.previousState == State.REELIN: + logging.info("HOLE DEPLETED") + _sound_and_send_fishy_data() + elif FishEvent.previousState == State.FISHING: logging.info("FISHING INTERRUPTED") _sound_and_send_fishy_data() diff --git a/fishy/engine/semifisher/fishing_mode.py b/fishy/engine/semifisher/fishing_mode.py index bf8a05c..ec2c505 100644 --- a/fishy/engine/semifisher/fishing_mode.py +++ b/fishy/engine/semifisher/fishing_mode.py @@ -17,21 +17,6 @@ class State(Enum): DEAD = 15 -Colors = { - State.IDLE : [255, 255, 255], - State.LOOKAWAY : [ 76, 0, 76], - State.LOOKING : [101, 69, 0], - State.DEPLETED : [ 0, 76, 76], - State.NOBAIT : [255, 204, 0], - State.FISHING : [ 75, 156, 213], - State.REELIN : [ 0, 204, 0], - State.LOOT : [ 0, 0, 204], - State.INVFULL : [ 0, 0, 51], - State.FIGHT : [204, 0, 0], - State.DEAD : [ 51, 51, 51] -} - - def _notify(event): for subscriber in subscribers: subscriber(event) @@ -42,17 +27,12 @@ class FishingMode: PrevMode = State.IDLE -def loop(rgb): +def loop(state_num: int): """ Executed in the start of the main loop in fishy.py Changes modes, calls mode events (callbacks) when mode is changed - - :param rgb: rgb read by the bot """ - FishingMode.CurrentMode = State.IDLE - for s in State: - if all(rgb == Colors[s]): - FishingMode.CurrentMode = s + FishingMode.CurrentMode = State(state_num) if FishingMode.CurrentMode != FishingMode.PrevMode: _notify(FishingMode.CurrentMode) diff --git a/fishy/engine/semifisher/pixel_loc.py b/fishy/engine/semifisher/pixel_loc.py deleted file mode 100644 index 90854de..0000000 --- a/fishy/engine/semifisher/pixel_loc.py +++ /dev/null @@ -1,66 +0,0 @@ -import cv2 - - -def get_keypoint_from_image(img): - """ - convert image int hsv - creates a mask for brown color - uses blob detection to find a blob in the mask - filter the blobs to find the correct one - - :param img: rgb image - :return: location of the pixel which is used to detect different fishing states - """ - - # Setup SimpleBlobDetector parameters. - hsv_img = cv2.cvtColor(img, cv2.COLOR_RGB2HSV) - lower = (99, 254, 100) - upper = (100, 255, 101) - mask = cv2.inRange(hsv_img, lower, upper) - - # Setup SimpleBlobDetector parameters. - params = cv2.SimpleBlobDetector_Params() - - # Change thresholds - params.minThreshold = 10 - params.maxThreshold = 255 - - params.filterByColor = True - params.blobColor = 255 - - params.filterByCircularity = False - params.filterByConvexity = False - params.filterByInertia = False - - params.filterByArea = True - params.minArea = 10.0 - - detector = cv2.SimpleBlobDetector_create(params) - - # Detect blobs. - key_points = detector.detect(mask) - - if len(key_points) <= 0: - return None - - return int(key_points[0].pt[0]), int(key_points[0].pt[1]) - - -class PixelLoc: - """ - finds the pixel loc and store it - """ - - val = None - - @staticmethod - def config(): - """ - Uses the game window to get an image of the game screen - then uses `GetKeypointFromImage()` to find the Chalutier pixel location - :return: false if pixel loc not found - """ - - PixelLoc.val = (0, 0, 1, 1) - - return True diff --git a/fishy/gui/main_gui.py b/fishy/gui/main_gui.py index e52f043..741fe3f 100644 --- a/fishy/gui/main_gui.py +++ b/fishy/gui/main_gui.py @@ -9,7 +9,7 @@ from ttkthemes import ThemedTk from fishy import helper from fishy.web import web -from ..constants import chalutier, lam2 +from ..constants import chalutier, lam2, fishyqr from ..helper.config import config from .discord_login import discord_login from ..helper.hotkey.hotkey_process import hotkey @@ -59,15 +59,13 @@ def _create(gui: 'GUI'): filemenu.add_command(label="Update", command=helper.update) def installer(): - if filemenu.entrycget(4, 'label') == "Remove Chalutier": - if helper.remove_addon(chalutier[0]) == 0: - filemenu.entryconfigure(4, label="Install Chalutier") + if filemenu.entrycget(4, 'label') == "Remove FishyQR": + if helper.remove_addon(fishyqr[0]) == 0: + filemenu.entryconfigure(4, label="Install FishyQR") else: - r = helper.install_addon(*chalutier) - r += helper.install_addon(*lam2) - if r == 0: - filemenu.entryconfigure(4, label="Remove Chalutier") - chaEntry = "Remove Chalutier" if helper.addon_exists(chalutier[0]) else "Install Chalutier" + helper.install_required_addons(True) + filemenu.entryconfigure(4, label="Remove FishyQR") + chaEntry = "Remove FishyQR" if helper.addon_exists(fishyqr[0]) else "Install FishyQR" filemenu.add_command(label=chaEntry, command=installer) menubar.add_cascade(label="Options", menu=filemenu) diff --git a/fishy/helper/__init__.py b/fishy/helper/__init__.py index df42062..084d649 100644 --- a/fishy/helper/__init__.py +++ b/fishy/helper/__init__.py @@ -5,5 +5,5 @@ from .helper import (addon_exists, create_shortcut, create_shortcut_first, install_addon, install_thread_excepthook, manifest_file, not_implemented, open_web, playsound_multiple, remove_addon, restart, unhandled_exception_logging, - update) + update, install_required_addons) from .luaparser import sv_color_extract diff --git a/fishy/helper/helper.py b/fishy/helper/helper.py index 35ffb16..56a0a93 100644 --- a/fishy/helper/helper.py +++ b/fishy/helper/helper.py @@ -22,6 +22,8 @@ from win32gui import GetForegroundWindow, GetWindowText import fishy from fishy import web +from fishy.constants import libgps, lam2, fishyqr +from fishy.helper.config import config def playsound_multiple(path, count=2): @@ -190,17 +192,34 @@ def get_addonversion(name, url=None, v=None): return 0 +def install_required_addons(force=False): + addons_req = [libgps, lam2, fishyqr] + addon_version = config.get("addon_version", {}) + installed = False + for addon in addons_req: + if force or (addon_exists(*addon) and + (addon[0] not in addon_version or ( + addon[0] in addon_version and addon_version[addon[0]] < addon[2]))): + remove_addon(*addon) + install_addon(*addon) + addon_version[addon[0]] = addon[2] + installed = True + config.set("addon_version", addon_version) + if installed: + logging.info("Please make sure to enable \"Allow outdated addons\" in ESO") + + # noinspection PyBroadException def install_addon(name, url, v=None): try: r = requests.get(url, stream=True) z = ZipFile(BytesIO(r.content)) z.extractall(path=get_addondir()) - logging.info("Add-On " + name + - " installed successfully!\nPlease make sure to enable \"Allow outdated addons\" in ESO") + logging.info("Add-On " + name + " installed successfully!") return 0 except Exception: logging.error("Could not install Add-On " + name + ", try doing it manually") + traceback.print_exc() return 1 diff --git a/fishy/helper/migration.py b/fishy/helper/migration.py new file mode 100644 index 0000000..a11079a --- /dev/null +++ b/fishy/helper/migration.py @@ -0,0 +1,33 @@ +import logging + +from fishy.helper.auto_update import _normalize_version + +from fishy.constants import chalutier, version +from fishy.helper import helper +from .config import config + + +class Migration: + @staticmethod + def up_to_0_5_3(): + helper.remove_addon(*chalutier) + config.delete("addoninstalled") + + @staticmethod + def migrate(): + prev_version = _normalize_version(config.get("prev_version", "0.0.0")) + current_version = _normalize_version(version) + + if current_version > prev_version: + for v, f in migration_code: + if prev_version < _normalize_version(v) <= current_version: + logging.info(f"running migration for {v}") + f() + config.set("prev_version", version) + + + +migration_code = [ + # version, upgrade_code + ("0.5.3", Migration.up_to_0_5_3) +]